Navigation is an essential element of every Android app. It should be fast, intuitive and reliable. Good navigation patterns are natural to the user, providing a seamless flow without ever having to think about it. In this article we will briefly look back at how navigation traditionally was done on Android and the pitfalls and caveats that came with it. We’ll discuss the introduction of the Navigation Component library (‘NCL’) in 2018 and see if the NCL is mature enough for a large production app in 2020.
Traditionally, on Android, you would use an Activity for every screen. If you were to navigate from one screen to another you would send the Android Framework your implicit or explicit Intent and the Framework would then try to launch the next Activity.
Activities are like islands; they provide entry points to your application and basically enable you to display data. Since Activities are 100% distinct from one another, sharing data between them is only possible either through disk, singletons or Intent arguments. Due to this fundamental design decision from the early days of Android, it is effectively not possible to share ViewModels between different activities. Every time you launch a new Activity, you ask the Android Framework to essentially create a new entry point for your app, usually only to display some different data. Having one or a very limited number of Activities inside your app improves UI transitions, makes sharing data between screens easier and prevents Activity lifecycle issues. A more in depth analysis and discussion of the benefits of the single Activity model can be found here.
It is possible to use Fragments instead of Activities. But usually this would entail to manually performing Fragment transactions and managing the back stack (and possibly an additional ‘up stack’ for when the ‘up’ button has different navigation behavior). This is tedious and error prone. The NCL promises to address these issues and promotes the use of the single Activity pattern with multiple Fragments as ‘destinations’.
Google introduced the NCL at the I/O developer festival in May 2018. It basically consists of 3 ‘N’s’.
- NavGraph, an XML resource file that contains all your destinations and navigation actions
- NavController, object to perform navigation actions inside the NavGraph
- NavHostFragment, an empty container to swap Fragments in and out
These 3 elements combined make up the NCL. The idea is to use a very limited number of activities in your app (or even just 1). The Activity will serve as an entry point into your app and manage global navigation with use of a BottomNavigationView or alternative UI element. Your apps’ content will be displayed inside the NavHostFragment. The NCL performs all the Fragment transactions, provide type safe ways to share data between screens (SafeArgs) and let you scope a ViewModel to a NavController. For a further introduction I refer you to the well written official documentation.
All this sounds great. At Egeniq we are currently building a large travel app with over 100+ screens. Since it’s been almost 2 years since the library was released, we decided to try the NCL and see how all these promises would pan out.
Before we dive into the details it’s important to note that modern Android smartphones are slimming down their screen bezels every new iteration. This means screens are getting taller. In 2015 the majority of smartphones had a 16:9 aspect ratio, in 2020 this has shifted to 20:9 or even taller. The body width of the mainstream phones has remained equal at around 70-75mm. Since many people use their phone with one hand, reaching the top corners is getting more difficult. Many app developers have recognized this trend and started replacing top navigation UI elements like Toolbars and DrawerLayouts in favor of a bottom navigation menu. Of course, Google is also aware of this trend so the NCL has first class support for navigating through a BottomNavigationView.
Taking the tour
Our experience with the basics of navigation with the NCL were pretty good. Navigating from one screen to another is just 1-2 lines of code and drawing an arrow inside the Navigation editor. We used SafeArgs to pass data between different destinations inside the navigation graph. We also scoped a ViewModel to the current NavController when we had multiple Fragments inside the same Activity. This was a nice alternative to SafeArgs when we wanted to share data between Fragments that were visible on the same screen.
Since our app has so many screens, it’s very convenient to have the Navigation Editor provide a visual overlay of the way users would navigate through the app. As a developer this enables you to verify if the mental model a user needs to make in their head is logical and not too complicated. It also made discussing the app functionality with the designer easier. Difficulties arose when we wanted to have multiple back stacks for the five ‘top level’ destinations a user could reach through the bottom navigation menu.
A common scenario for our app would be a user scrolling through our ‘home’ page which contained a long list of videos and select a certain video to see more details. Then the user could decide to go into ‘notifications’ to check if someone in the video sent him a message earlier. When the user would navigate back to ‘home’ through the bottom navigation menu they would expect to see the detailed video screen intact. Secondly, the user would expect to be able to return to the same scroll position in the video list on ‘home’. The NCL library does not support multiple back stacks (as of version 2.2.2 – released in April 2020). This seemed like a deal breaker for us, since it’s a core requirement for our app. It’s questionable why the default behavior for the NCL is to clear the back stack and lose all UI state when switching between bottom tabs. On iOS this is not the case and we feel for most apps this provides a better user experience.
Luckily we found an advanced Google sample which has a workaround solution to provide multiple back stacks in conjunction with the NCL.
The idea is to have a separate NavGraph for each of the top level destinations (so five for our app). The sample contained an extension function on BottomNavigationView which set up a FragmentManager, a list of NavGraphs, a NavHostFragment and a NavController wrapped in a MutableLiveData object. It would then basically create a separate NavHostFragment and NavController for each of the 5 ‘top level’ destinations so they would have their own separate back stacks. The FragmentManager would push and pop Fragments on or off the back stack as the user navigated inside one of the five NavGraphs. The first Fragment was excluded to provide a ‘fixed’ start destination. The user could switch to another NavGraph while leaving the previous back stack intact by saving it to a Bundle.
We implemented this workaround in our app and it seems to do the job. We are going to test it more thoroughly in the future when our codebase and number of screens expands. The Navigation Component team at Google is currently working on getting multiple back stack support into the NCL. Their goal was to already implemented it in version 2.3.0, but they ran into issues. We feel it’s an important step towards further adoption of the library by the Android community, since many flagship Android apps have a bottom navigation menu with multiple back stacks (like Google’s own YouTube app, LinkedIn, Netflix and many more).
Navigation is an essential element of every Android app. And it has come a long way on Android. Overall our experience with the NCL library was pleasant and the basics work well. To provide a quick summary:
Things we really liked were:
- A quick, visual and comprehensive overview of all the screens in the app. This also makes it easier to add new developers to the team and get them up to speed
- Navigating from one screen to another and passing data is just 1-2 lines of code and drawing an arrow inside the Navigation editor.
- SafeArgs is a nice type, safe way to share data between Fragment destinations
- Scoping a ViewModel to a NavController to share data with multiple Fragments inside the same screen. This provides data encapsulation and safety.
- NCL lets you adopt the single activity model easily and gain all the previously mentioned benefits that come with it.
Things to improve:
- Built-in support for multiple back stacks
- Change the default behaviour so that switching between tabs within a bottom navigation menu will not reset the back stack and save UI state (like iOS does)
If the NCL would support the more complex navigation cases out of the box (like multiple back stacks) we feel the library is mature enough to use in large production apps. With the NCL, building navigation for your app is fast and easy to implement. Secondly, it makes shifting to the single Activity model a breeze and with that, it enables you to improve the architecture and robustness of your app.