Android Setup Jetpack Navigation (Kotlin)

September 30, 2019

Why use Jetpack Navigation

  • One activity/theme to rule them all: each screen will be either a fragment or dialog
  • Auto handling of fragment transactions, with support for transition and animation
  • Easy integration with BottomNavigationView
  • Safe Args - easily pass data between destinations (fragment/dialog)
  • Ensures a consistent and predictable user experience by adhering to an established set of principles.

Edit project build.gradle.

buildscript {
    ext {
        ...
        material_version = '1.0.0'
        nav_version = '2.1.0'
    }

    ...

    dependencies {
        ...

        classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
    }
}

Edit module:app build.gradle.

...
// apply plugin: "androidx.navigation.safeargs"
apply plugin: "androidx.navigation.safeargs.kotlin"

android {
    ...

    kotlinOptions {
        jvmTarget = "1.8"
    }
}


dependencies {
    ...

    // Material
    implementation "com.google.android.material:material:$material_version"

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

Edit gradle.properties

android.useAndroidX=true

MainActivity.kt

class MainActivity : AppCompatActivity() {
    private lateinit var navController: NavController

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.main)
        setSupportActionBar(toolbar)

        navController = findNavController(R.id.nav_host_fragment)

        val appBarConfig = AppBarConfiguration()
        setupActionBarWithNavController(navController, appBarConfig)

        bottomNavView.setupWithNavController(navController)
    }

    override fun onSupportNavigateUp(): Boolean {
        // Allows NavigationUI to support proper up navigation or the drawer layout
        // drawer menu, depending on the situation
        // return navController.navigateUp(drawerLayout)
        return navController.navigateUp()
    }
}

res/layout/main.xml

<?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"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <androidx.appcompat.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/colorPrimary"
        android:theme="@style/ThemeOverlay.MaterialComponents.Dark.ActionBar" />

    <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/mobile_navigation" />

</LinearLayout>
  • navigate_home -> flow_step1(name) -> flow_step2(age) -> navigate_home
<?xml version="1.0" encoding="utf-8"?>
<navigation 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:id="@+id/mobile_navigation"
    app:startDestination="@id/navigate_home">

    <fragment
        android:id="@+id/navigate_home"
        android:name="com.luasoftware.bottomnavigationexample.view.HomeFragment"
        android:label="Home"
        tools:layout="@layout/home" >
        <action
            android:id="@+id/action_navigate_home_to_flow_step1"
            app:destination="@id/flow_step1" />
    </fragment>
    <fragment
        android:id="@+id/flow_step1"
        android:name="com.luasoftware.bottomnavigationexample.view.flow.Step1FlowFragment"
        android:label="step1_flow"
        tools:layout="@layout/step1_flow" >
        <action
            android:id="@+id/action_flow_step1_to_flow_step2"
            app:destination="@+id/flow_step2" />
        <argument
            android:name="name"
            app:argType="string" />
    </fragment>
    <fragment
        android:id="@+id/flow_step2"
        android:name="com.luasoftware.bottomnavigationexample.view.flow.Step2FlowFragment"
        android:label="step2_flow"
        tools:layout="@layout/step2_flow" >
        <argument
            android:name="age"
            app:argType="integer" />
        <action
            android:id="@+id/action_flow_step2_to_navigate_home"
            app:destination="@id/navigate_home" />
    </fragment>
</navigation>

Fragments

Home

class HomeFragment : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.home, container, false)
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)

        startFlowButton.setOnClickListener {
            // findNavController().navigate(R.id.flow_step1, null)
            val name = "Desmond"
            val action = HomeFragmentDirections.actionNavigateHomeToFlowStep1(name)
            findNavController().navigate(action)
        }
    }
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".view.HomeFragment">

    <Button
        android:id="@+id/startFlowButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Start Flow" />

</LinearLayout>

Step1Flow

class Step1FlowFragment : Fragment() {

    val args: Step1FlowFragmentArgs by navArgs()

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.step1_flow, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        statusTextView.text = args.name

        nextFlowButton.setOnClickListener {
            val age = 40
            val action = Step1FlowFragmentDirections.actionFlowStep1ToFlowStep2(age)
            findNavController().navigate(action)
        }
    }
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".view.flow.Step1FlowFragment">

    <TextView
        android:id="@+id/statusTextView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Step1 Flow" />

    <Button
        android:id="@+id/nextFlowButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Next Flow" />

</LinearLayout>

Step2Flow

class Step2FlowFragment : Fragment() {

    val args: Step2FlowFragmentArgs by navArgs()

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.step2_flow, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        statusTextView.text = args.age.toString()

        endFlowButton.setOnClickListener {
            val action = Step2FlowFragmentDirections.actionFlowStep2ToNavigateHome()
            findNavController().navigate(action)
        }
    }
}
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".view.flow.Step2FlowFragment">

    <TextView
        android:id="@+id/statusTextView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Step1 Flow" />

    <Button
        android:id="@+id/endFlowButton"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="End Flow" />

</LinearLayout>

NOTE: Refer Android Setup BottomNavigationView With Jetpack Navigation UI (Kotlin).

Proguard

-keepnames class com.path.to.your.ParcelableArg
-keepnames class com.path.to.your.SerializableArg
-keepnames class com.path.to.your.EnumArg

References:

This work is licensed under a
Creative Commons Attribution-NonCommercial 4.0 International License.