Skip to content

Commit

Permalink
KSP Generate deepLinkUris property (#27)
Browse files Browse the repository at this point in the history
  • Loading branch information
serbelga authored Apr 8, 2024
1 parent 41a0d3c commit 1c7c78d
Show file tree
Hide file tree
Showing 10 changed files with 168 additions and 74 deletions.
45 changes: 45 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,51 @@ composable(navDestination = SettingsNavDestination) { navBackStackEntry ->
userId = navArgs.userId ?: 0,
```
## Navigate with Deep Links
In the `AndroidManifest.xml`:
```xml
<activity
...>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="sample" android:host="home" />
</intent-filter>
</activity>
```
If we are defining the navigation destinations using the `@NavDestination` annotation, we can use the property `deepLinkUris` as follows:
```kotlin
@NavDestination(
destinationId = "home",
deepLinkUris = [
"sample://home",
]
)
@Composable
fun HomeScreen(navigateToSettings: () -> Unit) {}
```
otherwise, we should set the list of deepLink uris in the `NavDestination` object:
```kotlin
object HomeNavDestination : NavDestination<HomeNavArgumentKeys>() {
override val deepLinkUris: List<String> = listOf(
"sample://home",
"sample://home_secondary"
)
```
Trigger the deep link using adb:
```shell
adb shell am start -a android.intent.action.VIEW -d "sample://home"
```
## License
```
Expand Down
38 changes: 17 additions & 21 deletions docs/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,42 +210,38 @@ In the `AndroidManifest.xml`:
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:host="searchresult" android:scheme="sample" />
<data android:scheme="sample" android:host="home" />
</intent-filter>
</activity>
```
Next, we define the deep link uri in the `NavDestination` `deepLinkUris` property:
If we are defining the navigation destinations using the `@NavDestination` annotation, we can use the property `deepLinkUris` as follows:
```kotlin
object SearchResultNavDestination : NavDestination<SearchResultNavArgumentKeys>() {
// ...
override val deepLinkUris: List<String> =
listOf(
"sample://searchresult",
)
}
@NavDestination(
destinationId = "home",
deepLinkUris = [
"sample://home",
]
)
@Composable
fun HomeScreen(navigateToSettings: () -> Unit) {}
```
Set the deep links in the `NavHost`:
otherwise, we should set the list of deepLink uris in the `NavDestination` object:
```kotlin
NavHost {
composable(
route = SearchResultNavDestination.route,
deepLinks = SearchResultNavDestination.deepLinks,
) {
...
}
}
object HomeNavDestination : NavDestination<HomeNavArgumentKeys>() {
override val deepLinkUris: List<String> = listOf(
"sample://home",
"sample://home_secondary"
)
```
Trigger the deep link using adb:
```shell
adb shell am start -a android.intent.action.VIEW -d "sample://searchresult"
adb shell am start -a android.intent.action.VIEW -d "sample://home"
```
## Create Top Level Destinations
Expand Down
1 change: 0 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ vanniktechMavenPublishPlugin = "0.28.0"
android-gradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "androidGradlePlugin" }
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "activityCompose" }
androidx-compose-bom = { module = "androidx.compose:compose-bom", version.ref = "composeBom" }
androidx-compose-composeBom = { module = "androidx.compose:compose-bom", version.ref = "composeBom" }
androidx-compose-material3 = { module = "androidx.compose.material3:material3" }
androidx-compose-ui = { module = "androidx.compose.ui:ui" }
androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "coreKtx" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,5 @@ annotation class NavDestination(
val destinationId: String,
val name: String = "",
val isTopLevelNavDestination: Boolean = false,
// TODO: Add deepLinkUris to generator
val deepLinkUris: Array<String> = [],
)
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ internal object ClassNames {
* [MemberName]s used for navigation destination generation.
*/
internal object MemberNames {
val ListOf = MemberName(KOTLIN_COLLECTIONS, "listOf")
val MapOf = MemberName(KOTLIN_COLLECTIONS, "mapOf")
val NavArgsGetBoolean = MemberName("", "getBoolean")
val NavArgsGetFloat = MemberName("", "getFloat")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ internal class NavDestinationGenerator(
destinationId = annotation.destinationId,
navArgumentKeysClass = navArgumentKeysClass,
navArgumentParameters = navArgumentParameters,
deepLinksUris = annotation.deepLinkUris,
).generate(),
)
addType(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ internal class NavDestinationObjectGenerator(
private val destinationId: String,
private val navArgumentKeysClass: ClassName,
private val navArgumentParameters: List<NavArgumentParameter>,
private val deepLinksUris: Array<String>,
) {
fun generate(): TypeSpec {
val superClass = if (isTopLevelNavDestination) {
Expand All @@ -61,35 +62,40 @@ internal class NavDestinationObjectGenerator(
.initializer("%S", destinationId)
.build(),
)
.addProperty(
argumentsMapProperty(),
)
.addSafeNavRoute()
.addArgumentsMapProperty()
.addDeepLinksUrisProperty()
.addSafeNavRouteFunction()
.build()
}

private fun argumentsMapProperty(): PropertySpec =
PropertySpec.builder(
ARGUMENTS_MAP_PROPERTY_NAME,
Map::class.asClassName()
.parameterizedBy(
navArgumentKeysClass,
LambdaTypeName.get(
receiver = ClassNames.NavArgumentBuilder,
returnType = Unit::class.asClassName(),
),
),
).addModifiers(KModifier.OVERRIDE)
.initializer(
buildCodeBlock {
addStatement("%M(", MemberNames.MapOf)
indent()
addNavArgumentsToMap()
unindent()
addStatement(")")
},
)
.build()
private fun TypeSpec.Builder.addArgumentsMapProperty() =
apply {
if (navArgumentParameters.isNotEmpty()) {
addProperty(
PropertySpec.builder(
ARGUMENTS_MAP_PROPERTY_NAME,
Map::class.asClassName()
.parameterizedBy(
navArgumentKeysClass,
LambdaTypeName.get(
receiver = ClassNames.NavArgumentBuilder,
returnType = Unit::class.asClassName(),
),
),
).addModifiers(KModifier.OVERRIDE)
.initializer(
buildCodeBlock {
addStatement("%M(", MemberNames.MapOf)
indent()
addNavArgumentsToMap()
unindent()
add(")")
},
)
.build(),
)
}
}

private fun CodeBlock.Builder.addNavArgumentsToMap() {
navArgumentParameters.forEach { navArgumentParameter ->
Expand Down Expand Up @@ -119,12 +125,39 @@ internal class NavDestinationObjectGenerator(
unindent()
}

private fun TypeSpec.Builder.addSafeNavRoute() =
private fun TypeSpec.Builder.addDeepLinksUrisProperty() =
apply {
if (deepLinksUris.isNotEmpty()) {
addProperty(
PropertySpec.builder(
DEEP_LINK_URIS_PROPERTY_NAME,
List::class.asClassName().parameterizedBy(String::class.asClassName()),
).addModifiers(KModifier.OVERRIDE)
.initializer(
buildCodeBlock {
addStatement("%M(", MemberNames.ListOf)
indent()
add(
deepLinksUris.joinToString(
postfix = "\n",
separator = ",\n",
) { "\"$it\"" },
)
unindent()
add(")")
},
)
.build(),
)
}
}

private fun TypeSpec.Builder.addSafeNavRouteFunction() =
apply {
if (navArgumentParameters.isNotEmpty()) {
addFunction(
FunSpec.builder("safeNavRoute")
.addNavArgumentsToNavRouteFunctionParameters()
.addNavArgumentsToSafeNavRouteFunctionParameters()
.returns(
ClassNames.NavRoute.parameterizedBy(
navArgumentKeysClass,
Expand All @@ -134,7 +167,7 @@ internal class NavDestinationObjectGenerator(
buildCodeBlock {
add("return %M(\n", MemberNames.NavRoute)
indent()
addNavArgumentsToNavRouteFunctionBody()
addNavArgumentsToSafeNavRouteFunctionBody()
unindent()
add(")")
},
Expand All @@ -144,7 +177,7 @@ internal class NavDestinationObjectGenerator(
}
}

private fun FunSpec.Builder.addNavArgumentsToNavRouteFunctionParameters() =
private fun FunSpec.Builder.addNavArgumentsToSafeNavRouteFunctionParameters() =
apply {
navArgumentParameters.forEach { navArgumentParameter ->
val type = navArgumentParameter.parameter.type.resolve()
Expand All @@ -164,7 +197,7 @@ internal class NavDestinationObjectGenerator(
}
}

private fun CodeBlock.Builder.addNavArgumentsToNavRouteFunctionBody() =
private fun CodeBlock.Builder.addNavArgumentsToSafeNavRouteFunctionBody() =
navArgumentParameters.forEach { navArgumentParameter ->
addStatement(
"%T.%L to %L,",
Expand All @@ -177,5 +210,6 @@ internal class NavDestinationObjectGenerator(
companion object {
private const val ARGUMENTS_MAP_PROPERTY_NAME = "argumentsMap"
private const val DESTINATION_ID_PROPERTY_NAME = "destinationId"
private const val DEEP_LINK_URIS_PROPERTY_NAME = "deepLinkUris"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,32 +46,34 @@ internal class SafeNavArgsClassGenerator(
)
.build(),
)
.addProperty(
navArgsProperty(),
)
.addNavArgsProperty()
.addNavArgumentParametersGetters()
.build()

private fun navArgsProperty(): PropertySpec =
PropertySpec
.builder(
NAV_ARGS_PROPERTY_NAME,
ClassNames.NavArgs.parameterizedBy(navArgumentKeysClass),
)
.delegate(
buildCodeBlock {
beginControlFlow("lazy")
addStatement(
"%T.%N(%N)",
navDestinationClass,
private fun TypeSpec.Builder.addNavArgsProperty() =
apply {
addProperty(
PropertySpec
.builder(
NAV_ARGS_PROPERTY_NAME,
NAV_BACK_STACK_ENTRY_PARAMETER_NAME,
ClassNames.NavArgs.parameterizedBy(navArgumentKeysClass),
)
.delegate(
buildCodeBlock {
beginControlFlow("lazy")
addStatement(
"%T.%N(%N)",
navDestinationClass,
NAV_ARGS_PROPERTY_NAME,
NAV_BACK_STACK_ENTRY_PARAMETER_NAME,
)
endControlFlow()
},
)
endControlFlow()
},
.addModifiers(KModifier.PRIVATE)
.build(),
)
.addModifiers(KModifier.PRIVATE)
.build()
}

private fun TypeSpec.Builder.addNavArgumentParametersGetters() =
apply {
Expand Down
13 changes: 13 additions & 0 deletions sample-app-annotations/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,19 @@
<category android:name="android.intent.category.LAUNCHER" />

</intent-filter>

<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="sample" android:host="home" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="sample" android:host="home_secondary" />
</intent-filter>
</activity>
</application>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ import dev.sergiobelda.navigation.compose.extended.compiler.annotation.NavDestin
@NavDestination(
name = "Home",
destinationId = "home",
deepLinkUris = [
"sample://home",
"sample://home_secondary",
],
)
@Composable
fun HomeScreen(navigateToSettings: () -> Unit) {
Expand Down

0 comments on commit 1c7c78d

Please sign in to comment.