In this piece you will learn about Navigation Component through step by step android examples in Kotlin.

Contents hide

But first what is Navigation?

Navigation refers to the interactions that allow users to navigate across, into, and back out from the different pieces of content within your app.

Android Jetpack’s Navigation component helps you implement navigation, from simple button clicks to more complex patterns, such as app bars and the navigation drawer. The Navigation component also ensures a consistent and predictable user experience by adhering to an established set of principles.

Parts of a Navigation Component

The Navigation component consists of three key parts that are described below:

  • Navigation graph: An XML resource that contains all navigation-related information in one centralized location. This includes all of the individual content areas within your app, called destinations, as well as the possible paths that a user can take through your app.
  • NavHost: An empty container that displays destinations from your navigation graph. The Navigation component contains a default NavHost implementation, NavHostFragment, that displays fragment destinations.
  • NavController: An object that manages app navigation within a NavHost. The NavController orchestrates the swapping of destination content in the NavHost as users move throughout your app.

Advantages of using a Navigation Component

The Navigation Component provides several advantages over traditional navigation pattersm. These include:

  • It handles fragment transactions for you.
  • It handles Up and Back actions for you.
  • It provides standardized resources for animations and transitions.
  • It provides Implementation and handling of deep linking.
  • Through it you can Include Navigation UI patterns, such as navigation drawers and bottom navigation, with minimal additional work.
  • Safe Args – a Gradle plugin that provides type safety when navigating and passing data between destinations.
  • ViewModel support – you can scope a ViewModel to a navigation graph to share UI-related data between the graph’s destinations.

Example 1: Kotlin Android Navigation Component with Fragments

Let’s look at a simple example to help you learn Jetpack Component Navigation. The pages will be Fragments.

Step 1: Dependencies

Add the following Navigation Component dependencies in your gradle file:

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

    // Feature module Support
    implementation "androidx.navigation:navigation-dynamic-features-fragment:$nav_version"

Step 2: Create a Navigation Graph

The Navigation Graph as we had said is simply an XML resource that will contain all the navigation related information in one place. To create it, start by creating a folder known as navigation in your res directory. Then in this folder create a file called simple_navigation.xml and add the following code:

simple_navigation.xml

<?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/simple_navigation"
    app:startDestination="@id/homeFragment">

    <fragment
        android:id="@+id/homeFragment"
        android:name="com.example.mvvmnavigation.view.fragment.HomeFragment"
        android:label="fragment_home"
        tools:layout="@layout/fragment_home" >
        <action
            android:id="@+id/action_homeFragment_to_searchFragment"
            app:destination="@id/searchFragment"
            app:enterAnim="@anim/nav_default_pop_enter_anim"
            app:exitAnim="@anim/nav_default_pop_exit_anim" />
    </fragment>
    <fragment
        android:id="@+id/searchFragment"
        android:name="com.example.mvvmnavigation.view.fragment.SearchFragment"
        android:label="fragment_search"
        tools:layout="@layout/fragment_search" >
        <action
            android:id="@+id/action_searchFragment_to_homeFragment"
            app:destination="@id/homeFragment" />
        <action
            android:id="@+id/action_searchFragment_to_responsFragment"
            app:destination="@id/responsFragment"
            app:enterAnim="@anim/nav_default_pop_enter_anim"
            app:exitAnim="@anim/nav_default_pop_exit_anim" />
    </fragment>
    <fragment
        android:id="@+id/responsFragment"
        android:name="com.example.mvvmnavigation.view.fragment.ResponsFragment"
        android:label="fragment_respons"
        tools:layout="@layout/fragment_respons" >
        <action
            android:id="@+id/action_responsFragment_to_searchFragment"
            app:destination="@id/searchFragment" />
    </fragment>
</navigation>

Step 3: Create Fragment Layouts

In your layouts folder, create the XML UI designs for the Navigation Destinations. In this case we will have three fragments:

fragment_search.xml

In this fragment add three widgets: a TextView, a Button and a MultiAutoCompleteTextView:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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=".view.fragment.SearchFragment">

    <MultiAutoCompleteTextView
        android:id="@+id/multiAutoCompleteTextView_searchFragment_Search"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Search"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/textView_searchFragment_listSearch"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:background="#FFFFFF"
        android:elevation="6dp"
        android:gravity="center"
        android:padding="5dp"
        android:text="@string/str_list_search"
        app:layout_constraintBottom_toTopOf="@+id/btn_searchFragment_Search"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/multiAutoCompleteTextView_searchFragment_Search" />

    <Button
        android:id="@+id/btn_searchFragment_Search"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="104dp"
        android:text="Search"
        android:textAllCaps="false"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/multiAutoCompleteTextView_searchFragment_Search"
        app:layout_constraintVertical_bias="0.987" />
</androidx.constraintlayout.widget.ConstraintLayout>

fragment_respons.xml

Add two textview here:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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=".view.fragment.ResponsFragment"
    android:padding="10dp">

    <TextView
        android:id="@+id/textView_ResponsFragment_AboutMe"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/str_about_me"
        android:padding="10dp"
        android:layout_margin="10dp"
        android:layout_marginLeft="10dp"
        android:layout_marginRight="10dp"
        android:background="#FFFFFF"
        android:elevation="6dp"
        android:gravity="center"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/textView_ResponsFragment_Respons"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text=""
        android:padding="10dp"
        android:layout_margin="10dp"
        android:background="#FFFFFF"
        android:elevation="6dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/textView_ResponsFragment_AboutMe" />

</androidx.constraintlayout.widget.ConstraintLayout>

fragment_home.xml

Add a button here:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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=".view.fragment.HomeFragment">

    <Button
        android:id="@+id/btn_homeFragment_search"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Go To Search"
        android:textAllCaps="false"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

Step 4: Create Fragments

In this step you will write the code for the individual fragments. There are three fragments:

(a). SearchFragment.kt

Create the fragment by extending the Fragment class. Initialize the Button and MultiAutoCompleteTextView. The MultiAutoCompleteTextView will be used to search/filter the list. The search result will be transfered to the ResponsFragment via a Bundle object.

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.Button
import android.widget.MultiAutoCompleteTextView
import androidx.fragment.app.Fragment
import androidx.navigation.Navigation
import com.example.mvvmnavigation.R

class SearchFragment : Fragment() {

    lateinit var btn_search: Button
    lateinit var autoTextView: MultiAutoCompleteTextView

    override fun onCreateView(
            inflater: LayoutInflater, container: ViewGroup?,
            savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        val view = inflater.inflate(R.layout.fragment_search, container, false)

        Cast(view)

        return view
    }

    private fun Cast(view: View) {

        autoTextView = view.findViewById(R.id.multiAutoCompleteTextView_searchFragment_Search)
        btn_search = view.findViewById(R.id.btn_searchFragment_Search)

        val dataSearch = listOf(
                "ali",
                "reza",
                "mvvm",
                "nav",
                "android",
                "alireza",
                "mmd",
                "mvp",
                "kotlin",
                "java",
                "python",
                "telegram",
                "api",
                "android 10",
                "android 11",
                "android studio"
                )

        autoTextView.setAdapter(ArrayAdapter(view.context,android.R.layout.simple_list_item_1,dataSearch))
        autoTextView.threshold = 1
        autoTextView.setTokenizer(MultiAutoCompleteTextView.CommaTokenizer())

    }

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

        btn_search.setOnClickListener {

            val data = Bundle()
            data.putString("data", autoTextView.text.toString())
            Navigation.findNavController(btn_search).navigate(R.id.action_searchFragment_to_responsFragment,data)

        }
    }

}

(b). ResponsFragment.kt

Here you inflate the fragment_respons.xml layout then initialize the textview defined in it. This textview will be showing the search results. You receive these search results from the Search Fragment.

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.fragment.app.Fragment
import com.example.mvvmnavigation.R

class ResponsFragment : Fragment() {

    lateinit var txt: TextView

    override fun onCreateView(
            inflater: LayoutInflater, container: ViewGroup?,
            savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        val view = inflater.inflate(R.layout.fragment_respons, container, false)

        Cast(view)

        return view
    }

    private fun Cast(view: View) {
        txt = view.findViewById(R.id.textView_ResponsFragment_Respons)
    }

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

        val data: String? = arguments?.getString("data")

        if (data?.length ?: 0 >= 1)
            txt.text = data
        else
            txt.text = "Null"

    }

}

(a). HomeFragment.kt

From this home fragment you will navigate over to the SearchFragment. From there you will navigate over to the ResponseFragment. This fragment contains a button that when clicked initiates that navigation:

import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import androidx.navigation.Navigation
import com.example.mvvmnavigation.R

class HomeFragment : Fragment() {

    lateinit var btn_go_to_search : Button

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        val view = inflater.inflate(R.layout.fragment_home, container, false)

