top of page

Ultimate Guide to Navigation and Deep Links in Jetpack Compose

Updated: Oct 23, 2022

Hey everyone. Welcome back to my channel. Jetpack Components introduced a new way of navigation in xml. The same follows in Jetpack Compose as well. So if you're wondering that, how do we do navigation in Jetpack, compose, stay until the end of this blog where we will discover exactly that.

As usual, I have an empty project, so the very first thing I'd like to do is actually create different screens so that I can navigate between them. They will be very simple screens, so let's quickly do that first.

 Here. If you notice, I have created three fairly simple screens, Nothing special, a home screen, a favorite screen, and a setting screen. All of them just contains a column with a text at the. Now comes the main topic of this blog navigation. How do we navigate between these screens? The very first thing you'd want to do is to actually go ahead and add the project dependency for navigation. So let's quickly do that.

Here you see that we have added navigation dependency and synced up the project, So let's first start with the bottom navigation because that is the most common use case of navigation. The very first thing we need to do for the bottom bar is to actually create the bottom items, which will contain the title for the item in the bottom bar. It's icon, and also the screen route name, which will denote the unique route to be followed by navigation. So let's quickly do that as well.

 Here I have added a bottom nav item sealed class that is taking in the title I can and screen route name, which I have already mentioned that screen route name would be the unique name that the navigation library will use to uniquely identify each different route. So since it is a sealed class, we can create all the screens items here itself. So let's quickly do that as well.

 Now here I have added three objects, which are all of the bottom nav item type. That means the home bottom nav item is having the title of home and Icon of home, and also the screen route name of home. Same goes for the other items as well. Now remember that you can always use different names for different routes. I personally prefer to use the same name for the screen route. It makes development much more. Now let's first create the bottom bar.

Starting with the bottom bar composable. We have a list of items that we would like to show in the bottom bar that is home favorites and the settings.

 After that, I have added a bottom navigation composable, which is provided by the navigation library itself. If we have a look at it, we'll see that it is just a simple row wrapped around a surface.

So here you can see that it is just a simple rope. And that is wrapped around the surface. Now, a surface is nothing but a simple composable that, as the name suggests, provides a surface for the user interaction. Wrapping a composable with a surface means that now the user can perform, click drag, or any kind of other actions on the composable, in this case, the row.

 Here we add a bottom navigation item for each item in list. That was the home settings and the favorites. So for each item we add a bottom navigation item. Bottom navigation item is also provided by the navigation library. Right. So let's place this bottom bar on the screen and let's see how this looks.

 Here I have added a bottom bar, but noticed that I have used a scaffold for this. Well, scaffold is a material component that allows us to place various different components in a common pattern, such as a bottom bar, top bar, or fab buttons. We'll see the use of scaffold much more as we progress through this series.

Anyways, the bottom bar is looking as it should. Perfect. So now that we have created the bottom bar, we also need to hook it up with the functionality to navigate between different screens. If you remember, we used a nav host for that in XML as well. That nav host was the component that held the screens and when hooked up with the bottom bar, it could change screens based on the item.

So similar to xml, we are going to do the same in compose as well. Let's create a nav host for our screens to be hosted.

Here you can see that I have added a new navigation component that is taking a nav controller as a parameter inside it is creating a nav host, which takes that nav controller and a starting destination. So a nav controller is a component that takes care of the navigation for us. When we specify that we need to navigate to a different screen, it is that nav controller, which takes care of navigating to the different screen, maintaining back-stack, and much more. Now, let's add the different screens into the nav host

So you can see that we have added all the screens inside the navhost. This will create a navigation graph with all the screens. So we create a composable with the screen route, and when the screened out navigation happens, we show the respective. For example, when we navigate the home screen route, NAV controller knows that it needs to show the home screen. With this, we have created our nav controller. Now let's place it onto the screen.

Here we place a navigation component in the main screen, and for the nav controller, we are passing the remember nav controller object.

