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.

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

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

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

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

6. Quotes Trivia Game Navigation Component

Android sample app to get familiarized with Navigation Component.

Quotes Trivia is simple trivia game where you have to pick the notable humans in history that originally said the memorable quote in a multiple-choice challenge. At the end of each game you will get a score based on the number of correct answers you have selected. You can also share you score with others.

App in action

Here is the demo GIF of the app:

quotes_trivia Example Tutorial

Navigation Graph

It manages your app's navigation. It is a resource file that consists of the destinations along with the actions, which are used for navigating to another destination from the current one.

The Navigation Graph of the Quotes Trivia looks like this:

quotes_trivia Example Tutorial

Let us look at the full code below:

Step 1. Dependencies

We need to add some dependencies in our app/build.gradle file as shown below:

(a). build.gradle

Our app-level build.gradle.

We Prepare our dependencies as shown below. You may use later versions.

At the top of our app/build.gradle we will apply the following 4 plugins:

  1. Our com.android.application plugin.
  2. Our kotlin-android plugin.
  3. Our kotlin-android-extensions plugin.
  4. Our androidx.navigation.safeargs.kotlin plugin.

We will also enable Java8 so that we can utilize a myriad of Java8 features.

We then declare our app dependencies under the dependencies closure, using the implementation statement. We will need the following 8 dependencies:

  1. Kotlin-stdlib - So that we can use Kotlin as our programming language.
  2. Core-ktx - With this we can target the latest platform features and APIs while also supporting older devices.
  3. Appcompat - Allows us access to new APIs on older API versions of the platform (many using Material Design).
  4. Our Legacy-support-v4 support library. Feel free to use newer AndroidX versions.
  5. Constraintlayout - This allows us to Position and size widgets in a flexible way with relative positioning.
  6. Our Navigation-fragment-ktx library.
  7. Our Navigation-ui-ktx library.
  8. Material - Collection of Modular and customizable Material Design UI components for Android.

Here is our full app/build.gradle:

plugins {
    id "org.sonarqube" version "3.0"
}

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

