Issue
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 href="https://developer.android.com/codelabs/jetpack-compose-navigation#4" rel="nofollow noreferrer">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.
Solution
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:
- The deep link's destination composable must be a direct child of the main NavHost
- The AndroidManifest's
android:host
should match the destination composable's route, and lastly, - The deep link's Uri pattern should match the destination composable's route (if you use the format
scheme://host/....
you should be fine if you followed number 2)
Answered By - Stelios Papamichail
Answer Checked By - Candace Johnson (JavaFixing Volunteer)