Deep Links navigation in Jetpack Compose
Asked Answered
C

5

10

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

Cousteau answered 30/10, 2021 at 20:25 Comment(2)
Check the log messages. There is probably one that indicates an unrecognized path.Zygote
@Zygote there is no log message that seems to have to do anything with deep links or navigationCousteau
M
15

The problem is with the Activity launchMode:

The documentation says that:

It is strongly recommended to always use the default launchMode of standard 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 alternate launchMode such as singleTop. In this case, it is necessary to manually call handleDeepLink() in onNewIntent(), as shown in the following example:

override fun onNewIntent(intent: Intent?) {
    super.onNewIntent(intent)
    navController.handleDeepLink(intent)
}
Monsour answered 8/12, 2021 at 13:59 Comment(2)
How do you get instance navController onNewIntent when you use compose and navController is inside of setContent ?Vellavelleity
@Vellavelleity declare navController as a lateinit var in the activity's scope, set it in the setContent. It would be accessible throughout the activity; why did 8 people upvote this question of a comment?Australian
D
3

you also can do this

setContent {
        DisposableEffect(Unit) {
            val listener = Consumer<Intent> {
                //do som
            }
            addOnNewIntentListener(listener)
            onDispose { removeOnNewIntentListener(listener) }
        }
    }
Disremember answered 20/10, 2022 at 14:38 Comment(2)
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.Callum
Very helpful, as imo this is cleaner than having to keep a reference of the navController Thanks!Kaliope
C
1

I got it working by removing

android:launchMode="singleInstance"

from the manifest

Cousteau answered 1/11, 2021 at 11:48 Comment(0)
H
1

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()
    }
}
Hyperplasia answered 22/8, 2023 at 15:3 Comment(0)
R
0

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)
  }
}
Revamp answered 13/8, 2022 at 1:58 Comment(3)
Not a great idea for testing purposes.Australian
Richard, any better solusions are welcome here :)Revamp
Already posted under Kamil's comment.Australian

© 2022 - 2024 — McMap. All rights reserved.