android {
    compileSdkVersion 30
    buildToolsVersion "29.0.3"

    defaultConfig {
        applicationId "com.souvikbiswas.quotestrivia"
        minSdkVersion 21
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

    kotlinOptions {
        jvmTarget = JavaVersion.VERSION_1_8.toString()
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }

    buildFeatures {
        dataBinding true
    }
}

dependencies {
    // Kotlin
    implementation fileTree(dir: "libs", include: ["*.jar"])
    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    implementation 'androidx.core:core-ktx:1.3.1'
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'androidx.legacy:legacy-support-v4:1.0.0'

    // Constraint Layout
    implementation 'androidx.constraintlayout:constraintlayout:2.0.1'

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

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

    // Testing
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'

}

Step 2. Our Android Manifest

We will need to look at our AndroidManifest.xml.

(a). AndroidManifest.xml

Our AndroidManifest file.

Our project will have only a single Activity but we have to register it right here as shown below: We will be defining a meta-data tag as well which allows us to set our font resources via the android:resource tag. Here is the full Android Manifest file:

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

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <meta-data
            android:name="preloaded_fonts"
            android:resource="@array/preloaded_fonts" />
    </application>

</manifest>

Step 3. Create Navigation Rules

Create a directory known as navigation inside your res directory and add the following navigation rules:

(a). 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/nav_root"
    app:startDestination="@id/welcomeFragment">

    <fragment
        android:id="@+id/welcomeFragment"
        android:name="com.souvikbiswas.quotestrivia.WelcomeFragment"
        tools:layout="@layout/fragment_welcome">
        <action
            android:id="@+id/action_welcomeFragment_to_triviaFragment"
            app:destination="@id/triviaFragment"
            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/triviaFragment"
        android:name="com.souvikbiswas.quotestrivia.TriviaFragment"
        tools:layout="@layout/fragment_trivia">
        <action
            android:id="@+id/action_triviaFragment_to_wonFragment"
            app:destination="@id/wonFragment"
            app:enterAnim="@anim/fade_in"
            app:exitAnim="@anim/fade_out"
            app:popEnterAnim="@anim/slide_in_left"
            app:popExitAnim="@anim/slide_out_right"
            app:popUpTo="@id/triviaFragment"
            app:popUpToInclusive="true" />
        <action
            android:id="@+id/action_triviaFragment_to_lostFragment"
            app:destination="@id/lostFragment"
            app:enterAnim="@anim/slide_in_left"
            app:exitAnim="@anim/slide_out_right"
            app:popEnterAnim="@anim/slide_in_right"
            app:popExitAnim="@anim/slide_out_left"
            app:popUpTo="@id/triviaFragment"
            app:popUpToInclusive="true" />
    </fragment>
    <fragment
        android:id="@+id/wonFragment"
        android:name="com.souvikbiswas.quotestrivia.WonFragment"
        tools:layout="@layout/fragment_won">
        <argument
            android:name="numQuestions"
            app:argType="integer" />
        <argument
            android:name="numCorrect"
            app:argType="integer" />
        <action
            android:id="@+id/action_wonFragment_to_triviaFragment"
            app:destination="@id/triviaFragment"
            app:enterAnim="@anim/slide_in_left"
            app:exitAnim="@anim/slide_out_left"
            app:popEnterAnim="@anim/slide_in_left"
            app:popExitAnim="@anim/slide_out_right"
            app:popUpTo="@id/wonFragment"
            app:popUpToInclusive="true" />
    </fragment>
    <fragment
        android:id="@+id/lostFragment"
        android:name="com.souvikbiswas.quotestrivia.LostFragment"
        tools:layout="@layout/fragment_lost">
        <argument
            android:name="numQuestions"
            app:argType="integer" />
        <argument
            android:name="numCorrect"
            app:argType="integer" />
        <action
            android:id="@+id/action_lostFragment_to_triviaFragment"
            app:destination="@id/triviaFragment"
            app:enterAnim="@anim/slide_in_right"
            app:exitAnim="@anim/slide_out_left"
            app:popEnterAnim="@anim/slide_in_left"
            app:popExitAnim="@anim/slide_out_right"
            app:popUpTo="@id/lostFragment"
            app:popUpToInclusive="true" />
    </fragment>
    <fragment
        android:id="@+id/aboutFragment"
        android:name="com.souvikbiswas.quotestrivia.AboutFragment"
        android:label="@string/about_title"
        tools:layout="@layout/fragment_about" />
    <fragment
        android:id="@+id/rulesFragment"
        android:name="com.souvikbiswas.quotestrivia.RulesFragment"
        android:label="@string/rules_title"
        tools:layout="@layout/fragment_rules" />
</navigation>

Step 4. Create Menus

Create a directory known as menu inside your res directory and add the following menu files:

(a). share_menu.xml

Inside your /res/menu/ directory create a menu resource file named share_menu.xml and add menu items as shown below:

  • Share - To share the app
<?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/share"
        android:enabled="true"
        android:icon="@drawable/ic_share"
        android:title="@string/share_title"
        android:visible="true"
        app:showAsAction="ifRoom" />

</menu>

(b). overflow_menu.xml

Inside your /res/menu/ directory create another menu resource file named overflow_menu.xml and add menu items as shown below:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/aboutFragment"
        android:title="@string/about_title" />
</menu>

(c). navdrawer_menu.xml

Last but not least create another menu resource file named navdrawer_menu.xml and add menu items as shown below:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item
        android:id="@+id/rulesFragment"
        android:icon="@drawable/ic_rules"
        android:title="@string/rules_title" />
    <item
        android:id="@+id/aboutFragment"
        android:icon="@drawable/ic_about"
        android:title="@string/about_title" />
</menu>

Step 5. Create Animations

Create a directory known as anim inside your res directory and add the following xml file:

(a). fade_in.xml

<?xml version="1.0" encoding="utf-8"?>

<set xmlns:android="http://schemas.android.com/apk/res/android">
    <alpha
        android:duration="@android:integer/config_mediumAnimTime"
        android:fromAlpha="0.0"
        android:toAlpha="1.0" />
</set>

You will find more animation xml files in the sample download.

Step 6. Write Code

Finally we need to write our code as follows:

(a). AboutFragment.kt

Our AboutFragment class.

Create a Kotlin file named AboutFragment.kt and add the necessary imports. Here are some of the imports we will be using:

  1. Bundle from the android.os package.
  2. Fragment from the androidx.fragment.app package.
  3. LayoutInflater from the android.view package.
  4. View from the android.view package.
  5. ViewGroup from the android.view package.

Then extend the Fragment and add its contents as follows:

Here is the full code:

package replace_with_your_package_name

import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup

class AboutFragment : Fragment() {
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_about, container, false)
    }
}