        Cast(view)

        return view
    }

    private fun Cast(view: View){

        btn_go_to_search = view.findViewById(R.id.btn_homeFragment_search)

    }

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

        btn_go_to_search.setOnClickListener {

            val navDirections = HomeFragmentDirections.actionHomeFragmentToSearchFragment()
            Navigation.findNavController(btn_go_to_search).navigate(navDirections)

        }

    }

}

Step 5: Create Main Activity

Fragments need to be hosted in an activity. For us this will be the MainActivity, our launcher activity. Here we will attach the NavigationController to the NavHost. Here is the full code:

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.navigation.NavController
import androidx.navigation.Navigation
import androidx.navigation.ui.NavigationUI
import com.example.mvvmnavigation.R
import com.example.mvvmnavigation.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding
    lateinit var navController: NavController

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)

        navController = Navigation.findNavController(this, R.id.fragment)
        NavigationUI.setupActionBarWithNavController(this, navController)

    }
}

Run

Finally run the project.

Reference

Here are the reference links:

Number Link
1. Download code
2. Follow code author
3. Navigation Component Reference

Example 2: Kotlin Android Navigation Component Example with Fragments and TransitionAnimations

Our pages will be Fragments. We will apply transition animations when moving from one Fragment to another.

This example will teach you the following:

  • Material Design
  • View binding
  • Jetpack Navigation Component
  • Safe Args
  • Deep links

Here are the demo screenshots of what is created:

Screenshots:

[Italian Trulli [Italian Trulli [Italian Trulli

Step 1: Create Project

Start by creating an empty Android Studio project.

Step 2: Dependencies

In your app/build.gradle add the following Jetpack Navigation Component Libraries:

    // navigation component
    implementation "androidx.navigation:navigation-fragment-ktx:2.3.2"
    implementation "androidx.navigation:navigation-ui-ktx:2.3.2"
}

Step 3: Enable ViewBinding and Java8

Still in the app/build.gradle, but under the android{} closure, enable viewBinding and Java8 as follows:

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    kotlinOptions {
        jvmTarget = '1.8'
    }

    buildFeatures {
        viewBinding true
    }

Step 4: Create Transition Animations

Create a folder known as anim under the res directory and inside it add transition animations. Here are some of the animations:

slide_in_right

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:duration="200"
        android:fromXDelta="100%"
        android:toXDelta="0%" />
</set>

slide_out_left.xml

<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:duration="200"
        android:fromXDelta="0%"
        android:toXDelta="-100%" />
</set>

NB/= You wil find more transition animations in the source code download.

Step 5: Create a Navigation Graph

Under the res create a folder known as navigation and inside it create a navigation graph as follows:

nav_graph.xml

We will also reference our animations. We have the animation files in the res/anim folder.

<?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/nav_graph"
    app:startDestination="@id/homeFragment">

    <action
        android:id="@+id/action_global_termsFragment"
        app:destination="@id/termsFragment"
        app:enterAnim="@anim/slide_in_top"
        app:exitAnim="@anim/slide_out_bottom"
        app:popEnterAnim="@anim/slide_in_bottom"
        app:popExitAnim="@anim/slide_out_bottom" />

    <fragment
        android:id="@+id/homeFragment"
        android:name="xyz.teamgravity.navigationcomponent.fragment.HomeFragment"
        android:label="@string/home"
        tools:layout="@layout/fragment_home">
        <action
            android:id="@+id/action_homeFragment_to_loginFragment"
            app:destination="@id/loginFragment"
            app:enterAnim="@anim/slide_in_right"
            app:exitAnim="@anim/slide_out_left"
            app:popEnterAnim="@anim/slide_in_left"
            app:popExitAnim="@anim/slide_out_right" />
    </fragment>
    <fragment
        android:id="@+id/loginFragment"
        android:name="xyz.teamgravity.navigationcomponent.fragment.LoginFragment"
        android:label="@string/login"
        tools:layout="@layout/fragment_login">
        <action
            android:id="@+id/action_loginFragment_to_welcomeFragment"
            app:destination="@id/welcomeFragment"
            app:enterAnim="@anim/slide_in_right"
            app:exitAnim="@anim/slide_out_left"
            app:popEnterAnim="@anim/slide_in_left"
            app:popExitAnim="@anim/slide_out_right" />
        <argument
            android:name="username"
            android:defaultValue="@null"
            app:argType="string"
            app:nullable="true" />
        <deepLink
            android:id="@+id/deepLink"
            app:uri="gravity.xyz/login/{username}" />
    </fragment>
    <fragment
        android:id="@+id/welcomeFragment"
        android:name="xyz.teamgravity.navigationcomponent.fragment.WelcomeFragment"
        android:label="{username}"
        tools:layout="@layout/fragment_welcome" >
        <argument
            android:name="username"
            app:argType="string" />
        <argument
            android:name="password"
            app:argType="string" />
        <action
            android:id="@+id/action_welcomeFragment_to_homeFragment"
            app:destination="@id/homeFragment"
            app:enterAnim="@anim/slide_in_left"
            app:exitAnim="@anim/slide_out_right"
            app:popUpTo="@id/homeFragment"
            app:popUpToInclusive="true" />
    </fragment>
    <fragment
        android:id="@+id/settingsFragment"
        android:name="xyz.teamgravity.navigationcomponent.fragment.SettingsFragment"
        android:label="@string/settings"
        tools:layout="@layout/fragment_settings" />
    <fragment
        android:id="@+id/termsFragment"
        android:name="xyz.teamgravity.navigationcomponent.fragment.TermsFragment"
        android:label="@string/terms_and_conditions"
        tools:layout="@layout/fragment_terms" />
    <fragment
        android:id="@+id/searchFragment"
        android:name="xyz.teamgravity.navigationcomponent.fragment.SearchFragment"
        android:label="@string/search"
        tools:layout="@layout/fragment_search" />
</navigation>

Step 6: Create Options Menu

We will use Options Menu to navigate our fragments, therefore create the following two:

(a). options_menu.xml

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <item
        android:id="@+id/settingsFragment"
        android:menuCategory="secondary"
        android:orderInCategory="1"
        android:title="@string/settings"
        app:showAsAction="never" />

    <item
        android:id="@+id/termsAndConditions"
        android:orderInCategory="2"
        android:title="@string/terms_and_conditions"
        app:showAsAction="never" />
</menu>

(b). navigation_menu.xml

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:id="@+id/homeFragment"
        android:icon="@drawable/ic_home"
        android:title="@string/home" />

    <item
        android:id="@+id/searchFragment"
        android:icon="@drawable/ic_search"
        android:title="@string/search" />
</menu>

Step 7: Design Layouts

We will have the following layouts:

  1. MainActivity layout – activity_main.xml
  2. Home Fragment Layout – fragment_home.xml
  3. Login Fragment Layout – fragment_login.xml
  4. Search Fragment Layout – fragment_search.xml
  5. Settings Fragment Layout – fragment_settings.xml
  6. Terms Fragment Layout – fragment_terms.xml
  7. Welcome Fragment Layout – fragment_welcome.xml

Here is the code for the main activity layout:

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout 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/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".activity.MainActivity">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <androidx.appcompat.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="?attr/colorPrimary"
            android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
            app:layout_constraintTop_toTopOf="parent"
            app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />

        <androidx.fragment.app.FragmentContainerView
            android:id="@+id/nav_host_fragment"
            android:name="androidx.navigation.fragment.NavHostFragment"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            app:defaultNavHost="true"
            app:layout_constraintBottom_toTopOf="@id/bottom_navigation"
            app:layout_constraintTop_toBottomOf="@id/toolbar"
            app:navGraph="@navigation/nav_graph" />

        <com.google.android.material.bottomnavigation.BottomNavigationView
            android:id="@+id/bottom_navigation"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_constraintBottom_toBottomOf="parent"
            app:menu="@menu/navigation_menu" />
    </androidx.constraintlayout.widget.ConstraintLayout>

    <com.google.android.material.navigation.NavigationView
        android:id="@+id/navigation_drawer"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        app:menu="@menu/navigation_menu" />
</androidx.drawerlayout.widget.DrawerLayout>

NB/=You will find more layouts codes in the source code download.

Step 8: Create Fragments

We will have the following fragments:

  1. HomeFragment
  2. LoginFragment
  3. SearchFragment
  4. SettingsFragment
  5. TermsFragment
  6. WelcomeFragment

HomeFragment.kt

Here is the code for this fragment:

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import xyz.teamgravity.navigationcomponent.databinding.FragmentHomeBinding

class HomeFragment : Fragment() {

    private var _binding: FragmentHomeBinding? = null
    private val binding get() = _binding!!

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
        _binding = FragmentHomeBinding.inflate(inflater, container, false)

        return binding.root
    }

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

        // login button
        binding.loginB.setOnClickListener {
            val action = HomeFragmentDirections.actionHomeFragmentToLoginFragment()
            findNavController().navigate(action)
        }
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
}

NB/= You will find the code for other Fragments in the source code download

Step 9: Create MainActivity

Here is the code for the MainActivity:

MainActivity.kt

import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.NavController
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.fragment.findNavController
import androidx.navigation.ui.*
import xyz.teamgravity.navigationcomponent.NavGraphDirections
import xyz.teamgravity.navigationcomponent.R
import xyz.teamgravity.navigationcomponent.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    private lateinit var navController: NavController
    private lateinit var appBarConfiguration: AppBarConfiguration

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        binding.apply {
            setContentView(root)

            // find nav controller
            val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
            navController = navHostFragment.findNavController()

            // hide back button from top level fragments
            appBarConfiguration = AppBarConfiguration(setOf(R.id.homeFragment, R.id.searchFragment), binding.drawerLayout)

            setSupportActionBar(toolbar)
            setupActionBarWithNavController(navController, appBarConfiguration)

            bottomNavigation.setupWithNavController(navController)
            navigationDrawer.setupWithNavController(navController)

            // hide bottom navigation
            navController.addOnDestinationChangedListener { _, destination, _ ->
                when (destination.id) {
                    R.id.termsFragment, R.id.settingsFragment ->
                        bottomNavigation.visibility = View.GONE
                    else ->
                        bottomNavigation.visibility = View.VISIBLE
                }
            }
        }
    }

    // in order to respond back button in toolbar from fragment
    override fun onSupportNavigateUp(): Boolean {
        return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
    }

    override fun onCreateOptionsMenu(menu: Menu?): Boolean {
        menuInflater.inflate(R.menu.options_menu, menu)
        return true
    }

    // to navigate fragments in menu with nav controller
    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        return if (item.itemId == R.id.termsAndConditions) {
            val action = NavGraphDirections.actionGlobalTermsFragment()
            navController.navigate(action)
            true
        } else {
            item.onNavDestinationSelected(navController) || super.onOptionsItemSelected(item)
        }
    }
}

