Skip to content

Commit 03bc869

Browse files
committed
Address the simpler feedbacks
Rename activites, classes, and parameters.
1 parent 6f6717a commit 03bc869

File tree

7 files changed

+178
-68
lines changed

7 files changed

+178
-68
lines changed

app/src/main/AndroidManifest.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,12 +141,12 @@
141141
android:exported="true"
142142
android:theme="@style/Theme.Nav3Recipes"/>
143143
<activity
144-
android:name=".deeplink.parseintent.singleModule.ParseIntentLandingActivity"
144+
android:name=".deeplink.parseintent.singleModule.CreateDeepLinkActivity"
145145
android:exported="true"
146146
android:theme="@style/Theme.Nav3Recipes">
147147
</activity>
148148
<activity
149-
android:name=".deeplink.parseintent.singleModule.ParseIntentActivity"
149+
android:name=".deeplink.parseintent.singleModule.MainActivity"
150150
android:exported="true"
151151
android:theme="@style/Theme.Nav3Recipes">
152152
<intent-filter android:autoVerify="true">

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import com.example.nav3recipes.basicdsl.BasicDslActivity
3131
import com.example.nav3recipes.basicsaveable.BasicSaveableActivity
3232
import com.example.nav3recipes.commonui.CommonUiActivity
3333
import com.example.nav3recipes.conditional.ConditionalActivity
34-
import com.example.nav3recipes.deeplink.parseintent.singleModule.ParseIntentLandingActivity
34+
import com.example.nav3recipes.deeplink.parseintent.singleModule.CreateDeepLinkActivity
3535
import com.example.nav3recipes.dialog.DialogActivity
3636
import com.example.nav3recipes.modular.hilt.ModularActivity
3737
import com.example.nav3recipes.passingarguments.viewmodels.basic.BasicViewModelsActivity
@@ -84,7 +84,7 @@ private val recipes = listOf(
8484
Recipe("Return result as State", ResultStateActivity::class.java),
8585

8686
Heading("Deeplink"),
87-
Recipe("Parse Intent", ParseIntentLandingActivity::class.java),
87+
Recipe("Parse Intent", CreateDeepLinkActivity::class.java),
8888
)
8989