(b). WelcomeFragment.kt

Our WelcomeFragment class.

Create a Kotlin file named WelcomeFragment.kt and add the necessary imports. Here are some of the imports we will be using:

  1. ActionBar from the androidx.appcompat.app package.
  2. AppCompatActivity from the androidx.appcompat.app package.
  3. DataBindingUtil from the androidx.databinding package.
  4. findNavController from the androidx.navigation package.
  5. NavigationUI from the androidx.navigation.ui package.

Then extend the Fragment and add its contents as follows:

First override these callbacks:

  1. onCreateOptionsMenu(menu: Menu, inflater: MenuInflater).
  2. onOptionsItemSelected(item: MenuItem): Boolean.

Here is the full code:

package replace_with_your_package_name

import android.os.Bundle
import android.view.*
import androidx.fragment.app.Fragment
import androidx.appcompat.app.ActionBar
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.navigation.findNavController
import androidx.navigation.ui.NavigationUI
import com.souvikbiswas.quotestrivia.databinding.FragmentWelcomeBinding

class WelcomeFragment : Fragment() {
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val actionBar: ActionBar? = (activity as AppCompatActivity).supportActionBar

        actionBar?.elevation = 0f
        actionBar?.title = ""

        val binding: FragmentWelcomeBinding = DataBindingUtil.inflate(
            inflater, R.layout.fragment_welcome, container, false
        )

        binding.startButton.setOnClickListener { view: View ->
            view.findNavController()
                .navigate(WelcomeFragmentDirections.actionWelcomeFragmentToTriviaFragment())
        }

        setHasOptionsMenu(true)

        return binding.root
    }

    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
        super.onCreateOptionsMenu(menu, inflater)
        inflater.inflate(R.menu.overflow_menu, menu)
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        return NavigationUI.onNavDestinationSelected(
            item,
            requireView().findNavController()
        ) || super.onOptionsItemSelected(item)
    }
}

(c). RulesFragment.kt

Our RulesFragment class.

Create a Kotlin file named RulesFragment.kt and add the necessary imports. Here are some of the imports we will be using:

  1. Bundle from the android.os package.
  2. Fragment from the androidx.fragment.app package.
  3. LayoutInflater from the android.view package.
  4. View from the android.view package.
  5. ViewGroup from the android.view package.

Then extend the Fragment and add its contents as follows:

Here is the full code:

package replace_with_your_package_name

import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup

class RulesFragment : Fragment() {
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_rules, container, false)
    }
}

(d). TriviaFragment.kt

Our TriviaFragment class.

Create a Kotlin file named TriviaFragment.kt and add the necessary imports. Here are some of the imports we will be using:

  1. TextView from the android.widget package.
  2. Toast from the android.widget package.
  3. AppCompatActivity from the androidx.appcompat.app package.
  4. DataBindingUtil from the androidx.databinding package.
  5. findNavController from the androidx.navigation package.

Then extend the Fragment and add its contents as follows:

We will be creating the following functions:

  1. randomizeQuestions().
  2. setQuestion().

(a). Our randomizeQuestions() function

Write the randomizeQuestions() function as follows:

    private fun randomizeQuestions() {
        questions.shuffle()
        questionIndex = 0
        setQuestion()
    }

(b). Our setQuestion() function

Write the setQuestion() function as follows:

    private fun setQuestion() {
        currentQuestion = questions[questionIndex]
        answers = currentQuestion.answers.toMutableList()
        answers.shuffle()
    }

Here is the full code:

package replace_with_your_package_name

import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.navigation.findNavController
import com.souvikbiswas.quotestrivia.databinding.FragmentTriviaBinding
import com.souvikbiswas.quotestrivia.databinding.FragmentWelcomeBinding
import kotlin.math.min
import kotlin.math.round

class TriviaFragment : Fragment() {
    data class Question(val quote: String, val answers: List<String>)

