Android Setup Jetpack Navigation (Kotlin)

Sep 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.

Caveats: Android Jetpack Navigation Fragment Lost State After Navigation

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:

❤️ Is this article helpful?

Buy me a coffee ☕ or support my work via PayPal to keep this space 🖖 and ad-free.

Do send some 💖 to @d_luaz or share this article.

✨ By Desmond Lua

A dream boy who enjoys making apps, travelling and making youtube videos. Follow me on @d_luaz

👶 Apps I built

Travelopy - discover travel places in Malaysia, Singapore, Taiwan, Japan.