Run

Download code in the link below, build and run.

Reference

Here are the reference links:

Number Link
1. Download Example
2. Follow code author

More Examples

Here are more examples

1. Kotlin Android Custom Navigator Example

A simple example of how to implement Navigation Architecture Component using a custom Navigator.

Step 1: Dependencies

Add the following AndroidX and Navigation Libraries:

    implementation 'androidx.core:core-ktx:0.3'
    implementation 'android.arch.navigation:navigation-fragment-ktx:1.0.0-alpha02'
    implementation 'android.arch.navigation:navigation-ui-ktx:1.0.0-alpha02'

Step 2: Create Navigation rules

In your res directory create a folder called navigation and add the following xml file:

/navigation/navigation.xml

<?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/navigation"
    app:startDestination="@id/firstView">

    <custom_view
        android:id="@+id/firstView"
        android:label="FirstView"
        app:layout="@layout/first_view">
        <action
            android:id="@+id/action_firstView_to_secondView"
            app:destination="@id/secondView" />
    </custom_view>

    <custom_view
        android:id="@+id/secondView"
        android:label="SecondView"
        app:layout="@layout/second_view">
        <action
            android:id="@+id/action_secondView_to_thirdView"
            app:destination="@id/thirdView" />
    </custom_view>

    <custom_view
        android:id="@+id/thirdView"
        android:label="ThirdView"
        app:layout="@layout/third_view" />
</navigation>

Step 3: Design Layouts

Design 4 layouts as follows:

(a). first_view.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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">

    <TextView
        android:id="@+id/text_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="First View"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/button_first"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="32dp"
        android:layout_marginEnd="16dp"
        android:text="Next"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/text_view" />

</android.support.constraint.ConstraintLayout>

(b). second_view.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/text_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Second View"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/button_second"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="32dp"
        android:layout_marginEnd="16dp"
        android:text="Next"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/text_view" />

</android.support.constraint.ConstraintLayout>

(c). third_view.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/text_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Third View"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

(d). third_view.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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">

    <com.star_zero.customnavigation.CustomNavHost
        android:id="@+id/custom_nav_host"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:navGraph="@navigation/navigation" />

</android.support.constraint.ConstraintLayout>

Step 4: Write Code

We will have three classes:

(a). CustomNavHost.kt

package com.star_zero.customnavigation

import android.content.Context
import android.os.Bundle
import android.os.Parcel
import android.os.Parcelable
import android.util.AttributeSet
import android.widget.FrameLayout
import androidx.core.content.withStyledAttributes
import androidx.navigation.NavController
import androidx.navigation.NavHost
import androidx.navigation.Navigation
import androidx.navigation.plusAssign

class CustomNavHost @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr), NavHost {

    private val navController = NavController(context)

    private var graphId = 0

    init {
        Navigation.setViewNavController(this, navController)

        navController.navigatorProvider += CustomNavigator(this)

        context.withStyledAttributes(attrs, R.styleable.CustomNavHost, 0, 0, {
            graphId = getResourceId(R.styleable.CustomNavHost_navGraph, 0)
        })
    }

    override fun getNavController() = navController

    override fun onAttachedToWindow() {
        super.onAttachedToWindow()

        if (navController.graph == null) {
            navController.setGraph(graphId)
        }
    }

    override fun onSaveInstanceState(): Parcelable {
        val superState = super.onSaveInstanceState()
        val ss = SavedState(superState)
        ss.navControllerState = navController.saveState()
        return ss
    }

    override fun onRestoreInstanceState(state: Parcelable) {
        if (state is SavedState) {
            super.onRestoreInstanceState(state.superState)
            navController.restoreState(state.navControllerState)
        } else {
            super.onRestoreInstanceState(state)
        }
    }

    class SavedState: BaseSavedState {
        var navControllerState: Bundle? = null

        constructor(superState: Parcelable): super(superState)

        constructor(source: Parcel): super(source) {
            navControllerState = source.readBundle(javaClass.classLoader)
        }

        override fun writeToParcel(out: Parcel?, flags: Int) {
            super.writeToParcel(out, flags)
            out?.writeBundle(navControllerState)
        }

        companion object CREATOR : Parcelable.Creator<SavedState> {
            override fun createFromParcel(parcel: Parcel): SavedState {
                return SavedState(parcel)
            }

            override fun newArray(size: Int): Array<SavedState?> {
                return arrayOfNulls(size)
            }
        }
    }
}

(b). CustomNavigator.kt

package com.star_zero.customnavigation

import android.content.Context
import android.os.Bundle
import android.support.annotation.LayoutRes
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.core.content.withStyledAttributes
import androidx.core.view.plusAssign
import androidx.navigation.NavDestination
import androidx.navigation.NavOptions
import androidx.navigation.Navigator
import java.util.*

@Navigator.Name("custom_view") 
class CustomNavigator(private val container: ViewGroup) : Navigator<CustomNavigator.Destination>() {

    data class NavLayout(val id: Int, @LayoutRes val layout: Int)

    private val backStack: ArrayDeque<NavLayout> = ArrayDeque()

    override fun navigate(destination: Destination, args: Bundle?, navOptions: NavOptions?) {

        val navLayout = NavLayout(destination.id, destination.layout)
        backStack.push(navLayout)
        replaceView(navLayout.layout)

        dispatchOnNavigatorNavigated(navLayout.id, BACK_STACK_DESTINATION_ADDED)
    }

    override fun createDestination() = Destination(this)

    override fun popBackStack(): Boolean {

        return if (backStack.size < 2) {
            false
        } else {
            backStack.pop()
            val navLayout = backStack.peek()
            replaceView(navLayout.layout)
            dispatchOnNavigatorNavigated(navLayout.id, BACK_STACK_DESTINATION_POPPED)
            true
        }
    }

    private fun replaceView(@LayoutRes layout: Int) {
        container.removeAllViews()
        val view = LayoutInflater.from(container.context).inflate(layout, container, false)
        container += view
    }

    class Destination(navigator: Navigator<out NavDestination>) : NavDestination(navigator) {

        @LayoutRes
        var layout: Int = 0

        override fun onInflate(context: Context, attrs: AttributeSet) {
            super.onInflate(context, attrs)

            context.withStyledAttributes(attrs, R.styleable.CustomNavigator, 0, 0, {
                layout = getResourceId(R.styleable.CustomNavigator_layout, 0)
            })
        }
    }

    override fun onSaveState(): Bundle? {
        val bundle = Bundle()
        val id = IntArray(backStack.size)
        val layout = IntArray(backStack.size)

        backStack.forEachIndexed({ index, navLayout ->
            id[index] = navLayout.id
            layout[index] = navLayout.layout
        })

        bundle.putIntArray("id", id)
        bundle.putIntArray("layout", layout)

        return bundle
    }

