Reputation: 352
I want to use deep links with Jetpack Compose's Nav Host and followed this page on Compose Navigation: https://developer.android.com/jetpack/compose/navigation#deeplinks
My implementation: AndroidManifest.xml:
<application ...>
<activity
...
android:allowTaskReparenting="true"
android:launchMode="singleInstance">
...
<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="https" android:host="xkcd.com" />
<data android:scheme="https" android:host="www.xkcd.com" />
</intent-filter>
</activity>
</application>
MainActivity.onCreate().setContent{}
val rootUri = "https://www.xkcd.com"
NavHost(navController = navController, startDestination = "mainView") {
composable("mainView", deepLinks = listOf(navDeepLink { uriPattern = rootUri })) {
MainContent()
}
composable(
route = "singleView/{number}",
arguments = listOf(navArgument("number") { type = NavType.IntType }),
deepLinks = listOf(navDeepLink { uriPattern = "$rootUri/{number}" })
) { backStackEntry ->
val number = backStackEntry.arguments?.getInt("number")
SingleView(number)
}
}
If I now click on a corresponding link the app opens but the navigation doesn't work
Upvotes: 11
Views: 12493
Reputation: 204
The problem is with the Activity launchMode:
The documentation says that:
It is strongly recommended to always use the default
launchMode
ofstandard
when using Navigation. When using standard launch mode, Navigation automatically handles deep links by calling handleDeepLink() to process any explicit or implicit deep links within the Intent. However, this does not happen automatically if the Activity is re-used when using an alternatelaunchMode
such assingleTop
. In this case, it is necessary to manually callhandleDeepLink()
inonNewIntent()
, as shown in the following example:override fun onNewIntent(intent: Intent?) { super.onNewIntent(intent) navController.handleDeepLink(intent) }
Upvotes: 18
Reputation: 5635
I have a bit different approach. I need to handle incoming intents in activity's onCreate
:
fun onCreate() {
setContent {
// ... nav controller initialization
}
// ...
if (savedInstanceState == null) {
processCallerIntent(intent)
}
}
and in onNewIntent
method:
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
processCallerIntent(intent)
}
And in my case navController
initialization happens after processCallerIntent
gets called with new intent.
That's why I implemented this via viewModel.liveData
solution.
In the processCallerIntent
method I save navigation route to be processed later:
private fun processCallerIntent(intent: Intent?) {
if (intent == null) {
return
}
if (intent.action == Intent.ACTION_VIEW && /*...*/ ) {
// Do all required checks and get navigation route you need
viewModel.postPendingNavigationRoute(/*...*/)
}
}
In viewModel
I have something like this:
private val _pendingNavigationRoute = MutableLiveData<String>()
val pendingNavigationRoute: LiveData<String> = _pendingNavigationRoute
fun postPendingNavigationRoute(route: String) {
_pendingNavigationRoute.value = route
}
fun clearPendingNavigationRoute() {
_pendingNavigationRoute.value = ""
}
And in the root composabe I have something like this:
val pendingNavigationRoute by viewModel.pendingNavigationRoute.observeAsState()
LaunchedEffect(pendingNavigationRoute) {
val route = pendingNavigationRoute
if (!route.isNullOrEmpty()) {
navController.navigate(route)
viewModel.clearPendingNavigationRoute()
}
}
Upvotes: 1
Reputation: 41
you also can do this
setContent {
DisposableEffect(Unit) {
val listener = Consumer<Intent> {
//do som
}
addOnNewIntentListener(listener)
onDispose { removeOnNewIntentListener(listener) }
}
}
Upvotes: 3
Reputation: 142
To Kamil, you could save navController to viewModel, then reuse it inside onNewIntent method.
class HomeActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val model: MainModel by viewModels()
setContent {
val navController = rememberNavController()
model.navController = navController
MyTheme {
Scaffold {
NavigationComponent(navController)
}
}
}
}
override fun onNewIntent(intent: Intent?) {
super.onNewIntent(intent)
val model: MainModel by viewModels()
model.navController.handleDeepLink(intent)
}
}
Upvotes: -1
Reputation: 352
I got it working by removing
android:launchMode="singleInstance"
from the manifest
Upvotes: 1