├── .gitignore
├── .idea
└── codeStyles
│ ├── Project.xml
│ └── codeStyleConfig.xml
├── .travis.yml
├── DeepLinkHelperSample.gif
├── README.md
├── app
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── adammcneilly
│ │ └── deeplinkhelper
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── adammcneilly
│ │ │ └── deeplinkhelper
│ │ │ ├── DeepLink.kt
│ │ │ ├── DeepLinkAdapter.kt
│ │ │ ├── MainActivity.kt
│ │ │ ├── MainActivityViewModel.kt
│ │ │ └── data
│ │ │ ├── DLDatabase.kt
│ │ │ ├── DLRepository.kt
│ │ │ └── DeepLinkDAO.kt
│ └── res
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ └── ic_launcher_background.xml
│ │ ├── layout
│ │ ├── activity_main.xml
│ │ └── list_item_uri.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ └── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── adammcneilly
│ └── deeplinkhelper
│ └── ExampleUnitTest.kt
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/*.xml
5 | /.idea/caches
6 | /.idea/libraries
7 | /.idea/modules.xml
8 | /.idea/workspace.xml
9 | /.idea/navEditor.xml
10 | /.idea/assetWizardSettings.xml
11 | .DS_Store
12 | /build
13 | /captures
14 | .externalNativeBuild
15 |
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | xmlns:android
24 |
25 | ^$
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | xmlns:.*
35 |
36 | ^$
37 |
38 |
39 | BY_NAME
40 |
41 |
42 |
43 |
44 |
45 |
46 | .*:id
47 |
48 | http://schemas.android.com/apk/res/android
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | .*:name
58 |
59 | http://schemas.android.com/apk/res/android
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 | name
69 |
70 | ^$
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 | style
80 |
81 | ^$
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 | .*
91 |
92 | ^$
93 |
94 |
95 | BY_NAME
96 |
97 |
98 |
99 |
100 |
101 |
102 | .*
103 |
104 | http://schemas.android.com/apk/res/android
105 |
106 |
107 | ANDROID_ATTRIBUTE_ORDER
108 |
109 |
110 |
111 |
112 |
113 |
114 | .*
115 |
116 | .*
117 |
118 |
119 | BY_NAME
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: android
2 |
3 | android:
4 | components:
5 | - tools
6 | - build-tools-28.0.3
7 | - android-28
8 | licenses:
9 | - android-sdk-license-.+
10 |
11 | script:
12 | - chmod +x gradlew
13 | - ./gradlew clean build test ktlintCheck
14 |
--------------------------------------------------------------------------------
/DeepLinkHelperSample.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/AdamMc331/DeepLinkHelper/843230221cdf266d8e0126ef9b1d0d6e9904f28a/DeepLinkHelperSample.gif
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ### DeepLink Helper
2 |
3 | This app intends to help Android developers who want to test how the apps they develop handle deep links. While we have some ways to do this, like Android Studio run configurations or push notifications, I found some limitations to those. Run configurations in Android Studio don't allow me to test how an app that's already open handles a deep link, and sending push notifications can be a hassle.
4 |
5 | This helper app comes with persistent storage, showing your latest URIs at the top, and clicking on a suggestion fills in the text box for you to easily update the URI if necessary.
6 |
7 | I hope you find this helpful! If you have any requests, please submit an issue. :)
8 |
9 | ### Sample
10 |
11 | I purposely put in some URIs that wouldn't work, both to show the error but also just to demonstrate how the functionality works.
12 |
13 | 
14 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | apply plugin: 'kotlin-android'
4 |
5 | apply plugin: 'kotlin-android-extensions'
6 |
7 | apply plugin: 'kotlin-kapt'
8 |
9 | apply plugin: 'org.jlleitschuh.gradle.ktlint'
10 |
11 | android {
12 | compileSdkVersion 31
13 |
14 | defaultConfig {
15 | applicationId "com.adammcneilly.deeplinkhelper"
16 | minSdkVersion 16
17 | targetSdkVersion 31
18 | versionCode 1
19 | versionName "1.0"
20 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
21 | }
22 | buildTypes {
23 | release {
24 | minifyEnabled false
25 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
26 | }
27 | }
28 | }
29 |
30 | ktlint {
31 | enableExperimentalRules = true
32 | }
33 |
34 | dependencies {
35 | implementation fileTree(dir: 'libs', include: ['*.jar'])
36 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
37 | implementation 'androidx.appcompat:appcompat:1.4.0'
38 | implementation 'androidx.core:core-ktx:1.7.0'
39 | implementation 'androidx.constraintlayout:constraintlayout:2.1.2'
40 | implementation 'com.google.android.material:material:1.4.0'
41 | implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
42 | implementation 'androidx.room:room-runtime:2.4.0'
43 | annotationProcessor 'androidx.room:room-compiler:2.4.0'
44 | kapt 'androidx.room:room-compiler:2.4.0'
45 | implementation 'org.greenrobot:eventbus:3.1.1'
46 | testImplementation 'junit:junit:4.13.2'
47 | androidTestImplementation 'androidx.test:runner:1.4.1-alpha03'
48 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0-alpha03'
49 | }
50 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/adammcneilly/deeplinkhelper/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package com.adammcneilly.deeplinkhelper
2 |
3 | import androidx.test.InstrumentationRegistry
4 | import androidx.test.runner.AndroidJUnit4
5 | import org.junit.Assert.assertEquals
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | /**
10 | * Instrumented test, which will execute on an Android device.
11 | *
12 | * See [testing documentation](http://d.android.com/tools/testing).
13 | */
14 | @RunWith(AndroidJUnit4::class)
15 | class ExampleInstrumentedTest {
16 | @Test
17 | fun useAppContext() {
18 | // Context of the app under test.
19 | val appContext = InstrumentationRegistry.getTargetContext()
20 | assertEquals("com.adammcneilly.deeplinkhelper", appContext.packageName)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
12 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/java/com/adammcneilly/deeplinkhelper/DeepLink.kt:
--------------------------------------------------------------------------------
1 | package com.adammcneilly.deeplinkhelper
2 |
3 | import androidx.room.Entity
4 | import androidx.room.PrimaryKey
5 |
6 | /**
7 | * A model class representing a deep link the user can click on.
8 | */
9 | @Entity
10 | data class DeepLink(
11 | @PrimaryKey val uri: String = "",
12 | val lastTimeSent: Long = System.currentTimeMillis()
13 | )
14 |
--------------------------------------------------------------------------------
/app/src/main/java/com/adammcneilly/deeplinkhelper/DeepLinkAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.adammcneilly.deeplinkhelper
2 |
3 | import android.view.LayoutInflater
4 | import android.view.View
5 | import android.view.ViewGroup
6 | import android.widget.TextView
7 | import androidx.recyclerview.widget.RecyclerView
8 |
9 | class DeepLinkAdapter(private val deepLinkClicked: (DeepLink) -> Unit) : RecyclerView.Adapter() {
10 | var deepLinks: List = emptyList()
11 | set(value) {
12 | field = value
13 | notifyDataSetChanged()
14 | }
15 |
16 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DeepLinkViewHolder {
17 | val context = parent.context
18 | val inflater = LayoutInflater.from(context)
19 | val view = inflater.inflate(R.layout.list_item_uri, parent, false)
20 | return DeepLinkViewHolder(view)
21 | }
22 |
23 | override fun getItemCount(): Int {
24 | return deepLinks.size
25 | }
26 |
27 | override fun onBindViewHolder(holder: DeepLinkViewHolder, position: Int) {
28 | holder.bindDeepLink(deepLinks[position], deepLinkClicked)
29 | }
30 |
31 | class DeepLinkViewHolder(view: View) : RecyclerView.ViewHolder(view) {
32 | private val uriTextView = view.findViewById(R.id.uri)
33 |
34 | fun bindDeepLink(deepLink: DeepLink, deepLinkClicked: (DeepLink) -> Unit) {
35 | uriTextView.text = deepLink.uri
36 | itemView.setOnClickListener {
37 | deepLinkClicked.invoke(deepLink)
38 | }
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/app/src/main/java/com/adammcneilly/deeplinkhelper/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.adammcneilly.deeplinkhelper
2 |
3 | import android.content.ActivityNotFoundException
4 | import android.content.Context
5 | import android.content.Intent
6 | import android.net.Uri
7 | import android.os.Bundle
8 | import android.view.inputmethod.InputMethodManager
9 | import android.widget.Button
10 | import android.widget.Toast
11 | import androidx.appcompat.app.AppCompatActivity
12 | import androidx.lifecycle.Observer
13 | import androidx.lifecycle.ViewModel
14 | import androidx.lifecycle.ViewModelProvider
15 | import androidx.lifecycle.ViewModelProviders
16 | import androidx.recyclerview.widget.DividerItemDecoration
17 | import androidx.recyclerview.widget.LinearLayoutManager
18 | import androidx.recyclerview.widget.RecyclerView
19 | import com.adammcneilly.deeplinkhelper.data.DLDatabase
20 | import com.adammcneilly.deeplinkhelper.data.DLRepository
21 | import com.google.android.material.textfield.TextInputEditText
22 | import org.greenrobot.eventbus.EventBus
23 | import org.greenrobot.eventbus.Subscribe
24 |
25 | class MainActivity : AppCompatActivity() {
26 | private val adapter = DeepLinkAdapter(this::deepLinkClicked)
27 | private lateinit var viewModel: MainActivityViewModel
28 |
29 | private var uriInput: TextInputEditText? = null
30 |
31 | private val viewModelFactory = object : ViewModelProvider.Factory {
32 | override fun create(p0: Class): T {
33 | val database = DLDatabase.getInMemoryDatabase(this@MainActivity)
34 | val repository = DLRepository(database)
35 |
36 | @Suppress("UNCHECKED_CAST")
37 | return MainActivityViewModel(repository) as T
38 | }
39 | }
40 |
41 | private fun deepLinkClicked(deepLink: DeepLink) {
42 | viewModel.deepLinkClicked(deepLink)
43 | }
44 |
45 | override fun onCreate(savedInstanceState: Bundle?) {
46 | super.onCreate(savedInstanceState)
47 | setContentView(R.layout.activity_main)
48 |
49 | setupViews()
50 | setupViewModel()
51 | setupRecyclerView()
52 | setupSendButton()
53 | }
54 |
55 | override fun onResume() {
56 | super.onResume()
57 | EventBus.getDefault().register(this)
58 | }
59 |
60 | override fun onPause() {
61 | super.onPause()
62 | EventBus.getDefault().unregister(this)
63 | }
64 |
65 | private fun setupViews() {
66 | uriInput = findViewById(R.id.uri_input)
67 | }
68 |
69 | private fun setupRecyclerView() {
70 | val recyclerView = findViewById(R.id.deep_link_list)
71 | recyclerView.addItemDecoration(DividerItemDecoration(this, LinearLayoutManager.VERTICAL))
72 | recyclerView.layoutManager = LinearLayoutManager(this)
73 | recyclerView.adapter = adapter
74 | }
75 |
76 | private fun setupViewModel() {
77 | viewModel =
78 | ViewModelProviders.of(this, viewModelFactory).get(MainActivityViewModel::class.java)
79 |
80 | viewModel.deepLinks.observe(
81 | this,
82 | Observer {
83 | it?.let(adapter::deepLinks::set)
84 | }
85 | )
86 |
87 | viewModel.inputText.observe(
88 | this,
89 | Observer {
90 | uriInput?.setText(it)
91 | }
92 | )
93 |
94 | viewModel.inputErrorRes.observe(
95 | this,
96 | Observer {
97 | val errorString = it?.let(this::getString)
98 | uriInput?.error = errorString
99 | }
100 | )
101 | }
102 |
103 | private fun setupSendButton() {
104 | findViewById