Reputation: 1268
When a user enters a geo-fence in our app, we show them an offer notification about the area, which when clicked, should direct them to a specific composable screen called SingleNotification
. I've followed google's codelab and their documentation but I haven't managed to make the navigation to the specific screen work yet. Right now, clicking on the notification or running the adb shell am start -d “eway://station_offers/date_str/www.test.com/TITLE/CONTENT” -a android.intent.action.VIEW
command, simply opens the app.
The activity is declared as follows in the manifest:
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
</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:host="station_offers"
android:scheme="eway" />
</intent-filter>
</activity>
Our MainNavController class contains the NavHost which in turn contains various NavGraphs. I've only included the relevant graph below:
NavHost(
navController = navController,
startDestination = NavigationGraphs.SPLASH_SCREEN.route
) {
....
notificationsNavigation()
....
}
The notificationsNavigation graph is defined as follows:
fun NavGraphBuilder.notificationsNavigation() {
navigation(
startDestination = Screens.NOTIFICATION_DETAILS.navRoute,
route = NavigationGraphs.NOTIFICATIONS.route
) {
composable(
route = "${Screens.NOTIFICATION_DETAILS.navRoute}/{date}/{imageUrl}/{title}/{content}",
arguments = listOf(
navArgument("date") { type = NavType.StringType },
navArgument("imageUrl") { type = NavType.StringType },
navArgument("title") { type = NavType.StringType },
navArgument("content") { type = NavType.StringType }
),
deepLinks = listOf(navDeepLink {
uriPattern = "eway://${Screens.NOTIFICATION_DETAILS.navRoute}/{date}/{imageUrl}/{title}/{content}"
})
) { backstackEntry ->
val args = backstackEntry.arguments
SingleNotification(
date = args?.getString("date")!!,
imageUrl = args.getString("imageUrl")!!,
title = args.getString("title")!!,
description = args.getString("content")!!
)
}
}
}
The Screes.NOTIFICATION_DETAILS.navRoute
corresponds to the value of notification_details
.
Inside the geo-fence broadcast receiver, I construct the pending Intent as follows:
val deepLinkIntent = Intent(
Intent.ACTION_VIEW,
"eway://station_offers/${
offer.date
}/${
offer.image
}/${offer.title}/${offer.content}".toUri(),
context,
MainActivity::class.java
)
val deepLinkPendingIntent: PendingIntent =
TaskStackBuilder.create(context!!).run {
addNextIntentWithParentStack(deepLinkIntent)
getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT)!!
}
showNotification(offer.title, offer.content, deepLinkPendingIntent)
I can't figure out what I'm missing here.
Upvotes: 11
Views: 10094
Reputation: 1268
UPDATE: Please see @curioustechizen's answer below for the actual solution instead of this workaround!
Alright, after a lot of testing and running the solution of Google's relative code lab a bunch of times line by line, I figured out how to make it work.
First and foremost, it looks like the host
that we define in the AndroidManifest.xml for the <data>
tag of the intent filter needs to much the composable destination's route. So in my case, it is defined as:
<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:host="notification_details"
android:scheme="eway" />
</intent-filter>
Second of all, the deep link's uri pattern should match the composable's route format. In this case, since the composable's route is defined as route = "${Screens.NOTIFICATION_DETAILS.navRoute}/{date}/{imageUrl}/{title}/{content}"
, the correct deep Link uriPattern
, would be :
deepLinks = listOf(navDeepLink {
uriPattern =
"eway://${Screens.NOTIFICATION_DETAILS.navRoute}/{date}/{imageUrl}/{title}/{content}"
})
Furthermore, the composable destination seems to MUST be declared inside the NavHost
itself and not inside a NavGraph. Initially as you can see, I thought that the system would be able to find the destination via the nested NavGraph, but it couldn't (threw a relative exception), so I came to the conclusion that it must be done this way (as is done in the code labs). Please correct me if I'm wrong!
Lastly, I changed the val uri
definition inside my GeofenceBroadcastReceiver accordingly. Now it looks like so:
val uri = "eway://${Screens.NOTIFICATION_DETAILS.navRoute}/${
offer.date.replace(
"/",
"@"
)
}/${
offer.image.replace(
"/",
"@"
)
}/${offer.title}/${offer.content.replace("/", "@")}".toUri()
So to recap, these are the steps that seem to solve this issue as far as my understanding goes:
android:host
should match the destination composable's route, and lastly,scheme://host/....
you should be fine if you followed number 2)Upvotes: 6
Reputation: 10672
It turns out that the limitations described in this answer are not entirely true. Specifically,
Point 2 above was the key to unlock my understanding of how deeplinks work. They are just arbitrary URIs and have no relationship to the destination's route at all. The rule is that the following 3 items must match up
navDeepLink
DSLPendingIntent
for the notificationscheme
and host
declared in the intent-filter
in the manifest.Here are some code snippets. In my case the URIs were static, so you will need to make adjustments in order to address the OP's situation. This example has the following structure
LandingScreen
("landing_screen_route"
)SecondScreen
("second_screen_route"
)"nested_graph_route"
) with a NestedScreen
("nested_destination_route"
)We are going to see how to reach both SecondScreen
and NestedScreen
from a notification.
First, defining the NavGraph using the DSL. Pay special attention to the navDeepLink
entries here.
@Composable
fun AppGraph(onNotifyClick: () -> Unit) {
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "landing_screen_route"
) {
composable("landing_screen_route") {
LandingScreen {
navController.navigate("second_screen_route")
}
}
composable(
route = "second_screen_route",
deepLinks = listOf(
navDeepLink { uriPattern = "myapp://arbitrary_top_level" } // Note that this pattern has no relation to the route itself
)
) {
SecondScreen {
navController.navigate("nested_graph_route")
}
}
navigation(
startDestination = "nested_destination_route",
route = "nested_graph_route"
) {
composable(
route = "nested_destination_route",
deepLinks = listOf(
navDeepLink { uriPattern = "myapp://arbitrary_nested" } // Note that this pattern has no relation to the route itself
)
) {
NestedScreen(onNotifyClick)
}
}
}
}
Next, here's how you would construct the PendingIntent for both these cases:
val notNestedIntent = TaskStackBuilder.create(this).run {
addNextIntentWithParentStack(
Intent(
Intent.ACTION_VIEW,
"myapp://arbitrary_top_level".toUri() // <-- Notice this
)
)
getPendingIntent(1234, PendingIntent.FLAG_UPDATE_CURRENT)
}
val nestedIntent = TaskStackBuilder.create(this).run {
addNextIntentWithParentStack(
Intent(
Intent.ACTION_VIEW,
"myapp://arbitrary_nested".toUri() // <-- Notice this
)
)
getPendingIntent(2345, PendingIntent.FLAG_UPDATE_CURRENT)
}
Finally, here are the intent-filter
entries in the manifest
<activity
android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<!--
The scheme and host must match both of the below:
1. The navDeepLink declaration
2. The URI defined in the PendingIntent
-->
<data
android:scheme="myapp"
android:host="arbitrary_top_level"
/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<!--
The scheme and host must match both of the below:
1. The navDeepLink declaration
2. The URI defined in the PendingIntent
-->
<data
android:scheme="myapp"
android:host="arbitrary_nested"
/>
</intent-filter>
</activity>
Upvotes: 18