Remember, nav controller is a saveable object so that it can save the navigation back-stack across the config changes as well. In the ultimate guide to states lifecycle and view models video, we learn that we can use, remember saveable when we want to persist the state across config changes.

By the way, if you'd want to learn more about that, You can check the video here, and the blog here. Check them out as well.

So if I go ahead and open the Remember navcontroller, you'll see that we save the navigator so that the current navigation is saved across config changes. If we do not do that and change the config of the view, we'll see that the nav controller will be initialized again and we'll be seeing the start screen once again. We obviously do not want that. We want the user to stay at the same screen when the config changes. That's why we use, Remember navcontroller.

 So now if we run the app, we'll see that we are at the home screen. That is because it is the starting destination in the nav host, but nothing happens when we click on the different bottom bar items. To do that, we need to navigate to the correct screen on bottom bar click, so let's do that as well.

So here we add the single instance of the navcontroller so that we can hook the bottom bar with the navigation. We passed this instance to both bottom bar and the nav host. Moving on to the bottom bar, you see that I have added the nav hosts controller as a parameter in the bottom bar composable as well.

Now we'll be using this nav controller to handle all the bottom bar item clicks. Now let's get the backsack and also the current route of the nav controller.

So here I have added the backs stack entry. It is no secret that the jet pack navigation maintains a backs stack. That means it has a stack and all the new navigation gets added on top of that stack.

So, for example, I have a screen A, then I open screen B from A, so B will be added on top of a. This is called the back stack, which is based on the launch mode. Anyway, so the nav controller dot current entry as state returns the same BACKSACK entry as a state.

 So in here, according to the docs itself, when the given nav controller changes the back stack due to a nav controller dot navigate or nav controller dot pop backs stack, this will trigger a recompose and return the top entry on the backs stack. So that means we get the backs stack, which is currently at the top, or in most simple words, we get the screen, which is at the top.

Now we take the route name from this backs stack entry to get the current route. So here we are simply getting the route name from the current backstack.

Moving ahead to denote if the bottom nav item is selected or not. The add current route is equals to item screen do route. So if the unique name for that item is equals to the current route, then obviously it would be the selected route. Here in the bottom nav item, I have added a condition Check that if the current route is equal to the current items screen route, then it'll be marked as selected, otherwise not.

So if you notice at the start, the starting destination for the nav graph would be the home screen. So the current route will be the home screen route. Anyways. Now let's finally add the functionality to actually navigate to different screens on the on click method.

