Jetpack Navigation refresher

Jetpack Navigation refresher

Jetpack Navigation library has solved one of the most common and bigger pain points in Android development.

If you were unlucky enough to do Android development before Jetpack Navigation (and in general Jetpack), Android development resembled a jungle. You would have to reinvent the wheel yourself by implementing a clean navigation framework, or you could rely on one of the many (some of them excellent) third-party libraries (but you would have to figure out which one, and of course each one would have its own quirks).

Jetpack Navigation offers a clean and consistent way of handling navigation that covers most of the use-cases of a typical app. Also, it has IDE support in Android studio for visual editing of the navigation journeys and excellent documentation.

I won't cover every feature of the library in this post. My aim is rather to remind you how to use the library if you've used it before in a previous project and you want to use it again in a new one. If you haven't used it before, I suggest to read the documentation first (or if you are a fast learner, read the post :)

Installation

The library is split into different parts. Install only the features that you will use. Head here for the latest version and all the available parts you can install.

dependencies {
  def nav_version = "2.3.0-alpha03"

  // Base
  implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
  implementation "androidx.navigation:navigation-ui-ktx:$nav_version"

  // Testing Navigation
  androidTestImplementation "androidx.navigation:navigation-testing:$nav_version"
}
build.gradle (Module)

For auto-generating navigation actions and passing parameters in a type-safe way:

buildscript {
    repositories {
        google()
    }
    dependencies {
        def nav_version = "2.3.0-alpha03"
        classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
    }
}
build.gradle (Project)
apply plugin: "androidx.navigation.safeargs.kotlin"
build.gradle (Module)

Stores the screens and paths between them. Placed in res/navigation/your_nav_graph_name.xml.

For simple navigation, Screen 1 Screen 2, you would have the following  navigation XML (it's easier to use the UI editor anyway but good to have a basic understanding of the XML):

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:android="http://schemas.android.com/apk/res/android"
    app:startDestination="@id/screen1">
    
    <fragment
        android:id="@+id/screen1"
        android:name="com.example.cashdog.cashdog.Screen1Fragment"
        android:label="screen_1"
        tools:layout="@layout/fragment_screen_1" >
        <action
            android:id="@+id/action_screen1_to_screen2"
            app:destination="@id/screen2" />
    </fragment>
    
    <fragment
        android:id="@+id/screen2"
        android:name="com.example.cashdog.cashdog.Screen2"
        android:label="screen_2"
        tools:layout="@layout/fragment_screen2" />
</navigation>
res/navigation/nav_graph.xml

The navigation library pretty much just switches between Fragments for performing the navigation. This "switching" happens in a nav host that needs to be placed in the main activity.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <fragment
        android:id="@+id/nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true"
        app:navGraph="@navigation/nav_graph" />

</LinearLayout>
activity_main.xml

Noteworthy: app:defaultNavHost defines which navigation graph intercepts the system back button, in case there are multiple nav hosts in the same layout.

In summary:
1) Get a reference to the view
2) Find the nav host
3) Navigate using the auto-generated directions

override fun onClick(view: View) {
    val action =
        Screen1Directions
            .actionScreen1ToScreen2()
    view.findNavController().navigate(action)
}

Parameters

You can pass parameters between screens in a type-safe way using auto-generated classes. Remember that when changing the navigation graph, you might need to run Build    Make Project for the auto-generated classes to be created.

For the Screen 1 Screen 2 example above, if we want to send a parameter:

    <fragment
        android:id="@+id/screen1"
        android:name="com.example.cashdog.cashdog.Screen1Fragment"
        android:label="screen_1"
        tools:layout="@layout/fragment_screen_1" >
        <action
            android:id="@+id/action_screen1_to_screen2"
            app:destination="@id/screen2">
            
            <argument
            	android:name="amount"
            	app:argType="integer"
            	android:defaultValue="1" />
                
        </action>
    </fragment>
nav_graph.xml
override fun onClick(v: View) {
   val action = Screen1Directions.actionScreen1ToScreen2(42)
   v.findNavController().navigate(action)
}
Screen1.kt

And to receive this parameter:

val args: Screen1Args by navArgs()

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    val amount = args.amount
    amountTextView.text = amount.toString()
}
Screen2.kt

Stack history

The library takes care of the back stack automatically (i.e. if you navigate Screen 1 Screen 2, and hit back you will go to Screen 1). There are times that you don't want to go back (e.g. after a successful login, you don't want to go back to the login screen).

To clear the stack when you navigate set the popUpTo Action argument (or use the UI editor). This instructs the library to clear the stack up to the defined screen. The popUpToInclusive removes the defined screen as well.

In our example, if you navigate Screen 1 Screen 2 and you don't want to go back to Screen 1, you have to clear the stack up to the Screen 1 (included). Hitting back with an empty stack will exit the app.

  <fragment
        android:id="@+id/screen1"
        android:name="com.example.cashdog.cashdog.Screen1Fragment"
        android:label="screen_1"
        tools:layout="@layout/fragment_screen_1" >
        <action
            android:id="@+id/action_screen1_to_screen2"
            app:destination="@id/screen2">
            <argument
            	android:name="amount"
            	app:argType="integer"
            	android:defaultValue="1" 
                
                app:popUpTo="@+id/screen1"
                app:popUpToInclusive="true"
                
                />
        </action>
    </fragment>

You can alternative pop the entire navigation graph by setting:

[...]
app:popUpTo="@+id/nav_graph"
app:popUpToInclusive="true"
[...]

The Jetpack Navigation library has way more features than what is covered here. Hopefully, this was a good enough refresher that will allow you to set up the basic navigation in your app!

Show Comments