    override fun onRestoreState(savedState: Bundle) {
        val id = savedState.getIntArray("id")
        val layout = savedState.getIntArray("layout")

        backStack.clear()
        id.forEachIndexed { index, _ ->
            backStack.add(NavLayout(id[index], layout[index]))
        }

        replaceView(backStack.peek().layout)
        dispatchOnNavigatorNavigated(backStack.peek().id, BACK_STACK_DESTINATION_ADDED)
    }

}

(c). MainActivity.kt

package com.star_zero.customnavigation

import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.view.View
import androidx.navigation.NavController
import androidx.navigation.findNavController
import androidx.navigation.ui.NavigationUI.setupActionBarWithNavController

class MainActivity : AppCompatActivity() {

    private lateinit var navController: NavController

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.main_activity)

        navController = findNavController(R.id.custom_nav_host)

        navController.addOnNavigatedListener { controller, destination ->

            when (destination.id) {
                R.id.firstView -> {
                    findViewById<View>(R.id.button_first).setOnClickListener {
                        controller.navigate(R.id.action_firstView_to_secondView)
                    }
                }
                R.id.secondView -> {
                    findViewById<View>(R.id.button_second).setOnClickListener {
                        controller.navigate(R.id.action_secondView_to_thirdView)
                    }
                }
            }
        }

        setupActionBarWithNavController(this, navController)
    }

    override fun onSupportNavigateUp() = navController.navigateUp()

    override fun onBackPressed() {
        if (!navController.popBackStack()) {
            super.onBackPressed()
        }
    }
}

Reference

  • Download full code here.
  • Follow code author here.
[Read Individually.](https://android.camposha.info/en/example/customnavigation/)

2. Kotlin Android Navigation Architecture Component + DialogFragment

How to implement Jetpack Navigation Architecture Component with a DialogFragment in Kotlin.

Step 1: Dependencies

Include the following as part of your dependency specifications:


    implementation 'com.jakewharton.timber:timber:4.7.1'
    implementation "androidx.lifecycle:lifecycle-extensions:2.0.0"
    implementation "android.arch.navigation:navigation-fragment-ktx:1.0.0-alpha06"
    implementation "android.arch.navigation:navigation-ui-ktx:1.0.0-alpha06"

Step 2: Create Navigation Rules

Create a folder named navigation inside the res directory and add the following file:

/navigation/navigation.xml

<?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"
    android:id="@+id/navigation"
    app:startDestination="@id/mainFragment">

    <fragment
        android:id="@+id/mainFragment"
        android:name="com.example.navigationdialog.MainFragment"
        android:label="MainFragment">
        <action
            android:id="@+id/action_mainFragment_to_secondFragment"
            app:destination="@id/secondFragment" />
    </fragment>

    <fragment
        android:id="@+id/secondFragment"
        android:name="com.example.navigationdialog.SecondFragment"
        android:label="SecondFragment" />

    <dialog_fragment
        android:id="@+id/simpleDialog"
        android:name="com.example.navigationdialog.SimpleDialog"
        android:label="SimpleDialog">
        <argument
            android:name="title"
            app:argType="string" />
        <argument
            android:name="message"
            app:argType="string" />
        <argument
            android:name="requestCode"
            app:argType="integer" />
    </dialog_fragment>
    <action
        android:id="@+id/action_global_simpleDialog"
        app:destination="@id/simpleDialog" />
</navigation>

Step 3: Design Layouts

Design three layouts: two Fragments and One Activity:

(a). fragment_second.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <Button
            android:id="@+id/button_back"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:layout_marginTop="16dp"
            android:layout_marginEnd="16dp"
            android:layout_marginBottom="16dp"
            android:text="Back"
            app:layout_constraintBottom_toTopOf="@+id/button_dialog"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintVertical_chainStyle="packed" />

        <Button
            android:id="@+id/button_dialog"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:layout_marginEnd="16dp"
            android:layout_marginBottom="16dp"
            android:text="Show Dialog Second"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/button_back" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

(b). fragment_main.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <Button
            android:id="@+id/button_next"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="16dp"
            android:layout_marginEnd="8dp"
            android:layout_marginBottom="16dp"
            android:text="Next"
            app:layout_constraintBottom_toTopOf="@+id/button_dialog"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintVertical_chainStyle="packed" />

        <Button
            android:id="@+id/button_dialog"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"
            android:layout_marginEnd="16dp"
            android:layout_marginBottom="16dp"
            android:text="Show Dialog Main"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/button_next" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

(c). activity_main.xml

Place our NavHostFragment here:

<?xml version="1.0" encoding="utf-8"?>
<layout 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">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

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

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

Step 4: Initialize Timber

Do this initialization in the App class:

App.kt

package com.example.navigationdialog

import android.app.Application
import timber.log.Timber

class App : Application() {

    override fun onCreate() {
        super.onCreate()

        Timber.plant(Timber.DebugTree())
    }
}

Step 5: Create DialogFragment

Create a DialogFragment by extending the DialogFragment class:

SimpleDialog.kt

package com.example.navigationdialog

import android.app.Dialog
import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment

class SimpleDialog : DialogFragment() {
    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        val args = SimpleDialogArgs.fromBundle(arguments)

        return AlertDialog.Builder(requireActivity())
                .setTitle(args.title)
                .setMessage(args.message)
                .setPositiveButton("OK") { _, which ->
                    sendResult(which)
                }
                .create()
    }

    private fun sendResult(which: Int) {
        targetFragment?.onActivityResult(targetRequestCode, which, null)
    }
}

Step 6: Create Fragments

Create two Fragments as follows:

(a). SecondFragment.kt

package com.example.navigationdialog

import android.content.DialogInterface
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.navigation.findNavController
import com.example.navigationdialog.databinding.FragmentSecondBinding
import timber.log.Timber

class SecondFragment : Fragment() {

    companion object {
        private const val DIALOG_REQUEST_CODE = 1
    }

    private lateinit var binding: FragmentSecondBinding

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        binding = FragmentSecondBinding.inflate(inflater, container, false)
        return binding.root
    }

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

        binding.buttonBack.setOnClickListener { view ->
            view.findNavController().navigateUp()
        }

        binding.buttonDialog.setOnClickListener { view ->
            val action = SimpleDialogDirections.actionGlobalSimpleDialog(
                "Sample Title2",
                "Sample Message2",
                DIALOG_REQUEST_CODE
            )
            view.findNavController().navigate(action)
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        if (DIALOG_REQUEST_CODE == requestCode && DialogInterface.BUTTON_POSITIVE == resultCode) {
            Timber.d("Click button positive")
        }
    }
}

(b). MainFragment.kt

package com.example.navigationdialog

import android.content.DialogInterface
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.navigation.findNavController
import com.example.navigationdialog.databinding.FragmentMainBinding
import timber.log.Timber

class MainFragment : Fragment() {

    companion object {
        private const val DIALOG_REQUEST_CODE = 1
    }

    private lateinit var binding: FragmentMainBinding

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        binding = FragmentMainBinding.inflate(inflater, container, false)
        return binding.root
    }

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

        binding.buttonNext.setOnClickListener { view ->
            view.findNavController().navigate(R.id.action_mainFragment_to_secondFragment)
        }

        binding.buttonDialog.setOnClickListener { view ->
            val action = SimpleDialogDirections.actionGlobalSimpleDialog(
                "Sample Title1",
                "Sample Message1",
                DIALOG_REQUEST_CODE
            )
            view.findNavController().navigate(action)
        }
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        if (DIALOG_REQUEST_CODE == requestCode && DialogInterface.BUTTON_POSITIVE == resultCode) {
            Timber.d("Click button positive")
        }
    }
}

Step 7: Create DialogNavigator

Create a custom DialogNavigator class as follows:

package com.example.navigationdialog

import android.content.Context
import android.os.Bundle
import android.util.AttributeSet
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.FragmentManager
import androidx.navigation.NavDestination
import androidx.navigation.NavOptions
import androidx.navigation.Navigator