    private val questions: MutableList<Question> = mutableListOf(
        Question(
            quote = ""I have nothing to offer but blood, toil, tears and sweat."",
            answers = listOf(
                "Winston Churchill", "Sitting Bull", "Nikita Khrushchev", "Charles de Gaulle"
            )
        ),
        Question(
            quote = ""I consider myself the luckiest man on the face of the earth."",
            answers = listOf(
                "Lou Gehrig", "Bill Gates", "Adolf Hitler", "George Washington"
            )
        ),
        Question(
            quote = ""A desperate disease requires a dangerous remedy."",
            answers = listOf(
                "Guy Fawkes", "Louis Pasteur", "David Lloyd George", "Alexander Fleming"
            )
        ),
        Question(
            quote = ""To appreciate the importance of fitting every human soul for independent action, think for a moment of the immeasurable solitude of self."",
            answers = listOf(
                "Elizabeth Cady Stanton",
                "Pope John Paul II",
                "Queen Victoria",
                "George Washington Carver"
            )
        )
    )

    lateinit var currentQuestion: Question
    lateinit var questionNumberText: TextView
    lateinit var answers: MutableList<String>
    private var questionIndex = 0
    private var correctAnswers = 0
    private val numQuestions = 4

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val binding: FragmentTriviaBinding = DataBindingUtil.inflate(
            inflater, R.layout.fragment_trivia, container, false
        )

        randomizeQuestions()

        binding.trivia = this

        questionNumberText = binding.questionNumberText

        questionNumberText.text = "${questionIndex + 1} / $numQuestions"

        binding.submitButton.setOnClickListener { view: View ->
            val checkedId = binding.answerChoiceGroup.checkedRadioButtonId
            if (-1 != checkedId) {
                var answerIndex = 0
                when (checkedId) {
                    R.id.secondChoiceButton -> answerIndex = 1
                    R.id.thirdChoiceButton -> answerIndex = 2
                    R.id.fourthChoiceButton -> answerIndex = 3
                }
                questionIndex++

                if (answers[answerIndex] == currentQuestion.answers[0]) {
                    correctAnswers++
                }

                if (questionIndex < numQuestions) {
                    currentQuestion = questions[questionIndex]
                    setQuestion()
                    binding.invalidateAll()
                    questionNumberText.text = "${questionIndex + 1} / $numQuestions"
                } else {
                    if (correctAnswers < round(0.8 * numQuestions)) {
                        view.findNavController()
                            .navigate(TriviaFragmentDirections.actionTriviaFragmentToLostFragment(numQuestions, correctAnswers))
                    } else {
                        view.findNavController().navigate(TriviaFragmentDirections.actionTriviaFragmentToWonFragment(numQuestions, correctAnswers))
                    }
                }
            }
        }

        return binding.root
    }

    private fun randomizeQuestions() {
        questions.shuffle()
        questionIndex = 0
        setQuestion()
    }

    private fun setQuestion() {
        currentQuestion = questions[questionIndex]
        answers = currentQuestion.answers.toMutableList()
        answers.shuffle()
    }

}

(e). WonFragment.kt

Our WonFragment class.

First override these callbacks:

  1. onCreateOptionsMenu(menu: Menu, inflater: MenuInflater).
  2. onOptionsItemSelected(item: MenuItem): Boolean.

Then we will be creating the following functions:

  1. getShareIntent(): Intent.
  2. shareSuccess().

(a). Our shareSuccess() function

Write the shareSuccess() function as follows:

    private fun shareSuccess() {
        startActivity(getShareIntent())
    }

(b). Our getShareIntent() function

Write the getShareIntent() function as follows:

    private fun getShareIntent(): Intent {
        val args = WonFragmentArgs.fromBundle(requireArguments())
        val shareIntent = Intent(Intent.ACTION_SEND)
        shareIntent.setType("text/plain")
            .putExtra(
                Intent.EXTRA_TEXT,
                getString(R.string.share_text, args.numCorrect, args.numQuestions)
            )
        return shareIntent
    }

Here is the full code:

package replace_with_your_package_name

import android.content.Intent
import android.os.Bundle
import android.view.*
import androidx.fragment.app.Fragment
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.navigation.findNavController
import com.souvikbiswas.quotestrivia.databinding.FragmentWonBinding

class WonFragment : Fragment() {

    private lateinit var correctAnswersText: TextView
    private lateinit var totalQuestionsText: TextView

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

        binding.nextMatchButton.setOnClickListener { view: View ->
            view.findNavController()
                .navigate(WonFragmentDirections.actionWonFragmentToTriviaFragment())
        }

        correctAnswersText = binding.correctAnswersText
        totalQuestionsText = binding.totalQuestionsText

        val args = WonFragmentArgs.fromBundle(requireArguments())

        correctAnswersText.text = args.numCorrect.toString()
        totalQuestionsText.text = args.numQuestions.toString()

