Skip to content

Commit ff68141

Browse files
authored
Merge pull request #97 from claraf3/basic-deeplink-new-design
Add deeplink recipe for single module
2 parents b32199d + 48ec2ad commit ff68141

File tree

11 files changed

+943
-2
lines changed

11 files changed

+943
-2
lines changed

app/build.gradle.kts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@ dependencies {
7272
implementation(libs.androidx.adaptive.layout)
7373
implementation(libs.androidx.material3.navigation3)
7474

75-
7675
implementation(libs.kotlinx.serialization.core)
7776
implementation(libs.kotlinx.serialization.json)
7877
implementation(libs.androidx.navigation3.runtime)

app/src/main/AndroidManifest.xml

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@
3737
android:theme="@style/Theme.Nav3Recipes">
3838
<intent-filter>
3939
<action android:name="android.intent.action.MAIN" />
40-
4140
<category android:name="android.intent.category.LAUNCHER" />
4241
</intent-filter>
4342
</activity>
@@ -138,6 +137,48 @@
138137
android:name=".multiplestacks.MultipleStacksActivity"
139138
android:exported="true"
140139
android:theme="@style/Theme.Nav3Recipes"/>
140+
<activity
141+
android:name=".deeplink.basic.CreateDeepLinkActivity"
142+
android:exported="true"
143+
android:theme="@style/Theme.Nav3Recipes">
144+
</activity>
145+
<activity
146+
android:name=".deeplink.basic.MainActivity"
147+
android:exported="true"
148+
android:theme="@style/Theme.Nav3Recipes">
149+
<intent-filter android:autoVerify="true">
150+
<action android:name="android.intent.action.VIEW" />
151+
<category android:name="android.intent.category.DEFAULT" />
152+
<category android:name="android.intent.category.BROWSABLE" />
153+
<data android:scheme="https"
154+
android:host="www.nav3recipes.com"/>
155+
<!-- filter for exact url -->
156+
<uri-relative-filter-group android:allow="true">
157+
<data android:path="/home" />
158+
</uri-relative-filter-group>
159+
<!-- filter for exactly two path arguments -->
160+
<uri-relative-filter-group android:allow="true">
161+
<data android:pathPattern="/users/include/[^/]+$" />
162+
</uri-relative-filter-group>
163+
<!-- filter for optional query arguments -->
164+
<uri-relative-filter-group android:allow="true">
165+
<data android:path="/users/search" />
166+
<data android:query="firstName=value!" />
167+
</uri-relative-filter-group>
168+
<uri-relative-filter-group android:allow="true">
169+
<data android:path="/users/search" />
170+
<data android:query="minAge=value!" />
171+
</uri-relative-filter-group>
172+
<uri-relative-filter-group android:allow="true">
173+
<data android:path="/users/search" />
174+
<data android:query="maxAge=value!" />
175+
</uri-relative-filter-group>
176+
<uri-relative-filter-group android:allow="true">
177+
<data android:path="/users/search" />
178+
<data android:query="location=value!" />
179+
</uri-relative-filter-group>
180+
</intent-filter>
181+
</activity>
141182
</application>
142183

143184
</manifest>

app/src/main/java/com/example/nav3recipes/RecipePickerActivity.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import com.example.nav3recipes.basicsaveable.BasicSaveableActivity
4848
import com.example.nav3recipes.bottomsheet.BottomSheetActivity
4949
import com.example.nav3recipes.commonui.CommonUiActivity
5050
import com.example.nav3recipes.conditional.ConditionalActivity
51+
import com.example.nav3recipes.deeplink.basic.CreateDeepLinkActivity
5152
import com.example.nav3recipes.dialog.DialogActivity
5253
import com.example.nav3recipes.material.listdetail.MaterialListDetailActivity
5354
import com.example.nav3recipes.material.supportingpane.MaterialSupportingPaneActivity
@@ -109,6 +110,9 @@ private val recipes = listOf(
109110
Heading("Returning Results"),
110111
Recipe("Return result as Event", ResultEventActivity::class.java),
111112
Recipe("Return result as State", ResultStateActivity::class.java),
113+
114+
Heading("Deeplink"),
115+
Recipe("Parse Intent", CreateDeepLinkActivity::class.java),
112116
)
113117

114118
class RecipePickerActivity : ComponentActivity() {
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
package com.example.nav3recipes.deeplink.basic
2+
3+
import android.os.Bundle
4+
import androidx.activity.ComponentActivity
5+
import androidx.activity.compose.setContent
6+
import androidx.compose.runtime.LaunchedEffect
7+
import androidx.compose.runtime.getValue
8+
import androidx.compose.runtime.mutableStateMapOf
9+
import androidx.compose.runtime.mutableStateOf
10+
import androidx.compose.runtime.remember
11+
import androidx.compose.runtime.setValue
12+
import com.example.nav3recipes.deeplink.basic.ui.DeepLinkButton
13+
import com.example.nav3recipes.deeplink.basic.ui.EMPTY
14+
import com.example.nav3recipes.deeplink.basic.ui.EntryScreen
15+
import com.example.nav3recipes.deeplink.basic.ui.FIRST_NAME_JOHN
16+
import com.example.nav3recipes.deeplink.basic.ui.FIRST_NAME_JULIE
17+
import com.example.nav3recipes.deeplink.basic.ui.FIRST_NAME_MARY
18+
import com.example.nav3recipes.deeplink.basic.ui.FIRST_NAME_TOM
19+
import com.example.nav3recipes.deeplink.basic.ui.LOCATION_BC
20+
import com.example.nav3recipes.deeplink.basic.ui.LOCATION_BR
21+
import com.example.nav3recipes.deeplink.basic.ui.LOCATION_CA
22+
import com.example.nav3recipes.deeplink.basic.ui.LOCATION_US
23+
import com.example.nav3recipes.deeplink.basic.ui.MenuDropDown
24+
import com.example.nav3recipes.deeplink.basic.ui.MenuTextInput
25+
import com.example.nav3recipes.deeplink.basic.ui.PATH_BASE
26+
import com.example.nav3recipes.deeplink.basic.ui.PATH_INCLUDE
27+
import com.example.nav3recipes.deeplink.basic.ui.PATH_SEARCH
28+
import com.example.nav3recipes.deeplink.basic.ui.STRING_LITERAL_HOME
29+
import com.example.nav3recipes.deeplink.basic.ui.SearchKey
30+
import com.example.nav3recipes.deeplink.basic.ui.TextContent
31+
import com.example.nav3recipes.deeplink.basic.ui.HomeKey
32+
import com.example.nav3recipes.deeplink.basic.ui.UsersKey
33+
34+
/**
35+
* This activity allows the user to create a deep link and make a request with it.
36+
*
37+
* **HOW THIS RECIPE WORKS** it consists of two activities - [CreateDeepLinkActivity] to construct
38+
* and trigger the deeplink request, and the [MainActivity] to show how an app can handle
39+
* that request.
40+
*
41+
* **DEMONSTRATED FORMS OF DEEPLINK** The [MainActivity] has a several backStack keys to
42+
* demonstrate different types of supported deeplinks:
43+
* 1. [HomeKey] - deeplink with an exact url (no deeplink arguments)
44+
* 2. [UsersKey] - deeplink with path arguments
45+
* 3. [SearchKey] - deeplink with query arguments
46+
* See [MainActivity.deepLinkPatterns] for the actual url pattern of each.
47+
*
48+
* **RECIPE STRUCTURE** This recipe consists of three main packages:
49+
* 1. basic.deeplink - Contains the two activities
50+
* 2. basic.deeplink.ui - Contains the activity UI code, i.e. Screens, global string variables etc
51+
* 3. basic.deeplink.deeplinkutil - Contains the classes and helper methods to parse and match
52+
* the deeplinks
53+
*
54+
* See [MainActivity] for how the requested deeplink is handled.
55+
*/
56+
class CreateDeepLinkActivity : ComponentActivity() {
57+
override fun onCreate(savedInstanceState: Bundle?) {
58+
super.onCreate(savedInstanceState)
59+
60+
setContent {
61+
/**
62+
* UI for deeplink sandbox
63+
*/
64+
EntryScreen("Sandbox - Build Your Deeplink") {
65+
TextContent("Base url:\n${PATH_BASE}/")
66+
var showFilterOptions by remember { mutableStateOf(false) }
67+
val selectedPath = remember { mutableStateOf(MENU_OPTIONS_PATH[KEY_PATH]?.first()) }
68+
69+
var showQueryOptions by remember { mutableStateOf(false) }
70+
var selectedFilter by remember { mutableStateOf("") }
71+
val selectedSearchQuery = remember { mutableStateMapOf<String, String>() }
72+
73+
// manage path options
74+
MenuDropDown(
75+
menuOptions = MENU_OPTIONS_PATH,
76+
) { _, selection ->
77+
selectedPath.value = selection
78+
when (selection) {
79+
PATH_SEARCH -> {
80+
showQueryOptions = true
81+
showFilterOptions = false
82+
}
83+
84+
PATH_INCLUDE -> {
85+
showQueryOptions = false
86+
showFilterOptions = true
87+
}
88+
89+
else -> {
90+
showQueryOptions = false
91+
showFilterOptions = false
92+
}
93+
}
94+
}
95+
96+
// manage path filter options, reset state if menu is closed
97+
LaunchedEffect(showFilterOptions) {
98+
selectedFilter = if (showFilterOptions) {
99+
MENU_OPTIONS_FILTER.values.first().first()
100+
} else {
101+
""
102+
}
103+
}
104+
if (showFilterOptions) {
105+
MenuDropDown(
106+
menuOptions = MENU_OPTIONS_FILTER,
107+
) { _, selected ->
108+
selectedFilter = selected
109+
}
110+
}
111+
112+
// manage query options, reset state if menu is closed
113+
LaunchedEffect(showQueryOptions) {
114+
if (showQueryOptions) {
115+
val initEntry = MENU_OPTIONS_SEARCH.entries.first()
116+
selectedSearchQuery[initEntry.key] = initEntry.value.first()
117+
} else {
118+
selectedSearchQuery.clear()
119+
}
120+
}
121+
if (showQueryOptions) {
122+
MenuTextInput(
123+
menuLabels = MENU_LABELS_SEARCH,
124+
) { label, selected ->
125+
selectedSearchQuery[label] = selected
126+
}
127+
MenuDropDown(
128+
menuOptions = MENU_OPTIONS_SEARCH,
129+
) { label, selected ->
130+
selectedSearchQuery[label] = selected
131+
}
132+
}
133+
134+
// form final deeplink url
135+
val arguments = when (selectedPath.value) {
136+
PATH_INCLUDE -> "/${selectedFilter}"
137+
PATH_SEARCH -> {
138+
buildString {
139+
selectedSearchQuery.forEach { entry ->
140+
if (entry.value.isNotEmpty()) {
141+
val prefix = if (isEmpty()) "?" else "&"
142+
append("$prefix${entry.key}=${entry.value}")
143+
}
144+
}
145+
}
146+
}
147+
148+
else -> ""
149+
}
150+
val finalUrl = "${PATH_BASE}/${selectedPath.value}$arguments"
151+
TextContent("Final url:\n$finalUrl")
152+
// deeplink to target
153+
DeepLinkButton(
154+
context = this@CreateDeepLinkActivity,
155+
targetActivity = MainActivity::class.java,
156+
deepLinkUrl = finalUrl
157+
)
158+
}
159+
}
160+
}
161+
}
162+
163+
private const val KEY_PATH = "path"
164+
private val MENU_OPTIONS_PATH = mapOf(
165+
KEY_PATH to listOf(
166+
STRING_LITERAL_HOME,
167+
PATH_INCLUDE,
168+
PATH_SEARCH,
169+
),
170+
)
171+
172+
private val MENU_OPTIONS_FILTER = mapOf(
173+
UsersKey.FILTER_KEY to listOf(UsersKey.FILTER_OPTION_RECENTLY_ADDED, UsersKey.FILTER_OPTION_ALL),
174+
)
175+
176+
private val MENU_OPTIONS_SEARCH = mapOf(
177+
SearchKey::firstName.name to listOf(
178+
EMPTY,
179+
FIRST_NAME_JOHN,
180+
FIRST_NAME_TOM,
181+
FIRST_NAME_MARY,
182+
FIRST_NAME_JULIE
183+
),
184+
SearchKey::location.name to listOf(EMPTY, LOCATION_CA, LOCATION_BC, LOCATION_BR, LOCATION_US)
185+
)
186+
187+
private val MENU_LABELS_SEARCH = listOf(SearchKey::ageMin.name, SearchKey::ageMax.name)
188+
189+

0 commit comments

Comments
 (0)