Utilizing the Navigation Element in Jetpack Compose

Jetpack Compose is a declarative framework for constructing native Android UI really useful by Google. To simplify and speed up UI improvement, the framework turns the standard mannequin of Android UI improvement on its head. Relatively than developing UI by imperatively controlling views outlined in XML, UI is in-built Jetpack Compose by composing capabilities that outline how app information is remodeled into UI.
An app constructed completely in Compose could include a single Exercise that hosts a composition, which means that the fragment-based Navigation Architectural Elements can not be used immediately in such an software. Happily, navigation-compose
supplies a compatibility layer for interacting with the Navigation Component from Compose.
The androidx.navigation:navigation-compose
dependency supplies an API for Compose apps to work together with the Navigation Element, profiting from its acquainted options, together with dealing with up and again navigation and deep hyperlinks.
The Navigation Element consists of three elements:NavController
,NavHost
, and the navigation graph.
The NavController
is the category by way of which the Navigation Element is accessed. It’s used to navigate between locations and maintains every vacation spot’s state and the again stack’s state. An occasion of the NavController
is obtained by way of the rememberNavController()
technique as proven:
val navController = rememberNavController()
The NavHost
, because the identify signifies, serves as a bunch or container for the present navigation vacation spot. The NavHost
additionally hyperlinks the NavController
with the navigation graph (described under). Making a NavHost
requires an occasion of NavController
, obtained by way of rememberNavController()
as described above, and a String
representing the route related to the start line of navigation.
NavHost(navController = navController, startDestination = "house") { ... }
Within the fragment-based manifestation of the Navigation Element, the navigation graph consists of an XML useful resource that describes all locations and doable navigation paths all through the app. In Compose, the navigation graph is constructed utilizing the lambda syntax from the Navigation Kotlin DSL as a substitute of XML. The navigation graph is constructed within the trailing lambda handed to NavHost
as proven under:
NavHost(navController = navController, startDestination = "house") { composable("house") { MealsListScreen() } composable("particulars") { MealDetailsScreen() } }
On this instance, the MealsListScreen()
composable is related to the route outlined by the String
“house,” and the MealDetailsScreen()
composable is related to the “particulars” route. The startDestination
is about to “house,” which means that the MealsListScreen()
composable might be displayed when the app launches.
Observe that within the instance above, the lambda is handed to the builder
parameter of the NavHost
perform, which has a receiver kind of NavGraphBuilder
. This enables for the concise syntax for offering composable locations to the navigation graph by way of NavGraphBuilder.composable()
.
The NavGraphBuilder.composable()
technique has a required route
parameter that could be a String
representing every distinctive vacation spot on the navigation graph. The composable related to the vacation spot route is handed to the content material
parameter utilizing trailing lambda syntax.
The navigate
technique of NavController
is used to navigate to a vacation spot:
navController.navigate("particulars")
Whereas it could be tempting to cross the NavController
occasion right down to composables that may set off navigation, it’s best follow not to take action. Centralizing your app’s navigation code in a single place makes it simpler to grasp and preserve. Moreover, particular person composables could seem or behave in another way on completely different display screen sizes. For instance, a button could end in navigation to a brand new display screen on a cellphone however not on tablets. Due to this fact it’s best follow to cross capabilities right down to composables for navigation-related occasions that may be dealt with within the composable that hosts the NavController
.
For instance, think about MealsListScreen
takes an onItemClick: () -> Unit
parameter. You could possibly then deal with that occasion within the composable that incorporates NavHost
as follows:
NavHost(navController = navController, startDestination = "house") { composable("house") { MealsListScreen(onItemClick = { navController.navigate("particulars") }) } ... }
Arguments could be handed to a navigation vacation spot by together with argument placeholders inside the route. In case you wished to increase the instance above and cross a string representing an id for the small print display screen, you’ll first add a placeholder to the route:
NavHost(navController = navController, startDestination = "house") { ... composable("particulars/{mealId}") { MealDetailsScreen(...) } }
You then would add an argument to composable
, specifying its identify and kind:
composable( "particulars/{mealId}", arguments = listOf(navArgument("mealId") { kind = NavType.StringType }) ) { backStackEntry -> MealDetailsScreen(...) }
Then, you would want to replace calls that navigate to the vacation spot by passing the id as a part of the route:
navController.navigate("particulars/1234")
Lastly, you’ll retrieve the argument from the NavBackStackEntry
that’s accessible inside the content material
parameter of composable()
:
composable( "particulars/{mealId}", arguments = listOf(navArgument("mealId") { kind = NavType.StringType }) ) { backStackEntry -> MealDetailsScreen(mealId = backStackEntry.arguments?.getString("mealId")) }
One of many key advantages of utilizing the Navigation Element is the automated dealing with of deep hyperlinks. As a result of routes are outlined as strings that mimic URIs by conference, they are often constructed to correspond to the identical patterns used for deep hyperlinks into your app. Carrying ahead with the instance above and assuming that it’s related to a fictitious net property at https://bignerdranch.com/cookbook
you’ll first add the next intent filter to AndroidManifest.xml
to allow the app to obtain the suitable deep hyperlinks:
<intent-filter> <motion android:identify="android.intent.motion.VIEW" /> <class android:identify="android.intent.class.DEFAULT" /> <class android:identify="android.intent.class.BROWSABLE" /> <information android:host="bignerdranch.com" android:pathPrefix="/cookbook" android:scheme="https" /> </intent-filter>
You then would replace your composable vacation spot to deal with deep hyperlinks of the sample https://bignerdranch.com/cookbook/{mealId}
by passing a worth to the deepLinks
parameter as proven:
composable( "particulars/{mealId}", arguments = listOf(navArgument("mealId") { kind = NavType.StringType }), deepLinks = listOf(navDeepLink { uriPattern = "https://bignerdranch.com/cookbook/{mealId}" }) ) { backStackEntry -> MealDetailsScreen(mealId = backStackEntry.arguments?.getString("mealId")) }
These deep hyperlinks could possibly be examined utilizing an ADB command akin to:
adb shell am begin -d https://bignerdranch.com/cookbook/1234
Within the above demonstrations, string literals had been used to outline routes and navigation argument names for readability and ease. It’s best follow to retailer these strings as constants or in another assemble to scale back repetition and stop typo-based bugs. A cleaner implementation of the above instance may seem like this:
interface Vacation spot { val route: String val title: Int } object Residence : Vacation spot { override val route: String = "house" override val title: Int = R.string.app_name } object Particulars: Vacation spot { override val route: String = "particulars" override val title: Int = R.string.meal_details const val mealIdArg = "mealId" val routeWithArg: String = "$route/{$mealIdArg}" val arguments = listOf(navArgument(mealIdArg) { kind = NavType.StringType }) enjoyable getNavigationRouteToMeal(mealId: String) = "$route/$mealId" } ... NavHost( navController = navController, startDestination = Residence.route ) { composable(Residence.route) { MealsListScreen(onItemClick = { navController.navigate(Particulars.getNavigationRouteToMeal(it)) }) } composable( Particulars.routeWithArg, arguments = Particulars.arguments ) { backStackEntry -> MealDetailsScreen( mealId = backStackEntry.arguments?.getString(Particulars.mealIdArg) ?: "" ) } }
Lack of argument kind security
The first disadvantage is the dearth of kind security for passing arguments. Whereas this may increasingly not seem to be a giant deal in case you are following the best practice of not passing advanced information in navigation arguments, it could nonetheless be preferable to have compile-time assurance, even for easy varieties.
Repetitive and cumbersome API for passing arguments
Along with the dearth of kind security, the API for outlining argument varieties and parsing them from the BackStackEntry
is pretty repetitive and cumbersome. It includes a good quantity of probably tough string concatenation to construct routes.
Many builders have grown to get pleasure from utilizing the Navigation Editor to get a visible illustration of the navigation graph for his or her apps and to shortly and simply outline navigation actions. There isn’t any comparable instrument for Compose.
Use Fragments to host Compose
Maybe probably the most simple various, particularly when you’re already accustomed to the fragment-based Navigation element, could be to make use of Fragments to host every screen-level composable. This may carry the good thing about type-safe navigation arguments and entry to the Navigation Editor.
Third-party options
Because of the drawbacks above, a number of third-party instruments, akin to Compose Destinations and Voyager have been developed. For an in depth overview and comparability of those options, we advocate this article.