@Navigator.Name("dialog_fragment") 
class DialogNavigator(
    private val fragmentManager: FragmentManager
) : Navigator<DialogNavigator.Destination>() {

    companion object {
        private const val TAG = "dialog"
    }

    override fun navigate(destination: Destination, args: Bundle?, navOptions: NavOptions?, navigatorExtras: Extras?) {
        val fragment = destination.createFragment(args)

        fragment.setTargetFragment(fragmentManager.primaryNavigationFragment, SimpleDialogArgs.fromBundle(args).requestCode)

        fragment.show(fragmentManager, TAG)

        dispatchOnNavigatorNavigated(destination.id, BACK_STACK_UNCHANGED)
    }

    override fun createDestination(): Destination {
        return Destination(this)
    }

    override fun popBackStack(): Boolean {
        return true
    }

    class Destination(navigator: Navigator<out NavDestination>) : NavDestination(navigator) {

        private var fragmentClass: Class<out DialogFragment>? = null

        override fun onInflate(context: Context, attrs: AttributeSet) {
            super.onInflate(context, attrs)

            val a = context.resources.obtainAttributes(attrs, R.styleable.FragmentNavigator)
            a.getString(R.styleable.FragmentNavigator_android_name)?.let { className ->
                fragmentClass = parseClassFromName(context, className, DialogFragment::class.java)
            }
            a.recycle()
        }

        fun createFragment(args: Bundle?): DialogFragment {
            val fragment = fragmentClass?.newInstance()
                ?: throw IllegalStateException("fragment class not set")

            args?.let {
                fragment.arguments = it
            }
            return fragment
        }
    }
}

Step 8: MainActivity

Write your MainActivity code as follows:

MainActivity.kt

package com.example.navigationdialog

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.findNavController
import androidx.navigation.plusAssign
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val navController = findNavController(R.id.nav_host)

        val dialogNavigator = DialogNavigator(nav_host.childFragmentManager)
        navController.navigatorProvider += dialogNavigator

        val graph = navController.navInflater.inflate(R.navigation.navigation)
        navController.graph = graph
    }
}

Reference

  • Download full code here.
  • Follow code author here.
[Read Individually.](https://android.camposha.info/en/example/navigation-dialogfragment/)

3. Kotlin Android Navigation Architecture Component + SharedElement Transition

How to implement Jetpack Navigation Architecture Component with SharedElement Transition in Kotlin.

Step 1: Dependencies

Include the following in your app/build.gradle under the dependencies closure:

    implementation 'androidx.cardview:cardview:1.0.0'
    implementation "androidx.lifecycle:lifecycle-extensions:2.0.0"
    implementation 'com.github.bumptech.glide:glide:4.8.0'
    kapt 'com.github.bumptech.glide:compiler:4.8.0'
    implementation "android.arch.navigation:navigation-fragment-ktx:1.0.0-alpha08"
    implementation "android.arch.navigation:navigation-ui-ktx:1.0.0-alpha08"

Step 2: Create Navigation Rules

Create a folder called navigation inside the res directory and add the following rules:

/navigation/navigation.xml

<?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"
    android:id="@+id/navigation"
    app:startDestination="@id/gridFragment">

    <fragment
        android:id="@+id/gridFragment"
        android:name="com.example.withnavigation.sharedelementsample.GridFragment"
        android:label="GridFragment">
        <action
            android:id="@+id/action_gridFragment_to_imageFragment"
            app:destination="@id/imageFragment" />
        <action
            android:id="@+id/action_gridFragment_to_imageActivity"
            app:destination="@id/imageActivity" />
    </fragment>
    <fragment
        android:id="@+id/imageFragment"
        android:name="com.example.withnavigation.sharedelementsample.ImageFragment"
        android:label="ImageFragment">
        <argument
            android:name="imageURL"
            app:argType="string" />
    </fragment>
    <activity
        android:id="@+id/imageActivity"
        android:name="com.example.withnavigation.sharedelementsample.ImageActivity"
        android:label="ImageActivity">
        <argument
            android:name="image_url"
            app:argType="string" />
    </activity>
</navigation>

Step 3: Create SharedElement Transition

In your res directory create a folder called transition and add the following animation code:

/transition/change_image_transform.xml

<?xml version="1.0" encoding="utf-8"?>
<transitionSet xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="300"
    android:transitionOrdering="together">
    <changeBounds />
    <changeTransform />
    <changeImageTransform />
</transitionSet>

Step 4: Design Layouts

Design your xml layouts as follows:

(a). item_image.xml

Include an ImageView inside a cardView. Image URL will be passed over using data binding:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>

        <variable
            name="imageURL"
            type="String" />
    </data>

    <androidx.cardview.widget.CardView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:clickable="true"
        android:focusable="true"
        app:cardCornerRadius="4dp"
        app:cardElevation="4dp"
        app:cardUseCompatPadding="true">

        <ImageView
            android:id="@+id/image"
            android:layout_width="200dp"
            android:layout_height="200dp"
            android:scaleType="centerCrop"
            app:imageURL="@{imageURL}" />
    </androidx.cardview.widget.CardView>
</layout>

(b). fragment_image.xml

Include an ImageView inside a ConstraintLayout:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <ImageView
            android:id="@+id/image"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_marginStart="16dp"
            android:layout_marginTop="16dp"
            android:layout_marginEnd="16dp"
            android:layout_marginBottom="16dp"
            android:scaleType="fitCenter"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

(c). fragment_grid.xml

Include a RecyclerView inside a ConstraintLayout:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/content"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recycler"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

(d). activity_image.xml

Add an ImageView that will render the Image passed to this activity:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <ImageView
            android:id="@+id/image"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:layout_marginStart="16dp"
            android:layout_marginTop="16dp"
            android:layout_marginEnd="16dp"
            android:layout_marginBottom="16dp"
            android:scaleType="fitCenter"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

(e). activity_main.xml

Include a NavHostFragment here:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <fragment
        android:id="@+id/nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:defaultNavHost="true"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navGraph="@navigation/navigation" />
</androidx.constraintlayout.widget.ConstraintLayout>

Step 5: Create Utils

Here are helper classes to load images via Glide as well as bind them through data binding:

(a). GlideModule.kt

package com.example.withnavigation.sharedelementsample.util

import com.bumptech.glide.annotation.GlideModule
import com.bumptech.glide.module.AppGlideModule

@GlideModule
class GlideModule : AppGlideModule()

(b). BindingExtensions.kt

How to load image via Glide into an ImageView through data binding:

package com.example.withnavigation.sharedelementsample.util

import android.widget.ImageView
import androidx.databinding.BindingAdapter

@BindingAdapter("imageURL")
fun ImageView.setImageURL(url: String?) {
    if (url == null) {
        setImageDrawable(null)
        return
    }

    GlideApp.with(this)
            .load(url)
            .into(this)
}

Step 6: Create Adapter

Create a recyclerview adapter to set the images to the recyclerview:

(a). GridAdapter.kt

package com.example.withnavigation.sharedelementsample

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.example.withnavigation.sharedelementsample.databinding.ItemImageBinding

class GridAdapter(
        private val clickItem: (View) -> Unit
) : ListAdapter<String, GridAdapter.ViewHolder>(DIFF_CALLBACK) {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val binding = ItemImageBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return ViewHolder(binding)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.bind(getItem(position), clickItem)
    }

    companion object {
        private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<String>() {
            override fun areItemsTheSame(oldItem: String, newItem: String): Boolean {
                return oldItem == newItem
            }

            override fun areContentsTheSame(oldItem: String, newItem: String): Boolean {
                return oldItem == newItem
            }
        }
    }

    class ViewHolder(private val binding: ItemImageBinding) : RecyclerView.ViewHolder(binding.root) {
        fun bind(imageURL: String, clickItem: (View) -> Unit) {
            binding.image.transitionName = imageURL // Use image url for transitionName
            binding.imageURL = imageURL
            binding.image.setOnClickListener {
                clickItem(binding.image)
            }
            binding.executePendingBindings()
        }
    }
}

Step 7: Create Fragments

Create the following two fragments:

(a). GridFragment.kt

package com.example.withnavigation.sharedelementsample

import android.os.Bundle
import android.transition.TransitionInflater
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.app.ActivityOptionsCompat
import androidx.fragment.app.Fragment
import androidx.navigation.ActivityNavigator
import androidx.navigation.fragment.FragmentNavigatorExtras
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.GridLayoutManager
import com.example.withnavigation.sharedelementsample.databinding.FragmentGridBinding

class GridFragment : Fragment() {

    private lateinit var binding: FragmentGridBinding

    // Show Activity
    private val adapter = GridAdapter(this::clickItemActivity)

