How to trigger navigation in Jetpack Compose outside of composables

Using the Jetpack compose navigation library is a valid choice when moving from screen to screen. It allows to register several navigation destinations and to reference them by a path. The key to triggering such a navigation is the NavHostController. It offers several methods like navController.navigate("screenName"). This is fine but the navController is a composable and therefore has to be triggered in the context of another composable. But what if you want to trigger navigation from a ViewModel? Possibly after a long-running coroutine? In this blog post, we will discuss a possible solution for this scenario.

The key idea is to separate the navigation command into a stateful entity which can then be observed by the navController. For that, we create a Navigator class, which exposes the current navigation location and offers a navigate("route") method. First, we define our NavigationDestination. In our case, we have a login screen and the main screen.

Note that in a real app, the NavigationDestination would also contain arguments to be passed to the destination page. Next comes the Navigator, which exposes the current destination with the default screen of LoginScreen.

The Navigator should be provided as a service via a suitable dependency injection framework since we want to consume it across our app.

Now we are able to apply the configured navigation state. In our main composable, we obtain the NavHostController and listen for changes to our Navigator.

The most important part is the consumption of the navigator.destination as observable state. Whenever the app changes the navigation destination, we want to react to the change and invoke the navController.navigate(..). To not do so during recomposition, we wrap the navigation in a LaunchedEffect, which ensures the navigation is triggered only once. Additionally, we check the current route of the navController to not perform duplicate navigations on launch. Finally, we set the current (default navigation destination) as the startDestination of the NavHost composable.

Now we are able to perform decoupled navigation in our code by invoking the Navigator like so.

If you found this approach helpful, please let us know in the comments below. How do you handle navigation in jetpack compose? Do you have another mechanism? Again, let us know in the comments below.

0 replies

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply

Your email address will not be published. Required fields are marked *