Jetpack Navigation Component Examples
In this piece you will learn about Navigation Component through step by step android examples in Kotlin.
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 defaultNavHost
implementation,NavHostFragment
, that displays fragment destinations.NavController
: An object that manages app navigation within aNavHost
. TheNavController
orchestrates the swapping of destination content in theNavHost
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 aViewModel
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:
[
[
[
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:
- MainActivity layout - activity_main.xml
- Home Fragment Layout - fragment_home.xml
- Login Fragment Layout - fragment_login.xml
- Search Fragment Layout - fragment_search.xml
- Settings Fragment Layout - fragment_settings.xml
- Terms Fragment Layout - fragment_terms.xml
- 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:
- HomeFragment
- LoginFragment
- SearchFragment
- SettingsFragment
- TermsFragment
- 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
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
- Open your
AndroidStudio
IDE. - 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'
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>
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>
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>
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
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
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
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.gradle
add the following:
implementation "androidx.navigation:navigation-fragment-ktx:2.1.0"
implementation "androidx.navigation:navigation-ui-ktx:2.1.0"
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
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
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:
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:
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:
- Our
com.android.application
plugin. - Our
kotlin-android
plugin. - Our
kotlin-android-extensions
plugin. - 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:
Kotlin-stdlib
- So that we can use Kotlin as our programming language.Core-ktx
- With this we can target the latest platform features and APIs while also supporting older devices.Appcompat
- Allows us access to new APIs on older API versions of the platform (many using Material Design).- Our
Legacy-support-v4
support library. Feel free to use newer AndroidX versions. Constraintlayout
- This allows us to Position and size widgets in a flexible way with relative positioning.- Our
Navigation-fragment-ktx
library. - Our
Navigation-ui-ktx
library. 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:
onCreateOptionsMenu(menu: Menu, inflater: MenuInflater)
.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:
randomizeQuestions()
.setQuestion()
.
(a). Our randomizeQuestions()
function
Write the randomizeQuestions()
function as follows:
(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:
onCreateOptionsMenu(menu: Menu, inflater: MenuInflater)
.onOptionsItemSelected(item: MenuItem): Boolean
.
Then we will be creating the following functions:
getShareIntent(): Intent
.shareSuccess()
.
(a). Our shareSuccess()
function
Write the shareSuccess()
function as follows:
(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:
onCreate(savedInstanceState: Bundle?)
.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. |