    // Show Fragment
//    private val adapter = GridAdapter(this::clickItemFragment)

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        binding = FragmentGridBinding.inflate(inflater, container, false)
        return binding.root
    }

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

        binding.recycler.layoutManager = GridLayoutManager(requireContext(), 2)
        binding.recycler.adapter = adapter

        adapter.submitList(listOf(
                "https://images.pexels.com/photos/1383397/pexels-photo-1383397.jpeg?w=500",
                "https://images.pexels.com/photos/132694/pexels-photo-132694.jpeg?w=500",
                "https://images.pexels.com/photos/1423455/pexels-photo-1423455.jpeg?w=500",
                "https://images.pexels.com/photos/1251175/pexels-photo-1251175.jpeg",
                "https://images.pexels.com/photos/209037/pexels-photo-209037.jpeg",
                "https://images.pexels.com/photos/291528/pexels-photo-291528.jpeg",
                "https://images.pexels.com/photos/302478/pexels-photo-302478.jpeg",
                "https://images.pexels.com/photos/982612/pexels-photo-982612.jpeg"
        ))
    }

    private fun clickItemActivity(view: View) {
        val action = GridFragmentDirections.actionGridFragmentToImageActivity(view.transitionName) // transitionName == imageURL

        val options = ActivityOptionsCompat.makeSceneTransitionAnimation(
                requireActivity(),
                view,
                view.transitionName
        )
        val extras = ActivityNavigator.Extras.Builder().setActivityOptions(options).build()

        findNavController().navigate(action, extras)
    }

    private fun clickItemFragment(view: View) {
        exitTransition = TransitionInflater.from(requireContext()).inflateTransition(R.transition.change_image_transform)
        val action = GridFragmentDirections.actionGridFragmentToImageFragment(view.transitionName) // transitionName == imageURL
        val extra = FragmentNavigatorExtras(view to view.transitionName)
        findNavController().navigate(action, extra)
    }
}

(b). ImageFragment.kt

package com.example.withnavigation.sharedelementsample

import android.graphics.drawable.Drawable
import android.os.Bundle
import android.transition.TransitionInflater
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target
import com.example.withnavigation.sharedelementsample.databinding.FragmentImageBinding
import com.example.withnavigation.sharedelementsample.util.GlideApp

class ImageFragment : Fragment() {

    private lateinit var binding: FragmentImageBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        sharedElementEnterTransition = TransitionInflater.from(requireContext()).inflateTransition(R.transition.change_image_transform)
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        binding = FragmentImageBinding.inflate(inflater, container, false)
        return binding.root
    }

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

        val imageURL = ImageFragmentArgs.fromBundle(arguments).imageURL
        binding.image.transitionName = imageURL

        GlideApp.with(this)
                .load(imageURL)
                .dontAnimate()
                .listener(object : RequestListener<Drawable> {
                    override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Drawable>?, isFirstResource: Boolean): Boolean {
                        startPostponedEnterTransition()
                        return false
                    }

                    override fun onResourceReady(resource: Drawable?, model: Any?, target: Target<Drawable>?, dataSource: DataSource?, isFirstResource: Boolean): Boolean {
                        startPostponedEnterTransition()
                        return false
                    }
                })
                .into(binding.image)
    }
}

Step 8: Create Activities

We will have two activities;

(a). ImageActivity.kt

Load image via Glide:

package com.example.withnavigation.sharedelementsample

import android.graphics.drawable.Drawable
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.target.Target
import com.example.withnavigation.sharedelementsample.databinding.ActivityImageBinding
import com.example.withnavigation.sharedelementsample.util.GlideApp

class ImageActivity : AppCompatActivity() {

    private lateinit var binding: ActivityImageBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_image)

        // prevent blink in status bar
        window.enterTransition = null
        window.exitTransition = null

        postponeEnterTransition()

        val imageURL = ImageActivityArgs.fromBundle(intent.extras).imageUrl

        binding.image.transitionName = imageURL

        GlideApp.with(this)
                .load(imageURL)
                .dontAnimate()
                .listener(object : RequestListener<Drawable> {
                    override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Drawable>?, isFirstResource: Boolean): Boolean {
                        startPostponedEnterTransition()
                        return false
                    }

                    override fun onResourceReady(resource: Drawable?, model: Any?, target: Target<Drawable>?, dataSource: DataSource?, isFirstResource: Boolean): Boolean {
                        startPostponedEnterTransition()
                        return false
                    }
                })
                .into(binding.image)
    }
}

(b). MainActivity.kt

Here is the code for MainActivity:

package com.example.withnavigation.sharedelementsample

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.findNavController

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }

    override fun onSupportNavigateUp() = findNavController(R.id.nav_host_fragment).navigateUp()
}

Reference

  • Download full code here.
  • Follow code author here.
[Read Individually.](https://android.camposha.info/en/example/navigation-sharedelement/)

4. Kotlin Android Navigation Component Example

Learn Jetpack Navigation Component using this example. This example is written in Kotlin.

This example will comprise the following files:

  • AFragment.kt
  • BFragment.kt
  • CFragment.kt
  • MainActivity.kt

Step 1: Create Project

  1. Open your AndroidStudio IDE.
  2. Go to File-->New-->Project to create a new project.

Step 2: Add Dependencies

In your app/build.gradle add dependencies as shown below.

First apply the following plugins including the 'androidx.navigation.safeargs:

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'androidx.navigation.safeargs'

Among your dependencies include the following: navigation-fragment and navigation-ui:


dependencies {
//..
    implementation 'android.arch.navigation:navigation-fragment:1.0.0-alpha01'
    implementation 'android.arch.navigation:navigation-ui:1.0.0-alpha01'
}

Step 3: Create Navigation Graph

Create a folder named navigation inside the res directory and add the following code;

/res/navigation/main_navigation.xml

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    app:startDestination="@id/AFragment">

    <fragment
        android:id="@+id/AFragment"
        android:name="com.numero.navigation_example.AFragment"
        android:label="fragment_a"
        tools:layout="@layout/fragment_a" >

    </fragment>

    <fragment
        android:id="@+id/BFragment"
        android:name="com.numero.navigation_example.BFragment"
        android:label="fragment_b"
        tools:layout="@layout/fragment_b" >

    </fragment>

    <fragment
        android:id="@+id/CFragment"
        android:name="com.numero.navigation_example.CFragment"
        android:label="fragment_c"
        tools:layout="@layout/fragment_c" >

    </fragment>

</navigation>

Step 4: Design Layouts

Design our layouts as follows:

*(a). activity_main.xml

Create a file named activity_main.xml and design it as follows:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <fragment
        android:id="@+id/main_nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:defaultNavHost="true"
        app:layout_constraintBottom_toTopOf="@+id/navigation"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navGraph="@navigation/main_navigation" />

    <android.support.design.widget.BottomNavigationView
        android:id="@+id/navigation"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="0dp"
        android:layout_marginEnd="0dp"
        android:background="?android:attr/windowBackground"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:menu="@menu/navigation" />

</android.support.constraint.ConstraintLayout>

*(b). fragment_a.xml

Create a file named fragment_a.xml and design it as follows:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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=".AFragment">

    <!-- TODO: Update blank fragment layout -->
    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="@string/hello_blank_fragment" />

</FrameLayout>

(c). fragment_b.xml

Create a file named fragment_b.xml and design it as follows:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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=".BFragment">

    <!-- TODO: Update blank fragment layout -->
    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="@string/hello_blank_fragment" />

</FrameLayout>

(d). fragment_c.xml

Create a file named fragment_c.xml and design it as follows:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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=".CFragment">

    <!-- TODO: Update blank fragment layout -->
    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="@string/hello_blank_fragment" />

</FrameLayout>

Step 5: Write Code

Write Code as follows:

(a). AFragment.kt

Create a file named AFragment.kt

Here is the full code

package com.numero.navigation_example

import android.content.Context
import android.net.Uri
import android.os.Bundle
import android.support.v4.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup

// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private const val ARG_PARAM1 = "param1"
private const val ARG_PARAM2 = "param2"

/**
 * A simple [Fragment] subclass.
 * Activities that contain this fragment must implement the
 * [AFragment.OnFragmentInteractionListener] interface
 * to handle interaction events.
 * Use the [AFragment.newInstance] factory method to
 * create an instance of this fragment.
 *
 */
class AFragment : Fragment() {
    // TODO: Rename and change types of parameters
    private var param1: String? = null
    private var param2: String? = null
    private var listener: OnFragmentInteractionListener? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        arguments?.let {
            param1 = it.getString(ARG_PARAM1)
            param2 = it.getString(ARG_PARAM2)
        }
    }

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

    // TODO: Rename method, update argument and hook method into UI event
    fun onButtonPressed(uri: Uri) {
        listener?.onFragmentInteraction(uri)
    }

    override fun onAttach(context: Context) {
        super.onAttach(context)
        if (context is OnFragmentInteractionListener) {
            listener = context
        }
    }

    override fun onDetach() {
        super.onDetach()
        listener = null
    }

    /**
     * This interface must be implemented by activities that contain this
     * fragment to allow an interaction in this fragment to be communicated
     * to the activity and potentially other fragments contained in that
     * activity.
     *
     *
     * See the Android Training lesson [Communicating with Other Fragments]
     * (http://developer.android.com/training/basics/fragments/communicating.html)
     * for more information.
     */
    interface OnFragmentInteractionListener {
        // TODO: Update argument type and name
        fun onFragmentInteraction(uri: Uri)
    }

    companion object {
        /**
         * Use this factory method to create a new instance of
         * this fragment using the provided parameters.
         *
         * @param param1 Parameter 1.
         * @param param2 Parameter 2.
         * @return A new instance of fragment AFragment.
         */
        // TODO: Rename and change types and number of parameters
        @JvmStatic
        fun newInstance(param1: String, param2: String) =
                AFragment().apply {
                    arguments = Bundle().apply {
                        putString(ARG_PARAM1, param1)
                        putString(ARG_PARAM2, param2)
                    }
                }
    }
}