9090
class RecipePickerActivity : ComponentActivity() {

app/src/main/java/com/example/nav3recipes/deeplink/parseintent/singleModule/CommonScreens.kt

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,53 @@ import androidx.compose.ui.unit.TextUnit
3232
import androidx.compose.ui.unit.dp
3333
import androidx.compose.ui.unit.sp
3434
import androidx.core.net.toUri
35+
import androidx.navigation3.runtime.NavKey
36+
import kotlinx.serialization.Serializable
37+
38+
internal interface NavRecipeKey: NavKey {
39+
val name: String
40+
}
41+
42+
@Serializable
43+
internal object HomeKey: NavRecipeKey {
44+
override val name: String = STRING_LITERAL_HOME
45+
}
46+
47+
@Serializable
48+
internal data class UsersKey(
49+
val filter: String,
50+
): NavRecipeKey {
51+
override val name: String = STRING_LITERAL_USERS
52+
companion object {
53+
const val FILTER_KEY = STRING_LITERAL_FILTER
54+
const val FILTER_OPTION_RECENTLY_ADDED = "recentlyAdded"
55+
const val FILTER_OPTION_ALL = "all"
56+
}
57+
}
58+
59+
@Serializable
60+
internal data class SearchKey(
61+
val firstName: String? = null,
62+
val ageMin: Int? = null,
63+
val ageMax: Int? = null,
64+
val location: String? = null,
65+
): NavRecipeKey {
66+
override val name: String = STRING_LITERAL_SEARCH
67+
}
68+
69+
@Serializable
70+
internal data class User(
71+
val firstName: String,
72+
val age: Int,
73+
val location: String,
74+
)
3575

3676
@Composable
37-
internal fun EntryScreen(text: String, block: @Composable () -> Unit = { }) {
77+
internal fun EntryScreen(text: String, content: @Composable () -> Unit = { }) {
3878
Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
3979
Column(horizontalAlignment = Alignment.CenterHorizontally, verticalArrangement = Arrangement.spacedBy(10.dp)) {
4080
Text(text, fontWeight = FontWeight.Bold, fontSize = FONT_SIZE_TITLE)
41-
block()
81+
content()
4282
}
4383
}
4484
}
Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,11 @@ import androidx.compose.runtime.remember
1111
import androidx.compose.runtime.setValue
1212

1313
/**
14-
* Landing page for this recipe. It displays several options users can choose from to formulate
15-
* the final deeplink request.
14+
* This activity allows the user to create a deep link and make a request with it
1615
*
17-
* See [ParseIntentActivity] for how the requested deeplink is handled.
16+
* See [MainActivity] for how the requested deeplink is handled.
1817
*/
19-
class ParseIntentLandingActivity : ComponentActivity() {
18+
class CreateDeepLinkActivity : ComponentActivity() {
2019
override fun onCreate(savedInstanceState: Bundle?) {
2120
super.onCreate(savedInstanceState)
2221

@@ -111,8 +110,8 @@ class ParseIntentLandingActivity : ComponentActivity() {
111110
TextContent("Final url:\n$finalUrl")
112111
// deeplink to target
113112
DeepLinkButton(
114-
context = this@ParseIntentLandingActivity,
115-
targetActivity = ParseIntentActivity::class.java,
113+
context = this@CreateDeepLinkActivity,
114+
targetActivity = MainActivity::class.java,
116115
deepLinkUrl = finalUrl
117116
)
118117
}

app/src/main/java/com/example/nav3recipes/deeplink/parseintent/singleModule/DeepLinkUtil.kt

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -40,19 +40,19 @@ internal class DeepLinkRequest(
4040
*
4141
* returns a [DeepLinkMatchResult] if this matches the candidate, returns null otherwise
4242
*/
43-
fun <T : NavRecipeKey> match(deepLink: DeepLink<T>): DeepLinkMatchResult<T>? {
44-
if (pathSegments.size != deepLink.pathSegments.size) return null
43+
fun <T : NavRecipeKey> match(deepLinkPattern: DeepLinkPattern<T>): DeepLinkMatchResult<T>? {
44+
if (pathSegments.size != deepLinkPattern.pathSegments.size) return null
4545
// exact match (url does not contain any arguments)
46-
if (uri == deepLink.uriPattern)
47-
return DeepLinkMatchResult(deepLink.serializer, mapOf())
46+
if (uri == deepLinkPattern.uriPattern)
47+
return DeepLinkMatchResult(deepLinkPattern.serializer, mapOf())
4848

4949
val args = mutableMapOf<String, Any>()
5050
// match the path
5151
pathSegments
5252
.asSequence()
5353
// zip to compare the two objects side by side, order matters here so we
5454
// need to make sure the compared segments are at the same position within the url
55-
.zip(deepLink.pathSegments.asSequence())
55+
.zip(deepLinkPattern.pathSegments.asSequence())
5656
.forEach { it ->
5757
// retrieve the two path segments to compare
5858
val requestedSegment = it.first
@@ -75,7 +75,7 @@ internal class DeepLinkRequest(
7575
// match queries (if any)
7676
queries.forEach { query ->
7777
val name = query.key
78-
val queryStringParser = deepLink.queryValueParsers[name]
78+
val queryStringParser = deepLinkPattern.queryValueParsers[name]
7979
val queryParsedValue = try {
8080
queryStringParser!!.invoke(query.value)
8181
} catch (e: IllegalArgumentException) {
@@ -85,7 +85,7 @@ internal class DeepLinkRequest(
8585
args[name] = queryParsedValue
8686
}
8787
// provide the serializer of the matching key and map of arg names to parsed arg values
88-
return DeepLinkMatchResult(deepLink.serializer, args)
88+
return DeepLinkMatchResult(deepLinkPattern.serializer, args)
8989
}
9090
}
9191

@@ -98,16 +98,16 @@ internal class DeepLinkRequest(
9898
* supports deeplink. This means that if this deeplink contains any arguments (path or query),
9999
* the argument name must match any of [T] member field name.
100100
*
101-
* One [DeepLink] should be created for each supported deeplink. This means if [T]
101+
* One [DeepLinkPattern] should be created for each supported deeplink. This means if [T]
102102
* supports two deeplink patterns:
103103
* ```
104104
* val deeplink1 = www.nav3recipes.com/home
105105
* val deeplink2 = www.nav3recipes.com/profile/{userId}
106106
* ```
107-
* Then two [DeepLink] should be created
107+
* Then two [DeepLinkPattern] should be created
108108
* ```
109-
* val parsedDeeplink1 = DeepLinkCandidate(T.serializer(), deeplink1)
110-
* val parsedDeeplink2 = DeepLinkCandidate(T.serializer(), deeplink2)
109+
* val parsedDeeplink1 = DeepLinkPattern(T.serializer(), deeplink1)
110+
* val parsedDeeplink2 = DeepLinkPattern(T.serializer(), deeplink2)
111111
* ```
112112
*
113113
* This implementation assumes a few things:
@@ -118,7 +118,7 @@ internal class DeepLinkRequest(
118118
* @param serializer the serializer of [T]
119119
* @param uriPattern the supported deeplink's uri pattern, i.e. "abc.com/home/{pathArg}"
120120
*/
121-
internal class DeepLink<T : NavRecipeKey>(
121+
internal class DeepLinkPattern<T : NavRecipeKey>(
122122
val serializer: KSerializer<T>,
123123
val uriPattern: Uri
124124
) {
@@ -268,7 +268,9 @@ private fun getTypeParser(kind: SerialKind): TypeParser {
268268
PrimitiveKind.FLOAT -> String::toFloat
269269
PrimitiveKind.LONG -> toLong
270270
PrimitiveKind.SHORT -> toShort
271-
else -> Any::toString
271+
else -> throw IllegalArgumentException(
272+
"Unsupported argument type of SerialKind:$kind. The argument type must be a Primitive."
273+
)
272274
}
273275
}
274276

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package com.example.nav3recipes.deeplink.parseintent.singleModule
2+
3+
import android.net.Uri
4+
import android.os.Bundle
5+
import androidx.activity.ComponentActivity
6+
import androidx.activity.compose.setContent
7+
import androidx.core.net.toUri
8+
import androidx.navigation3.runtime.NavBackStack
9+
import androidx.navigation3.runtime.NavKey
10+
import androidx.navigation3.runtime.entryProvider
11+
import androidx.navigation3.runtime.rememberNavBackStack
12+
import androidx.navigation3.ui.NavDisplay
13+
14+
/**
15+
* Parses a target deeplink into a NavKey. There are several crucial steps involved:
16+
*
17+
* STEP 1.Parse supported deeplinks (URLs that can be deeplinked into) into a readily readable
18+
* format (see [DeepLinkPattern])
19+
* STEP 2. Parse the requested deeplink into a readily readable, format (see [DeepLinkRequest])
20+
* **note** the parsed requested deeplink and parsed supported deeplinks should be cohesive with each
21+
* other to facilitate comparison and finding a match
22+
* STEP 3. Compare the requested deeplink target with supported deeplinks in order to find a match
23+
* (see [DeepLinkMatchResult]). The match result's format should enable conversion from result
24+
* to backstack key, regardless of what the conversion method may be.
25+
* STEP 4. Associate the match results with the correct backstack key
26+
*
27+
* This recipes provides an example for each of the above steps by way of kotlinx.serialization.
28+
*
29+
* **This recipe is designed to focus on parsing an intent into a key, and therefore these additional
30+
* deeplink considerations are not included in this scope**
31+
* - Create synthetic backStack
32+
* - Multi-modular setup
33+
* - DI
34+
* - Managing TaskStack
35+
* - Up button ves Back Button
36+
*
37+
*/
38+
class MainActivity : ComponentActivity() {
39+
/** STEP 1. Parse supported deeplinks */
40+
private val deepLinkPatterns: List<DeepLinkPattern<out NavRecipeKey>> = listOf(
41+
// "https://www.nav3recipes.com/home"
42+
DeepLinkPattern(HomeKey.serializer(), (URL_HOME_EXACT).toUri()),
43+
// "https://www.nav3recipes.com/users/with/{filter}"
44+
DeepLinkPattern(UsersKey.serializer(), (URL_USERS_WITH_FILTER).toUri()),
45+
// "https://www.nav3recipes.com/users/search?{firstName}&{age}&{location}"
46+
DeepLinkPattern(SearchKey.serializer(), (URL_SEARCH.toUri())),
47+
)
48+
49+
override fun onCreate(savedInstanceState: Bundle?) {
50+
super.onCreate(savedInstanceState)
51+
52+
// retrieve the target Uri
53+
val uri: Uri? = intent.data
54+
// associate the target with the correct backstack key
55+
val key: NavKey = uri?.let {
56+
/** STEP 2. Parse requested deeplink */
57+
val target = DeepLinkRequest(uri)
58+
/** STEP 3. Compared requested with supported deeplink to find match*/
59+
val match = deepLinkPatterns.firstNotNullOfOrNull { candidate ->
60+
target.match(candidate)
61+
}
62+
/** STEP 4. If match is found, associate match to the correct key*/
63+
match?.let {
64+
//leverage kotlinx.serialization's Decoder to decode
65+
// match result into a backstack key
66+
KeyDecoder(match.args)
67+
.decodeSerializableValue(match.serializer)
68+
}
69+
} ?: HomeKey // fallback if intent.uri is null or match is not found
70+
71+
/**
72+
* Then pass starting key to backstack
73+
*/
74+
setContent {
75+
val backStack: NavBackStack<NavKey> = rememberNavBackStack(key)
76+
NavDisplay(
77+
backStack = backStack,
78+
onBack = { backStack.removeLastOrNull() },
79+
entryProvider = entryProvider {
80+
entry<HomeKey> { key ->
81+
EntryScreen(key.name) {
82+
TextContent("<matches exact url>")
83+
}
84+
}
85+
entry<UsersKey> { key ->
86+
EntryScreen("${key.name} : ${key.filter}") {
87+
TextContent("<matches path argument>")
88+
val list = when {
89+
key.filter.isEmpty() -> LIST_USERS
90+
key.filter == UsersKey.FILTER_OPTION_ALL -> LIST_USERS
91+
else -> LIST_USERS.take(5)
92+
}
93+
FriendsList(list)
94+
}
95+
}
96+
entry<SearchKey> { search ->
97+
EntryScreen(search.name) {
98+
TextContent("<matches query parameters, if any>")
99+
val matchingUsers = LIST_USERS.filter { user ->
100+
(search.firstName == null || user.firstName == search.firstName) &&
101+
(search.location == null || user.location == search.location) &&
102+
(search.ageMin == null || user.age >= search.ageMin) &&
103+
(search.ageMax == null || user.age <= search.ageMax)
104+
}
105+
FriendsList(matchingUsers)
106+
}
107+
}
108+
}
109+
)
110+
}
111+
}
112+
}

app/src/main/java/com/example/nav3recipes/deeplink/parseintent/singleModule/NavRecipeKey.kt

Lines changed: 0 additions & 43 deletions
This file was deleted.

0 commit comments

Comments
 (0)