Android Navigation Components — Part 3
Two weeks ago, I started writing about the Android Navigation Components.
The Android Navigation Components library is part of the Android JetPack set of libraries announced and released during Google I/O 2018.
I started these series of posts talking about the importance of (re) understanding your app structure before using the library.
Then, I wrote a second post to show you how to implement the Navigation Components and how effortless it is to do so after you have a good understanding of your app structure.
If you haven’t had the chance to check out the two previous posts, you can find them here and here.
This post will cover things beyond the simple case of going from one destination to another shown in the previous post.
The topics in this post are separated into independent sections and you can jump straight to the topic that is most relevant to you.
I will try to keep this as a Live post and update it as I start to encounter more interesting stuff worth sharing here.
By the end of this post, you will be able to:
- Use the safe args Gradle plugin to pass data between destinations.
- Setup the Navigation Component to work with different Views.
- Handling Navigation between two Activity destinations.
- Add deep linking support to a destination.
Passing data around in a safe way — The safe args plugin
On the previous part of this series, I talked about how to pass data from one destination to another using a bundle as you might have been doing all this time.
This solution is fine but it does not prevent the developer to pass a random Bundle or use the wrong key when extracting the arguments from it.
To prevent this small mistakes from happening, the Navigation Components library comes with a Gradle plugin called safe args.
Safe args let you define your destinations arguments from the editor and it will generate code to help you bundle and unbundle these arguments.
To use the safe args plugin, you need to first add the classpath to the Gradle plugin on your project build.gradle
file:
classpath "android.arch.navigation:navigation-safe-args-gradle-plugin:1.0.0-alpha01"
then apply the plugin to the app module Gradle build file.
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'//Apply Safe Args Plugin
apply plugin: 'androidx.navigation.safeargs'
android {
...
}
After doing the project sync with Gradle, you can start adding safe args for the destinations in your app using the Navigation Editor.
To do this you have to click on top of the destination in the Navigation Graph, go to the attributes panel and start adding all the arguments the destination can receive.
The editor will allow you to define, the argument name, type(Int, String, Reference) and the default value.
After adding all the arguments, the project needs to be rebuilt and the Gradle plugin will generate two files, one to allow to help set the arguments and another to help retrieve the arguments from the bundle.
Set the arguments
The safe args plugin will generate a Directions class. The generated class implements the NavDirections
interface and this interface describes the Navigation operation.
A Navigation operation as I mentioned in a previous article is mainly composed by an action and it’s arguments.
The generated class will provide setters to define all the arguments defined.
After defining the arguments, an instance of this class can be passed to the NavController
navigate function.
For the Events Finder App that has been used as an example for these series of posts, setting the eventId
argument and navigate to the event details destination would be as shown below:
Retrieve the arguments
On the destination side, the safe args plugin will generate a class that has the defined arguments as variables and a static method that takes in a bundle and retrieves the values for all the arguments.
For this posts app example, retrieving arguments using the generated class by the safe args plugin looks like this:
Setup the Navigation Component to work with different Views.
Up until now, the examples showing how to do the actual Navigation did it so “manually”.
By “manually” I mean going through the process of implementing listeners for the UI components and in the implementation of this listeners get the Nav Controller and call the navigate method.
All that is correct and it is okay to do, but in the case where we have things such a Navigation Drawer, going through that process would be boring and add a lot of unnecessary boiler plate code.
To help reduce the boilerplate, Google has created a set of static functions that helps with the setup of the Navigation component with some common UI components such as Toolbars and the Bottom Navigation View.
These functions are inside the NavigationUI class.
This class contains functions for binding with an ActionBar, NavigationView and Bottom Navigation View.
If you have noticed in the diagrams shown in the previous posts, the Event Finder App principal destinations have no arrows(Actions) connecting them.
This is because the top level destinations will be connected by a specific navigation component.
The example in this post has only 3 top level destinations and according to the Material design specs, the Bottom Navigation View is the suitable navigation components to use.
Setup the navigation controller with the Bottom Navigation View
As I mentioned, the setup of the Navigation Controller with the Bottom Navigation component is easy and it is done by calling the following static function of the NavigationUI class:
setupWithNavController(bottomNav, navHost)
When this setup is done, the click events, and the selection state of the Bottom Navigation items will all be handled for you by the Nav Controller.
The only thing required to have this function working is to ensure that the id’s in the Bottom Navigation menu items are the same id’s as the navigation graph destinations.
Setup the Navigation Controller with a Custom View
As I mentioned earlier, the NavigationUi class only contains functions to support a small set of UI components in the NavigationUI. If you have a custom View, you will have to create your functions.
To create your own function you will need to follow these two steps in order:
- Handle the click events for your custom UI component and use the Nav controller to navigate to the desired destination.
- Listen to the completion of the Navigation inside the
addOnNavigatedListener
and do the work required after the navigation done. (Example: Check the currently selected item).
These are the exact same steps followed by the existing functions in the NavigationUtils class and you can see below the implementation for the function that sets up the Bottom Navigation View:
Navigating between Activities
While Google is recommending and pushing for the single Activity application model, this is not the model currently used by most of the apps out there.
If this is the case with the app you are working on, this should not be a problem.
There is a good way to start using the Navigation Components and move(or not) to the Single Activity application model without having to break everything.
This consists of identifying which destinations are currently at the same level and group them together in the same navigation graph hosted in an Activity.
If you look at the use case diagram for the Event Finder app, we can set up things so that:
- The top three destinations in the same level are in the main_nav_graph.xml hosted in the MainActivity.
- The event details destinations and a fictional checkout flow are in the same navigation graph (checkout_flow_nav_graph.xml)hosted in the CheckoutFlowActivity.
- The CheckoutFlowActivity will be a destination inside the main_nav_graph.xml and we can navigate to it from any of the destinations in that graph the same way as any other destinations in the same graph as shown in the previous posts and below:
The Navigation controller will know that the action you are calling is for an Activity destination and it will make all the calls needed to start an Activity and pass the arguments it requires.
Navigating Up from an Activity destination
To navigate up, for example from the CheckoutFlowActivity
to the MainActivity
, you will have to do it the same way it has been recommended to do this even before the Navigation Components library existence.
As shown in the docs provided on this page, you will have to:
- Define a meta-data tag specifying that the
MainActivity
is the parent of theCheckoutFlowActivity
in the CheckoutActivity manifest declaration.
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".home.MainActivity" />
2. Use the NavUtils class to navigateUp. Using this class to navigate up to the parent activity, in most of the cases only requires that we call navigateUpFromSameTask
function.
However, if this activity is accessible through a deep link, navigating up will take the user out of the app.
To correct that behavior, the documentation advises doing the necessary check and recreate the task with a parent and navigate up if needed.
Deep linking to a destination
Deep linking is one of those things that I always thought was very painful especially before the release of Firebase Dynamic links.
If you don’t know what are deep links, according to the docs in the developer.android.com website:
Deep links are URLs that take users directly to specific content in your app. In Android, you can set up deep links by adding intent filters and extracting data from incoming intents to drive users to the right activity.
When a user receives and opens a deep link, two things can happen:
- If the user does not have the app installed she will be taken to the playstore to download the app and when the download is complete opening the app will take her to a specific content in the app.
- If the user does have the app installed, opening the deep link will take her to the specific content.
As I wrote above, handling the different scenarios was a very complicated thing to do, and thanks to the Firebase Dynamic Link and now the Navigation Components, implementing Deep linking in an app is an easy and pleasant thing to do.
On this post, I will not go into details about Firebase Dynamic links but I STRONGLY RECOMMEND you read more about the topic on this amazing post by Joe Birch.
To implement deep linking on the Event App Finder that takes the user straight to an event details screen you should:
- Click on the event details fragment destination in the main_nav_graph.xml and on the attributes panel, click the plus sign where it says deep link.
- On the little dialog that it is opened, Insert the Uri to be used to identify the destination and when you are done click the “add” button.
As you can see on the image below the Uri ends with the “event_id” word inside the curly brackets. This is defining a placeholder that you can replace with the actual arguments you will specify at run time.
Example: A deep link to take the user to an event details with the id number 2334456 would be something like this: https://eventfinder.app/event/2334456
3. Specify the navigation graph in the CheckoutActivity manifest definition. You can do this by copying and pasting the following line :
<nav-graph android:value="@navigation/main_nav"/>
During the manifest merge step, this line will be expanded and the new manifest will have all the required configurations required to handle deep links.
To test that everything is working, there is an ADB command that allow you to start an Activity given a URI and the application package name.
adb shell am start -a android.intent.action.VIEW -d "https://eventfinder.app/event/2334456" com.eventfinder.app
As I mentioned in the beginning of this post, It was not intended to be read from top to bottom and I intend to keep updating it as the Navigation Library becomes more mature and I encounter more interesting things to share.
This is the last post of this “intro” series about the Navigation Components and I hope that you were able to learn something new by reading them.
If you have any questions, suggestions or simply want to share your experience working with the Navigation Components, please don’t hesitate to drop a comment on the section below or send me a tweet:
See you next time!
DM