onClick = { 

Now nav controller has a navigate method, which takes in the screen route and navigates to the respective screens on the right. You'll see exactly that happening.

We have multiple navigation options with the Navigate method as well. For example, the Navigate Method will run a new instance of the screen every time we navigate to the respective.

So we had a mode for this in XML as well called Launch Single Top Mode. Similarly, we can pass the single top option and the navigation options as well.

navController.navigate(item.screen_route) { 
    launchSingleTop = true 

With that added now, only single instance of the screen will be launched once we navigate to the respective screen. Along with the launch mode. We also have the popup to method, which pops up to the destination to avoid large stacks.

Let's add that first.

Here. What we are doing is basically that we found the starting destination of the graph. In our case, that would be the home screen, so it gets the start destination. And if that is not nu, it sets the popup two method for the start destination. In simple words, this would mean that pressing the back arrow button from any destination would pop the entire backsack to home.

We also have set the save state to true. That means that when popping up or going back, it would actually store the save state. Along with that, we have something called as Restore State, which is another navigation option.

 Now this attribute determines whether this navigation action should restore any state previously saved by save state or the popup to save state attribute.

 So as expected, everything is working fine now. So now let's take a look at how we would have multiple nav graphs inside of the same project like we used to have in xml. In xml. If you remember, we could have multiple nav graphs such as a different nav graph for the auth module and a different nav graph for the main navigation of the project. So for that, let's create a detailed screen for the homepage and a language selection screen for the settings page.

So now I have created two more screens. One is the language screen, which simply contains column along with some text, and same is the detailed screen, which also contains a column along with some text. Also, notice that I have changed the home screen and added a button at the center.

Similarly, I have also changed the setting screen to add a button at the center. If I go ahead and show you the app, you'll see that on the home screen you'll find a go-to detail screen button, and on the setting screen you'll find a go-to language screen button. Now obviously on clicking, nothing will happen on these buttons because we haven't hooked up these screens with the navigation.

 Right. So with all the screens made, let's now create different nav graphs for the home screen and also the setting screen for the home screen. I have created a package navigation in which I will be having the nav graph for the home screen.

So as you can see that I am having this graph and I want the home screen to be shown here, I would want to shift the home screen to this nav graph because now our main navigation controller will use the entire nav graph to navigate. So in this nav graph, since we want the home screen to be the starting screen, that's why we shift the home screen in this nav graph itself.

 Similarly for the setting screen, let's create the setting screen's item as well.

 So we have created yet another item for the setting screens item. I have done nothing different from what I did in the home screen nav graph. Now let's create the nav graph for both of them, starting with the home screen.

Similar to before we create a new nav graph named Home Nav. It is an extension function to the nav graft builder, which contains the detailed screen, the navigation method takes in the start destination and then add the different screens as before. In our case, we want the start destination to be the home screen itself.

Similar to this, let's also create the setting nav graph as.

 Here we are. The language. Now that we have the nav graph for the screens ready, we need to inform the main nav host that home and setting screens have a nested navigation, so it needs to be aware of that.

So to make the main nav hosts aware. Now, instead of referencing the screens, we are now referencing the entire nav graph for those screens in the main navigation component. Along with this, we also want to change the bottom navigation to reference the graphs inside of the screen. So let's change that.

So here you can see that I have changed the screen routes to reference the respective graphs instead of the screens themselves. And for the screen routes, I have used the starting screen as the route names for them. That is done because on click off the bottom bar, we navigate to the respective screen. In the main nav controller, we have the nav graphs. Now upon clicking on the

home button, the navigation controller navigates to the home screen, which is the starting screen of the home nav graft. So the nav controller loads the home nav graph onto the nav host

 so now what I have done is that I have basically passed on the nav controller to the home and the settings nav graph. In the home nav graph, I have also passed down the nav controller to the home and the detailed screen. Similarly, in the settings nav graph, I have passed down the nav host controller to the settings and the language screen.

I have done this because I want to navigate from the setting screen to the language screen, and in the home screen, I want to navigate from the home screen to the detailed screen. So if I go ahead in the setting screen, let's say. In the on click method of the button, I am simply navigating to the language screen.

In here I am passing the language as the screen route name for the Navs controller. And in the home screen, I am similarly passing in the detailed page, screen route name to the navigation controller. Now, if you run the app. We'll see that we are able to now go to the detailed screen, come back to the home screen, go to the favorite screen setting, screen language screen back and back to home, so everything is working as expected and we are able to navigate between the different graphs as well.


Hey guys,

just a quick pause here. Just want to tell you that if you're interested in learning about the advanced concepts of jetpack compose such as creating our own animations, gestures, views, and layouts, then do check out my course here.


Right. So now that we know about the nested navigation, let's learn about passing the arguments as well. So let's pass in the arguments to the details screen. we are going to pass an ID argument into the detail screen, which would be of the integer data type.

So as you can see here in the home screen nav graph, I have added a list of arguments. For the detailed screen. We have an argument name and the type of the argument that it contains. In our case, that would be an integer. Then in the detail composable, we need to pass the route and the arguments.


    route =     "${HomeScreenItems.Detail.screen_route}/{${HomeScreenItems.Detail.ARGUMENT_NAME}}", 
    arguments = HomeScreenItems.Detail.arguments 

So here, as you can see that we have created a route note that whenever we need to pass in an argument, we pass them in a block. That means the argument name that we have passed in the route parameter of the composable is the placeholder, which will be replaced by the actual argument. Remember to keep it inside curly square brackets.

Then we pass on the list of arguments, which we created in the detailed screen object of the home screen items. That is the list of arguments, which in our case contains only one item. That is an argument often in integer data type.

Okay. Finally we get that argument from the route and pass it onto the details.

Note that in the detail screen. I am now showing the ID that was passed as an argument So now we have everything in place. We just need to pass on the actual arguments now. So let's do that from the home screen as well.

 In the home screen, I am doing nothing fancy.

I have just included a mutable state of integer value and three buttons, as for now. So this is the first button with the click state one, the button with the click state two, and the button, the click state.

With each button I am passing a different integer value as an argument to the detailed screen in the same format as we described are detailed screen route in.

 Note that I have changed the ID parameter to use the dollar sign instead of the curly braces. It was a simple typo that I made. Anyways, let's now jump onto the app. So if I go ahead and build the app, you'll see that I now have three details, screen buttons. Now if I go ahead and click on the screen one, I'll see that detailed screen one is being showing up on the two detailed screen.

Two will show up, and similarly on three, the detailed screen three will show up. So now we see that the correct argument is being shown when we click on the respective buttons. So to summarize it, what happened was that we defined an argument name and the type of argument that we were going to take for the detailed screen.

After that, we create a route that takes in the same arguments. We get that argument from the route and pass it on to the detailed screen. The argument parameter in the compostable method takes in a list of arguments. Places it as a bundle to the nav backsack for the screen to use. We get those arguments and pass it onto the actual screen.

 So now that we know about passing the arguments, now let's finally take a look at how we will define a deep link for our app. I want my deep link to open up a respective detailed screen based on the argument. So to do that, the first step is to add the intent filters in the manifest, which is exactly the same what we did in XML layouts as well.

So in here you can see that I have added the intent filter along with a data scheme and a host, right? This is exactly the same scenario of what we used today in the XM layouts as well. After that, we would want to create the deep links for the detail screen.

val deepLinks = listOf( 
    navDeepLink { 
        uriPattern = "myapp://${Detail.screen_route}/{${ARGUMENT_NAME}}" 

Now firstly for the detailed screen, we pass in the list of nav, deep link along with the URI pattern. So this is the URI pattern that we have decided upon, so that will be the scheme of that app that we defined in the Android manifest.

Along with that, I pass on the details screen route. Add the argument that we need to pass onto the detail screen

 after that to use the deep links in the detailed screen. We add that into the composable as well.

So in here we first define the URI pattern of the deep link that we want to match in our case. That is the same route with the arguments. We can pass any argument with the deep link. I have also added the deep link's parameter to the composable as well. Now let's test the functionality. We can go to the terminal and run the command to test it.

So before testing it,

you need to make sure that you have installed the app onto the screen. Now you can go ahead and close it And then open up the terminal. Now you need to make sure that you are exactly at the place where your ADB is located. So in my case, I have my ADB located in the platform tools. Now, after that is done,

./adb shell am start -d "myapp://Details/20" -a android.intent.action.VIEW

You can go ahead and run this command. This command will basically start the app along with the deep link that we have provided. So in our case, we have provided the argument of 20. So let's test this functionality first. As you can see that now, detail screen 20 has been printed onto the screen. Now we

can use this command to pass in any argument we want and open up the app using the deep links.

So instead of 20, let's use hundred as the argument and as usually can see that now detailed screen hundred is being printed onto the screen. That means it is taking the argument value directly from the Deep Links itself.

So let's keep the blog, Until this point, I hope that you learn something of value in this blog. Thank you for reading the blog and for your time, we'll meet in another blog. Until then, keep contributing Code to Humanity.

32 views0 comments

Book an appointment

bottom of page