        setHasOptionsMenu(true)

        return binding.root
    }

    private fun getShareIntent(): Intent {
        val args = WonFragmentArgs.fromBundle(requireArguments())
        val shareIntent = Intent(Intent.ACTION_SEND)
        shareIntent.setType("text/plain")
            .putExtra(
                Intent.EXTRA_TEXT,
                getString(R.string.share_text, args.numCorrect, args.numQuestions)
            )
        return shareIntent
    }

    private fun shareSuccess() {
        startActivity(getShareIntent())
    }

    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
        super.onCreateOptionsMenu(menu, inflater)
        inflater.inflate(R.menu.share_menu, menu)

        if (null == getShareIntent().resolveActivity(requireActivity().packageManager)) {
            menu.findItem(R.id.share)?.isVisible = false
        }
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        when (item.itemId) {
            R.id.share -> shareSuccess()
        }

        return super.onOptionsItemSelected(item)
    }
}

(f). LostFragment.kt

Our LostFragment class.

Create a Kotlin file named LostFragment.kt and add the necessary imports. Here are some of the imports we will be using:

  1. View from the android.view package.
  2. ViewGroup from the android.view package.
  3. TextView from the android.widget package.
  4. DataBindingUtil from the androidx.databinding package.
  5. findNavController from the androidx.navigation package.

Then extend the Fragment and add its contents as follows:

Here is the full code:

package replace_with_your_package_name

import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.databinding.DataBindingUtil
import androidx.navigation.findNavController
import com.souvikbiswas.quotestrivia.databinding.FragmentLostBinding

class LostFragment : Fragment() {

    private lateinit var correctAnswersText: TextView
    private lateinit var totalQuestionsText: TextView

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val binding: FragmentLostBinding = DataBindingUtil.inflate(
            inflater, R.layout.fragment_lost, container, false
        )

        binding.playAgainButton.setOnClickListener { view: View ->
            view.findNavController()
                .navigate(LostFragmentDirections.actionLostFragmentToTriviaFragment())
        }

        correctAnswersText = binding.correctAnswersText
        totalQuestionsText = binding.totalQuestionsText

        val args = LostFragmentArgs.fromBundle(requireArguments())

        correctAnswersText.text = args.numCorrect.toString()
        totalQuestionsText.text = args.numQuestions.toString()

        return binding.root
    }
}

(g). MainActivity.kt

Our MainActivity class.

Create a Kotlin file named MainActivity.kt and add the necessary imports. Here are some of the imports we will be using:

  1. NavDestination from the androidx.navigation package.
  2. findNavController from the androidx.navigation package.
  3. NavHostFragment from the androidx.navigation.fragment package.
  4. AppBarConfiguration from the androidx.navigation.ui package.
  5. NavigationUI from the androidx.navigation.ui package.

Then extend the AppCompatActivity and add its contents as follows:

Override these callbacks:

  1. onCreate(savedInstanceState: Bundle?).
  2. onSupportNavigateUp(): Boolean.

Here is the full code:

package replace_with_your_package_name

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.databinding.DataBindingUtil
import androidx.drawerlayout.widget.DrawerLayout
import androidx.navigation.NavController
import androidx.navigation.NavDestination
import androidx.navigation.findNavController
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.NavigationUI
import com.souvikbiswas.quotestrivia.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {

    private lateinit var drawerLayout: DrawerLayout
    private lateinit var appBarConfiguration: AppBarConfiguration

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

        drawerLayout = binding.drawerLayout

        val navHostFragment =
            supportFragmentManager.findFragmentById(R.id.myNavHostFragment) as NavHostFragment
        val navController = navHostFragment.navController
        NavigationUI.setupActionBarWithNavController(this, navController, drawerLayout)

        appBarConfiguration = AppBarConfiguration(navController.graph, drawerLayout)

        navController.addOnDestinationChangedListener { nc: NavController, nd: NavDestination, _: Bundle? ->
            if (nd.id == nc.graph.startDestination) {
                drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED)
            } else {
                drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
            }
        }

        NavigationUI.setupWithNavController(binding.navView, navController)
    }

    override fun onSupportNavigateUp(): Boolean {
        val navController = this.findNavController(R.id.myNavHostFragment)
        return NavigationUI.navigateUp(navController, drawerLayout)
    }
}

Reference

Download the code below:

No. Link
1. Download Full Code
2. Read more here.
3. Follow code author here.