Jetpack Compose Destinations
In this tutorial, you’ll learn how to implement an effective navigation pattern with Jetpack Compose, in a way that will work with different screen sizes, from phones to tablets. By Roberto Orgiu.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Jetpack Compose Destinations
20 mins
Compose is in its early stages and navigation is one of the most complex topics.
With Compose Destinations you have a central API for navigating between screens and taking care of the back stack. In this tutorial, you’ll use Compose Destinations to implement navigation in an app called Landscapes. Say hello to Jetpack Compose Destinations!
Here’s a summary of the various lessons you’ll learn from this tutorial:
- How to implement routes and destinations
- How to implement navigation using the Bottom Navigation UI pattern
- Animate when navigating between different screens
- Handle layouts based on screen dimensions
Getting Started
Download the starter project by clicking the Download Materials button at the top or bottom of the tutorial.
You’re going to build an app that will show pictures of several cities all over the world in a classic list/detail pattern. You’ll also implement the navigation for a Settings screen, just to make things a bit more challenging.
On a phone, you’ll put navigation at the bottom to let the user switch between the list of the cities and the settings. Tapping any of the navigation items will bring the user to the related screen, and tapping an item in the list of cities will hide the bottom navigation bar and show some pictures related to each city. Tapping the back button will bring the user back to the list of cities.
On a tablet, though, the navigation will be different. You’ll implement the list/detail pattern by putting the two screens side by side, with a lateral navigation rail to switch from this content to the settings.
Import the project into Android Studio. Once it finishes downloading all the dependencies, build and run. You’ll see… nothing! An empty screen will welcome you, and it’s time to start.
Setting Up the Project
Open build.gradle of your app module, and you’ll find this dependency:
implementation("androidx.navigation:navigation-compose:2.4.2")
This is the dependency you’ll need to implement the navigation. In this project, you won’t need to declare it and you won’t need to poke around the screens. You can if you want to, but in this tutorial you’ll focus on the navigation.
Now that you have all necessary dependencies, you can start coding!
Adding the First Destination
Open CompatUi.kt. In CompatUi()
, look for items
. Notice that the value is a list of screens available for your users. You’ll start by showing the list of the cities.
Add this code inside the Scaffold()
at // TODO: Define NavHost here
:
NavHost(
navController = navController,
startDestination = Screen.List.path,
modifier = Modifier.padding(innerPadding)
) {
composable(Screen.List.path) {
CityListUi(viewModel = viewModel,
onCitySelected = { /* Replace here */ })
}
}
With this code, you create a NavHost
— the shell that contains all your destinations. You instruct it to show a specific startDestination
, and you define how the destination should display by adding the right Composable
to it.
Now, open MainUi.kt, and add this line of code right above the theme
declaration:
val navController = rememberNavController()
You’re asking Jetpack Compose Navigation Library to give you the NavController
— you’ll need it to create the NavHost
.
Now, add this line at the end of // TODO: Add CompatUi() here
:
CompatUi(navController = navController, viewModel = viewModel, themeStore = themeStore)
This line simply loads the UI you just created when the app starts. You’re using navController
to present a screen — viewModel
contains logic for the screen parts and themeStore
defines an appropriate theme.
Build and run. Notice the app shows the list of cities. Scroll down to see all cities you used.
You’re done with the first step! You just added your first destination. Next, you’ll add a couple more destinations, plus a way to reach them.
Navigating to the Detail screen
In this section, you’ll add the city detail and the settings screen. Don’t worry — the screens are ready for you to use in your code. All you have to do is link them so that when a user taps a city, it opens a screen that shows more pictures of that city.
In CompatUi.kt, find the /* Replace here */
comment and replace it with the click behavior:
navController.navigate("detail/${it.name}")
This line instructs the NavController
where to go when the user taps a city. In this case, the app will navigate to a screen with details of a tapped city.
Now, you need to add more destinations! Below the lambda of composable(Screen.List.path)
, add the following code:
// 1
composable(route = "detail/{city}") { backstackEntry ->
val cityName = backstackEntry.arguments?.getString("city") ?: error("City is required")
val city = viewModel.cities.first { it.name == cityName }
CityDetailUi(viewModel = viewModel, city = city, isBigLayout = false)
}
// 2
composable(route = Screen.Settings.path) {
SettingsUi(themeStore = themeStore)
}
There are two different parts:
- You define a destination that users reach by tapping a city using
composable()
. Here, you get thebackstackEntry
, from which you can extract all destination arguments (in this case, the name of the city). Take a look at the route definition, and you’ll noticecity
appearing inside curly braces. This is how Jetpack Compose Navigation indicates the variable data (arguments) you can pass to the different routes. Once you have the argument, you can pass it toCityDetailUi()
. - You specify the settings screen by passing
themeStore
. You can’t reach that screen yet, but soon you’ll implement all you need to be able to see it.
Build and run the app. When it starts, tap a city to see its details:
Implementing Bottom Navigation
As you’re fully aware, you can’t reach the settings screen just yet. But this changes right now!
The next step is to add a BottomNavigation
that interacts with your shiny NavHost
and lets you reach the settings screen as well.
In order to do it, continue working in CompatUi.kt, but this time inside Scaffold()
.
At // TODO: Define bottomBar here
, paste the following code:
bottomBar = {
BottomNavigation(backgroundColor = MaterialTheme.colors.primary) {
// 1
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentDestination = navBackStackEntry?.destination
// 2
items.forEach { screen ->
BottomNavigationItem(
// 3
icon = {
Icon(
imageVector = screen.icon,
contentDescription = screen.name
)
},
label = { Text(text = screen.name) },
// 4
selected = currentDestination?.hierarchy?.any { it.route == screen.path } == true,
// 5
onClick = {
navController.navigate(screen.path) {
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
launchSingleTop = true
restoreState = true
}
})
}
}
}
Here’s what the code above does:
-
Since the
BottomNavigation
has more than one destination stored inside a stack, you need to know where you are so that you can react accordingly. With this line, you’re getting the exact place where you are in the stack. -
You cycle through the different destinations available for the
BottomNavigation
, and you create aBottomNavigationItem
for each of them. -
You assign an icon and label for each
BottomNavigationItem
. - This one is particularly interesting: While looping through all the destinations, you check whether the current destination is presented in the app. When you run into the presented one, you mark it as selected.
-
Whenever the user taps on the item, you instruct the
NavController
to reach a specific destination in the stack.
Build and run. You can now reach the settings screen by simply tapping on the Settings tab in the bottom navigation bar!