(b). BFragment.kt

Create a file named BFragment.kt. This is a simple Fragment subclass. Activities that contain this fragment must implement the BFragment.OnFragmentInteractionListener interface to handle interaction events. Use the BFragment.newInstance factory method to create an instance of this fragment.

Here is the full code

package com.numero.navigation_example

import android.content.Context
import android.net.Uri
import android.os.Bundle
import android.support.v4.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup

// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private const val ARG_PARAM1 = "param1"
private const val ARG_PARAM2 = "param2"

class BFragment : Fragment() {
    // TODO: Rename and change types of parameters
    private var param1: String? = null
    private var param2: String? = null
    private var listener: OnFragmentInteractionListener? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        arguments?.let {
            param1 = it.getString(ARG_PARAM1)
            param2 = it.getString(ARG_PARAM2)
        }
    }

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

    // TODO: Rename method, update argument and hook method into UI event
    fun onButtonPressed(uri: Uri) {
        listener?.onFragmentInteraction(uri)
    }

    override fun onAttach(context: Context) {
        super.onAttach(context)
        if (context is OnFragmentInteractionListener) {
            listener = context
        }
    }

    override fun onDetach() {
        super.onDetach()
        listener = null
    }

    /**
     * This interface must be implemented by activities that contain this
     * fragment to allow an interaction in this fragment to be communicated
     * to the activity and potentially other fragments contained in that
     * activity.
     *
     *
     * See the Android Training lesson [Communicating with Other Fragments]
     * (http://developer.android.com/training/basics/fragments/communicating.html)
     * for more information.
     */
    interface OnFragmentInteractionListener {
        // TODO: Update argument type and name
        fun onFragmentInteraction(uri: Uri)
    }

    companion object {
        /**
         * Use this factory method to create a new instance of
         * this fragment using the provided parameters.
         *
         * @param param1 Parameter 1.
         * @param param2 Parameter 2.
         * @return A new instance of fragment BFragment.
         */
        // TODO: Rename and change types and number of parameters
        @JvmStatic
        fun newInstance(param1: String, param2: String) =
                BFragment().apply {
                    arguments = Bundle().apply {
                        putString(ARG_PARAM1, param1)
                        putString(ARG_PARAM2, param2)
                    }
                }
    }
}

(c). CFragment.kt

Create a file named CFragment.kt

Here is the full code

package com.numero.navigation_example

import android.content.Context
import android.net.Uri
import android.os.Bundle
import android.support.v4.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup

// TODO: Rename parameter arguments, choose names that match
// the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
private const val ARG_PARAM1 = "param1"
private const val ARG_PARAM2 = "param2"

/**
 * A simple [Fragment] subclass.
 * Activities that contain this fragment must implement the
 * [CFragment.OnFragmentInteractionListener] interface
 * to handle interaction events.
 * Use the [CFragment.newInstance] factory method to
 * create an instance of this fragment.
 *
 */
class CFragment : Fragment() {
    // TODO: Rename and change types of parameters
    private var param1: String? = null
    private var param2: String? = null
    private var listener: OnFragmentInteractionListener? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        arguments?.let {
            param1 = it.getString(ARG_PARAM1)
            param2 = it.getString(ARG_PARAM2)
        }
    }

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

    // TODO: Rename method, update argument and hook method into UI event
    fun onButtonPressed(uri: Uri) {
        listener?.onFragmentInteraction(uri)
    }

    override fun onAttach(context: Context) {
        super.onAttach(context)
        if (context is OnFragmentInteractionListener) {
            listener = context
        }
    }

    override fun onDetach() {
        super.onDetach()
        listener = null
    }

    /**
     * This interface must be implemented by activities that contain this
     * fragment to allow an interaction in this fragment to be communicated
     * to the activity and potentially other fragments contained in that
     * activity.
     *
     *
     * See the Android Training lesson [Communicating with Other Fragments]
     * (http://developer.android.com/training/basics/fragments/communicating.html)
     * for more information.
     */
    interface OnFragmentInteractionListener {
        // TODO: Update argument type and name
        fun onFragmentInteraction(uri: Uri)
    }

    companion object {
        /**
         * Use this factory method to create a new instance of
         * this fragment using the provided parameters.
         *
         * @param param1 Parameter 1.
         * @param param2 Parameter 2.
         * @return A new instance of fragment CFragment.
         */
        // TODO: Rename and change types and number of parameters
        @JvmStatic
        fun newInstance(param1: String, param2: String) =
                CFragment().apply {
                    arguments = Bundle().apply {
                        putString(ARG_PARAM1, param1)
                        putString(ARG_PARAM2, param2)
                    }
                }
    }
}

(d). MainActivity.kt

Create a file named MainActivity.kt

Here is the full code

package com.numero.navigation_example

import android.os.Bundle
import android.support.design.widget.BottomNavigationView
import android.support.design.widget.NavigationView
import android.support.v7.app.AppCompatActivity
import android.view.MenuItem
import androidx.navigation.NavController
import androidx.navigation.Navigation
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.NavigationUI
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val host: NavHostFragment = supportFragmentManager.findFragmentById(R.id.main_nav_host_fragment) as NavHostFragment? ?: return

        val navController = host.navController
        setupActionBar(navController)

        setupBottomNavMenu(navController)
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        return NavigationUI.onNavDestinationSelected(item, Navigation.findNavController(this, R.id.main_nav_host_fragment)) || super.onOptionsItemSelected(item)
    }

    private fun setupBottomNavMenu(navController: NavController) {
        navigation?.let {
            NavigationUI.setupWithNavController(it, navController)
        }
    }

    private fun setupActionBar(navController: NavController) {
        NavigationUI.setupActionBarWithNavController(this, navController)
    }

    override fun onSupportNavigateUp(): Boolean {
        return NavigationUI.navigateUp(null, Navigation.findNavController(this, R.id.main_nav_host_fragment))
    }
}

Run

Simply copy the source code into your Android Project,Build and Run.

Reference

  1. Download code or Browse here.
  2. Follow code author.
