Google has been releasing a lot of Architecture Components in the past year. These are libraries and documents which help developers build better apps by standardizing certain parts of their applications. In terms of functionality and completeness the Lifecycle component is definitely my favorite. However, the navigation component is also very interesting, and not just in terms of the library itself, but also in the guidance it gives towards overall structure of the app. So I decided to write a list of (sometimes less obvious) reasons to start trying out the library now.
I would not necessarily advise anyone to rewrite their navigation stack if they already have something that works, but for new projects it has a couple of cool features, and steers towards some very nice, more general patterns for writing applications.
Awaiting the library to be completely stable, on a recent app I was working on, we structured it in such a way that we could have a branch containing a single commit which would switch us over to using the library once we deem it ready. We were successfully able to keep rebasing that branch on our master without much trouble.
1. High level abstractions as the standard
For years Google has provided very little guidance on application architecture. The recent introduction of “opinions, not prescriptions” (as explicitly stated by Google) seeks to change this. One of the big advantages of them being opinionated is that codebases built by different teams/companies may get to look more alike, thus making the chances of doing something wrong when you are on an unfamiliar codebase smaller.
It is not mandatory to use Fragment (although, truth be told, it takes quite a bit of knowledge of the API to implement a different navigator). Fragment navigation is just the default because it is what Google deems the easiest way to architect your app.
It is also not mandatory to use XML. A lot of the classes in the public API are specifically for building a navigation graph without XML.
PopUpToBuilder are all classes which you will never have to touch if you use XML. They are used by the
GraphInflater to generate a
NavGraph from an XML resource. Anything you can do in XML, you can achieve by just using these classes as well.
2. Small API
The public API of the library is very small. On a
ViewModel level you basically only have to deal with the
NavController and no need for touching any
FragmentTransaction. The introductory article takes just 20 minutes to read and covers most of the public API. I find that pretty impressive.
Besides the documentation itself being very good, if you take a close look at the class names in the library (granted that I have a bit of prior knowledge about the library). There is not a single class where the name does not make sense and describes what the class is intended for. I would say that there are very few libraries where the class names make so much sense, mostly because they all describe pretty high-level functionality.
Navigation between different screens is a graph. The editor actually gives you a very nice overview of the different screen your app consists of. Which I can imagine to be an awesome resource for a new developer on-boarding the team.
4. Android framework dependency
The design of the library suggests that
ViewModel layer would ideally only talk to the
Navigator and pass it a
resourceId for a destination or action (with possible arguments) This means that in that layer you don’t need any dependency on the Android Framework. In the theoretical scenario where we have a Navigator implementation for iOS this method call would be exactly the same at
ViewModel level. A different iOS specific implementation of the Navigation would handle all platform specific stuff. Thus not platform specific at all.
Testing is hard, testing
Fragment transactions, or verifying a back stack on the JVM without using Robolectric is impossible. Luckily we don’t need to do any of that to test a
ViewModel. The simple abstraction that the navigation library provides gives us some great opportunities for testing. The actual business logic part of navigation is much easier to test, since you only need to verify that one of the navigate methods is called with the correct action/destination id and possibly arguments.
For testing the actual navigation actions, you would either need to do a lot of mocking on the JVM, does not actually give you a lot of assurance, or use Robolectric/InstrumentedTests to verify Intent and or Bundle creation and the right method on the Activity/Fragment/FragmentManager being called. In my opinion these would ideally all be instrumented tests which actually execute the actions on an emulator or physical device. while mocking any external resources like an API.
6. Permissions, Google Sign-In
There is no option to start an Activity for a result in the library. According to this article such an action would typically be implemented using a
SingleLiveEvent, this means we don’t actually need the method in the NavController API. When your Fragment observes a SingleLiveEvent you can actually have, the call to
startActivityForResult, and the override of
onActivityResult, directly below each other. This is very nice because the cohesion between these methods is very high. If one of them changes, it is likely that the other one needs to be changed as well.
If the ViewModel would call some sort of
startActivityForResult on a NavController it would be quite tough to find out where that result is handled.
Also note that there is no need to go back to the Activity to launch another Activity for result. The Fragment class also has a
startActivityForResult, which will give you a callback with the same request code in that Fragment’s
onActivityResult (Initially the Activity’s
onActivityResult is called with a modified request code, but if that just calls it’s super method, the
FragmentManger will know what
Fragment it should pass the result to).
7. Conditional deep linking
I am going to be honest here and say that I have not worked with this yet, but I believe that if you need to use conditional deep-linking you can check the deep link on launch and then invoke one of the actions on the NavController to build the back-stack based on the condition. This will typically be a task the activity will delegate to a separate class in a single activity set-up.
8. Support for Instant Apps
The usage of
resourceIds for navigation gives you some great opportunities for doing Instant Apps (or modularization in general). If you have a gradle module for each feature, one application module which depends on all features, and one instant app module which just depends on core and a single feature modules, you can have two navigation graphs. One in the application module containing the full graph, and one in the instant app which just the navigation possible within the instant app.
Since the nav graphs are resource id based, you can define destinations as single ids in core and with your navigation graph in app you can then even navigate from one feature to another without the gradle modules depending on each other.
9. More control over analytics
Automatic tracking of services like Firebase and GA do not work as well in the single activity pattern as used by the navigation library. However, it gives you a couple of other, more accurate methods to track screens. Also, I think in most larger applications it is unlikely that you want that enabled since your client will want the tracking in a specific format.
When you are moving to single activity this gives you a powerful way of doing analytics, partly because if you have just one activity, you can handle it’s ViewModel lifetime as the user session. This was harder to track in an Activity based architecture. Also, you can use the FragmentManager’s LifecycleCallbacks to write a replacement of the automatic tracking feature.
Furthermore, NavController allows you to set
OnNavigatedListener to track navigation events, which you would not have in a traditional setup.
Although it is still in early stages I find the AAC Navigation library a very welcome addition to the tools Google provides us to build apps. Having a standard for the higher level abstraction the framework provides, will allow us to have familiarity across codebases. The Framework pushes you a little bit to use a single Activity setup, but once you have that, and realize that Fragments really are the new Activities, you will hopefully find it a very delightful change.