[Read Individually.](https://android.camposha.info/en/example/navigationexample-android/)

5. Kotlin Android Navigation Architecture Component - Maintain Fragment State

How to implement Jetpack Navigation and maintain Fragment state.

Here is a screenshot:

Step 1: Dependencies

Among your dependencies in the app/build.gradleadd the following:

    implementation "androidx.navigation:navigation-fragment-ktx:2.1.0"
    implementation "androidx.navigation:navigation-ui-ktx:2.1.0"

Enable Data Binding and Java8 as well:

    dataBinding {
        enabled true
    }
    kotlinOptions {
        jvmTarget = "1.8"
    }

Step 2: Create Navigation Graph

Create a folder named navigation inside the res directory and add the following:

(a). nav_graph.xml

<?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"
    android:id="@+id/nav_graph"
    app:startDestination="@id/navigation_notifications"> <!-- Change start tab -->

    <keep_state_fragment
        android:id="@+id/navigation_home"
        android:name="com.star_zero.navigation_keep_fragment_sample.HomeContainerFragment"
        android:label="HomeContainerFragment" />
    <keep_state_fragment
        android:id="@+id/navigation_dashboard"
        android:name="com.star_zero.navigation_keep_fragment_sample.DashboardFragment"
        android:label="DashboardFragment" />
    <keep_state_fragment
        android:id="@+id/navigation_notifications"
        android:name="com.star_zero.navigation_keep_fragment_sample.NotificationsFragment"
        android:label="NotificationsFragment" />
</navigation>

(b). nav_graph_home.xml

<?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"
    android:id="@+id/nav_graph_home"
    app:startDestination="@id/home">

    <fragment
        android:id="@+id/home"
        android:name="com.star_zero.navigation_keep_fragment_sample.HomeFragment"
        android:label="HomeFragment">
        <action
            android:id="@+id/action_home_to_detail"
            app:destination="@id/detail" />
    </fragment>
    <fragment
        android:id="@+id/detail"
        android:name="com.star_zero.navigation_keep_fragment_sample.DetailFragment"
        android:label="DetailFragment" />
</navigation>

Step 3: Create BottomNavigation Menus

Create a folder named menu inside the res directory and add the following menu items:

navigation.xml

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:id="@+id/navigation_home"
        android:icon="@drawable/ic_home_black_24dp"
        android:title="@string/title_home" />

    <item
        android:id="@+id/navigation_dashboard"
        android:icon="@drawable/ic_dashboard_black_24dp"
        android:title="@string/title_dashboard" />

    <item
        android:id="@+id/navigation_notifications"
        android:icon="@drawable/ic_notifications_black_24dp"
        android:title="@string/title_notifications" />

</menu>

Step 4: Design layouts

We will have the following 6 layouts:

(a). fragment_notifications.xml

Add a simple TextView:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <TextView
            android:id="@+id/text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="8dp"
            android:layout_marginTop="8dp"
            android:layout_marginEnd="8dp"
            android:layout_marginBottom="8dp"
            android:text="Notifications"
            android:textSize="24sp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

(b). fragment_home_container.xml

Add a NavHostFragment:

<?xml version="1.0" encoding="utf-8"?>
<layout 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">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/container"
        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="0dp"
            android:layout_height="0dp"
            app:defaultNavHost="true"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:navGraph="@navigation/nav_graph_home" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

(c). fragment_dashboard.xml

Add a recyclerview here:

<?xml version="1.0" encoding="utf-8"?>
<layout 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">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recycler"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

(d). fragment_home.xml

<?xml version="1.0" encoding="utf-8"?>
<layout 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">

    <data>

        <variable
            name="handler"
            type="com.star_zero.navigation_keep_fragment_sample.HomeFragment" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <TextView
            android:id="@+id/text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="8dp"
            android:layout_marginTop="8dp"
            android:layout_marginEnd="8dp"
            android:layout_marginBottom="8dp"
            android:text="Home"
            android:textSize="24sp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <Button
            android:id="@+id/button_detail"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="8dp"
            android:layout_marginTop="8dp"
            android:layout_marginEnd="8dp"
            android:onClick="@{handler::navigateToDetail}"
            android:text="Navigate to Detail"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/text" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

(e). fragment_detail.xml

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <TextView
            android:id="@+id/text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginStart="8dp"
            android:layout_marginTop="8dp"
            android:layout_marginEnd="8dp"
            android:layout_marginBottom="8dp"
            android:text="Detail"
            android:textSize="24sp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

(f). activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<layout 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">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:id="@+id/container"
        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="0dp"
            android:layout_height="0dp"
            app:defaultNavHost="true"
            app:layout_constraintBottom_toTopOf="@+id/bottom_nav"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <com.google.android.material.bottomnavigation.BottomNavigationView
            android:id="@+id/bottom_nav"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginStart="0dp"
            android:layout_marginEnd="0dp"
            android:background="?android:attr/windowBackground"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:menu="@menu/navigation" />

    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

Step 5: Create FragmentNavigator

Implement it as follows:

(a). KeepStateNavigator.kt

package com.star_zero.navigation_keep_fragment_sample.navigation

import android.content.Context
import android.os.Bundle
import androidx.fragment.app.FragmentManager
import androidx.navigation.NavDestination
import androidx.navigation.NavOptions
import androidx.navigation.Navigator
import androidx.navigation.fragment.FragmentNavigator

@Navigator.Name("keep_state_fragment") // keep_state_fragment is used in navigation xml
class KeepStateNavigator(
    private val context: Context,
    private val manager: FragmentManager, // Should pass childFragmentManager.
    private val containerId: Int
) : FragmentNavigator(context, manager, containerId) {

    override fun navigate(
        destination: Destination,
        args: Bundle?,
        navOptions: NavOptions?,
        navigatorExtras: Navigator.Extras?
    ): NavDestination? {
        val tag = destination.id.toString()
        val transaction = manager.beginTransaction()

        var initialNavigate = false
        val currentFragment = manager.primaryNavigationFragment
        if (currentFragment != null) {
            transaction.detach(currentFragment)
        } else {
            initialNavigate = true
        }

        var fragment = manager.findFragmentByTag(tag)
        if (fragment == null) {
            val className = destination.className
            fragment = manager.fragmentFactory.instantiate(context.classLoader, className)
            transaction.add(containerId, fragment, tag)
        } else {
            transaction.attach(fragment)
        }

        transaction.setPrimaryNavigationFragment(fragment)
        transaction.setReorderingAllowed(true)
        transaction.commitNow()

        return if (initialNavigate) {
            destination
        } else {
            null
        }
    }
}

Step 6: Create Fragments

(a). NotificationsFragment.kt

package com.star_zero.navigation_keep_fragment_sample

import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.star_zero.navigation_keep_fragment_sample.databinding.FragmentNotificationsBinding

class NotificationsFragment : Fragment() {

    private lateinit var binding: FragmentNotificationsBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.d("NotificationsFragment", "onCreate")
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        binding = FragmentNotificationsBinding.inflate(inflater, container, false)
        return binding.root
    }
}

(b). HomeContainerFragment.kt

package com.star_zero.navigation_keep_fragment_sample

import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.star_zero.navigation_keep_fragment_sample.databinding.FragmentHomeContainerBinding

class HomeContainerFragment : Fragment() {

    private lateinit var binding: FragmentHomeContainerBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.d("HomeContainerFragment", "onCreate")
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        binding = FragmentHomeContainerBinding.inflate(inflater, container, false)
        return binding.root
    }
}

(c). DetailFragment.kt

package com.star_zero.navigation_keep_fragment_sample

import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import com.star_zero.navigation_keep_fragment_sample.databinding.FragmentDetailBinding

class DetailFragment : Fragment() {

    private lateinit var binding: FragmentDetailBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.d("DetailFragment", "onCreate")
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        binding = FragmentDetailBinding.inflate(inflater, container, false)
        return binding.root
    }
}

(d). DashboardFragment.kt

package com.star_zero.navigation_keep_fragment_sample

import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import com.star_zero.navigation_keep_fragment_sample.databinding.FragmentDashboardBinding

class DashboardFragment : Fragment() {

    private lateinit var binding: FragmentDashboardBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.d("DashboardFragment", "onCreate")
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        binding = FragmentDashboardBinding.inflate(inflater, container, false)
        return binding.root
    }

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

        val adapter = DashboardAdapter()
        binding.recycler.layoutManager = LinearLayoutManager(requireContext())
        binding.recycler.addItemDecoration(DividerItemDecoration(requireContext(), DividerItemDecoration.VERTICAL))
        binding.recycler.adapter = adapter

        val data = (1..50).map { "Item $it" }
        adapter.submitList(data)
    }
}

(e). HomeFragment.kt

package com.star_zero.navigation_keep_fragment_sample

import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import com.star_zero.navigation_keep_fragment_sample.databinding.FragmentHomeBinding

class HomeFragment : Fragment() {

    private lateinit var binding: FragmentHomeBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.d("HomeFragment", "onCreate")
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        binding = FragmentHomeBinding.inflate(inflater, container, false)
        binding.handler = this
        return binding.root
    }

    fun navigateToDetail(view: View) {
        findNavController().navigate(R.id.action_home_to_detail)
    }
}

Step 7: Create Recyclerview Adapter

(a). DashboardAdapter.kt

package com.star_zero.navigation_keep_fragment_sample

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView

class DashboardAdapter: ListAdapter<String, DashboardAdapter.ViewHolder>(DIFF_CALLBACK) {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val inflater = LayoutInflater.from(parent.context)
        val view = inflater.inflate(android.R.layout.simple_list_item_1, parent, false)
        return ViewHolder(view)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.itemView.findViewById<TextView>(android.R.id.text1).text = getItem(position)
    }

    class ViewHolder(view: View): RecyclerView.ViewHolder(view)

    companion object {
        private val DIFF_CALLBACK = object: DiffUtil.ItemCallback<String>() {
            override fun areItemsTheSame(oldItem: String, newItem: String): Boolean {
                return oldItem == newItem
            }

            override fun areContentsTheSame(oldItem: String, newItem: String): Boolean {
                return oldItem == newItem
            }
        }
    }
}

Step 8: Create MainActivity

(a). MainActivity.kt

package com.star_zero.navigation_keep_fragment_sample

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.navigation.findNavController
import androidx.navigation.plusAssign
import androidx.navigation.ui.setupWithNavController
import com.star_zero.navigation_keep_fragment_sample.databinding.ActivityMainBinding
import com.star_zero.navigation_keep_fragment_sample.navigation.KeepStateNavigator

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)

        val navController = findNavController(R.id.nav_host_fragment)

        // get fragment
        val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment)!!

        // setup custom navigator
        val navigator = KeepStateNavigator(this, navHostFragment.childFragmentManager, R.id.nav_host_fragment)
        navController.navigatorProvider += navigator

        // set navigation graph
        navController.setGraph(R.navigation.nav_graph)

        binding.bottomNav.setupWithNavController(navController)
    }
}

Reference

  • Download code here.
  • Follow code author here.
[Read Individually.](https://android.camposha.info/en/example/navigation-fragment-state/)