Navigation is the way user navigate between different fragments and activities . New navigation component helps us visualize all the navigation in our application in a single navigation graph . Navigation graph is xml resource which contains all navigation related information in one centralized location. This include all individual content area(fragments) which are called destinations and paths user can take in the applicatio known as actions. Navigation graph of our sample application is
Full source code of the sample application can be downloaded from the following github link
In this application we use navigation component to implement overflow menu and Navigation drawer. We also use Safe Args for sending data between fragments.
1.First we need to add following dependencies
For Navigation component use the following dependencies
implementation'androidx.navigation:navigation-fragment-ktx:2.1.0' implementation'androidx.navigation:navigation-ui-ktx:2.1.0'To use Navigation drawer use the following depecency
implementation'com.google.android.material:material:1.3.0'To use Safe Args first we need to add dependency in project level build.gradle file
dependencies{ def nav_version = "2.3.3" classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version" }
Then add plugin in app level guild.gradle file
apply plugin: "androidx.navigation.safeargs"
android { ... buildFeatures { dataBinding true } }Our app level build.gradle file
apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: "androidx.navigation.safeargs" android { compileSdkVersion 29 buildToolsVersion "30.0.2" defaultConfig { applicationId "com.arun.androidtutsforu.demonavigation" minSdkVersion 21 targetSdkVersion 29 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } buildFeatures{ dataBinding true } } dependencies { implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.navigation:navigation-fragment-ktx:2.1.0' implementation 'androidx.navigation:navigation-ui-ktx:2.1.0' def lifecycle_version = "2.3.0" implementation fileTree(dir: "libs", include: ["*.jar"]) implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation 'androidx.core:core-ktx:1.3.2' implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.constraintlayout:constraintlayout:2.0.4' implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version" testImplementation 'junit:junit:4.12' androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' implementation 'com.google.android.material:material:1.3.0' }Project level build.gradle file is
buildscript { ext.kotlin_version = "1.4.21" repositories { google() jcenter() } dependencies { classpath "com.android.tools.build:gradle:4.0.1" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.3.3" } } allprojects { repositories { google() jcenter() } } task clean(type: Delete) { delete rootProject.buildDir }2.Next we need to create Fragment and Layouts for our application.
click to see our fragments and layouts
GameFragment.kt and it's layout fragment_game.xml
ScoreFragment.kt and it's layout fragment_score.xml
GameOverFragment and i's layout fragment_game_over.xml
AboutFragment and it's layout fragment_about.xml
RulesFragment and it's layout fragment_rules.xml
3. Create navigation graph
i. first we need to create a navigation resource directory navigation .
-- Right click on project > new > directory and give name as navigation
ii. Then create navigation resource file navigatio.xml in it.
--Right click on navigation directory > new > navigation resource file
iii. give name as navigation.xml
following short video shows how to create a navigation graph
4.Next we need to add Navigation Graph to our application.
Add the Navigation Host Fragment to activity_main.xml
<fragment android:id="@+id/navHostFragment" android:name="androidx.navigation.fragment.NavHostFragment" android:layout_width="match_parent" android:layout_height="match_parent" app:defaultNavHost="true" app:navGraph="@navigation/navigation" />
app:navGraph="@navigation/navigation" --- will point to our navigation graph
app:defaultNavHost="true" means NavHostFragment will intercept system back key.
5.Now we can can navigate using Navigation.
In our Navigation graph we have created an action between gameFragment and scoreFragment
we need to navigate from GameFragment to ScoreFragment when we hit ''Move to next Destination'' button in GameFragment.
binding.button.setOnClickListener{view->
Navigation.findNavController(it).navigate(R.id.action_gameFragment_to_scoreFragment)
}
we have 2 actions from our ScoreFragment .
i.first One to GameOverFragment on clicking "Game over" button
binding.btover.setOnClickListener{view -> Navigation.findNavController(view).navigate(R.id.action_scoreFragment_to_gameOverFragment) }
ii.Second one to GameWonFragment on clicking "Game Won" button
binding.btwon.setOnClickListener{view -> Navigation.findNavController(view).navigate(R.id.action_scoreFragment_to_gameWonFragment) }6.Pop Behavior
From ScoreFrgment we navigated to GameWonFragment , when we click the back button from GameWonFragment we need to go back to GameFragment not to ScoreFragment. for this we need to pop the ScoreFragment from backstack. we can can do this in navigation graph
--Click the action from scoreFragment to gameWonFragment > on the right attribute panel click pop behavior >selct popUpTo Scorefragment and popUpToInclusive true
--popUpToInclusive true will pop every fragment in backstack including scoreFragment
7.Passing arguments usig Safe Args
--click gameWonFragment > in the attribute panel click + in argument > give name as score and type Integer.
now we can navigate from gameFragment to GameWonFragment with argument . action id will be changed to Directions.
binding.btwon.setOnClickListener {view-> val score = binding.etScore.text.toString().toInt() Navigation.findNavController(view).navigate(ScoreFragmentDirections.actionScoreFragmentToGameWonFragment(score)) }ScoreFragment.kt is
package com.arun.androidtutsforu.demonavigation.score import android.os.Bundle import androidx.fragment.app.Fragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.databinding.DataBindingUtil import androidx.navigation.Navigation import androidx.navigation.findNavController import com.arun.androidtutsforu.demonavigation.R import com.arun.androidtutsforu.demonavigation.databinding.FragmentScoreBinding class ScoreFragment : Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { val binding : FragmentScoreBinding = DataBindingUtil.inflate(inflater,R.layout.fragment_score, container, false) binding.btover.setOnClickListener{view -> Navigation.findNavController(view).navigate(ScoreFragmentDirections.actionScoreFragmentToGameOverFragment()) } binding.btwon.setOnClickListener {view-> val score = binding.etScore.text.toString().toInt() Navigation.findNavController(view).navigate(ScoreFragmentDirections.actionScoreFragmentToGameWonFragment(score)) } return binding.root } }
In the GameWonFragment we extract the argument from bundle and dispay it.
val args = GameWonFragmentArgs.fromBundle(arguments!!) binding.tvFinalScore.text = args.score.toString()
GameWonFragment.kt is
package com.arun.androidtutsforu.demonavigation.result import android.os.Bundle import androidx.fragment.app.Fragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.databinding.DataBindingUtil import androidx.navigation.Navigation import com.arun.androidtutsforu.demonavigation.R import com.arun.androidtutsforu.demonavigation.databinding.FragmentGameWonBinding class GameWonFragment : Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { val binding : FragmentGameWonBinding = DataBindingUtil.inflate(inflater,R.layout.fragment_game_won, container, false) val args = GameWonFragmentArgs.fromBundle(arguments!!) binding.tvFinalScore.text = args.score.toString() binding.btHome.setOnClickListener { view -> Navigation.findNavController(view).navigate(GameWonFragmentDirections.actionGameWonFragmentToGameFragment()) } return binding.root } }8.Using Up Button in ActionBar
Apart from back button , our appliction should have an up button(arrow on top left side) for navigating within our application. To implement this we need to add some code to Mainactivity.kt
val navController = this.findNavController(R.id.navHostFragment) NavigationUI.setupActionBarWithNavController(this,navController)overeide onSupportNavigateUp
override fun onSupportNavigateUp(): Boolean { val navController = this.findNavController(R.id.navHostFragment) return navController.navigateUp() }Now we can navigate using up button. Our MainAcitity with UpButton (without nvigation drawer)
package com.arun.androidtutsforu.demonavigation 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.ui.NavigationUI import com.arun.androidtutsforu.demonavigation.databinding.ActivityMainBinding class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val binding :ActivityMainBinding = DataBindingUtil.setContentView(this,R.layout.activity_main) val navController = this.findNavController(R.id.navHostFragment) NavigationUI.setupActionBarWithNavController(this,navController) } override fun onSupportNavigateUp(): Boolean { val navController = this.findNavController(R.id.navHostFragment) return navController.navigateUp() } }
9.Adding Overflow menu
for creating a overflow menu first we need create a menu resource file overflow_menu.xml
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/aboutFragment" android:title="About" /> </menu>--Id should match with our fragment id and title is name shown on the screen . Our overflow menu will be shown as About and clicking it will open the aboutFragment.
--OverFlow menu is shown in gamefargment so we need to add setHasOptionsMenu(true) in oncreate of GameFragment.xml
--Override onCreateOptionsMenu
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { super.onCreateOptionsMenu(menu, inflater) inflater.inflate(R.menu.overflow_menu, menu) }--Override onOptionsItemSelected
override fun onOptionsItemSelected(item: MenuItem): Boolean { return NavigationUI.onNavDestinationSelected( item, view!!.findNavController() ) || super.onOptionsItemSelected(item) }GameFragment.xml file is
package com.arun.androidtutsforu.demonavigation.game import android.os.Bundle import android.view.* import androidx.databinding.DataBindingUtil import androidx.fragment.app.Fragment import androidx.navigation.Navigation import androidx.navigation.findNavController import androidx.navigation.ui.NavigationUI import com.arun.androidtutsforu.demonavigation.R import com.arun.androidtutsforu.demonavigation.databinding.FragmentGameBinding class GameFragment : Fragment() { override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { val binding: FragmentGameBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_game, container, false) binding.button.setOnClickListener { view -> Navigation.findNavController(view).navigate(R.id.action_gameFragment_to_scoreFragment) } 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, view!!.findNavController() ) || super.onOptionsItemSelected(item) } }10.Next we need to add Navigation drawer to our application
i. For creating navigation drawer first we need to ceate a new menu resource file navdrawer_menu.xml
<?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/aboutFragment" android:icon="@android:drawable/ic_menu_info_details" android:title="About" /> <item android:id="@+id/rulesFragment" android:icon="@android:drawable/ic_menu_manage" android:title="Rules" /> </menu>android:id="@+id/aboutFragment"-- id should match with id of the fragment we navigate to when clicking on the menu item(on clicking about we will navigate to aboutFragment)
ii.Change activity_main.xml to include navigation drawer.
Add DrawerLayout below layout tag
<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" tools:context=".MainActivity"> <androidx.drawerlayout.widget.DrawerLayout android:id="@+id/draweLayout" android:layout_width="match_parent" android:layout_height="match_parent" > .... .... </androidx.drawerlayout.widget.DrawerLayout> </layout>Add navigationView at the bottom of DrawerLayout
<com.google.android.material.navigation.NavigationView android:id="@+id/navView" android:layout_width="wrap_content" android:layout_height="match_parent" app:menu="@menu/navdrawer_menu" android:layout_gravity="start" app:headerLayout="@layout/nav_header" /> </androidx.drawerlayout.widget.DrawerLayout>
app:menu="@menu/navdrawer_menu" --- this will point to the menu resource file
app:headerLayout="@layout/nav_header" -- this points to the header layout for our navigation drawer. Our header layout nav_header.xml
<?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="162dp" android:background="#009688"> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Nav Header" android:textSize="24sp" android:textStyle="bold" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>Our 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" tools:context=".MainActivity"> <androidx.drawerlayout.widget.DrawerLayout android:id="@+id/draweLayout" android:layout_width="match_parent" android:layout_height="match_parent" > <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" > <fragment android:id="@+id/navHostFragment" android:name="androidx.navigation.fragment.NavHostFragment" android:layout_width="match_parent" android:layout_height="match_parent" app:defaultNavHost= "true" app:navGraph="@navigation/navigation" /> </LinearLayout> <com.google.android.material.navigation.NavigationView android:id="@+id/navView" android:layout_width="wrap_content" android:layout_height="match_parent" app:menu="@menu/navdrawer_menu" android:layout_gravity="start" app:headerLayout="@layout/nav_header" /> </androidx.drawerlayout.widget.DrawerLayout> </layout>iii. we are done with layout , go to MainActivity.kt and itialize yourdrawer layout and pass this drawerlayout as third parameter of NavigationUI.setupActionBarWithNavController()
drawerLayout = binding.draweLayout val navController = this.findNavController(R.id.navHostFragment) NavigationUI.setupActionBarWithNavController(this,navController,drawerLayout)iv. Add our Navigation View to Navigation UI
NavigationUI.setupWithNavController(binding.navView,navController)v. Override onSupportNavigateUp
aoverride fun onSupportNavigateUp(): Boolean { val navController = this.findNavController(R.id.navHostFragment) return NavigationUI.navigateUp(navController,drawerLayout) }we only need to show navigation drawer in first destination ,in other location we can lock it. We use navController.addOnDestinationChangedListener which will be called when destination changes
navController.addOnDestinationChangedListener{nc : NavController , nd : NavDestination , args : Bundle? ->
if(nd.id == nc.graph.startDestination){ drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED) } else{ drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED) } }
our MainActivity.kt is
package com.arun.androidtutsforu.demonavigation 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.ui.NavigationUI import com.arun.androidtutsforu.demonavigation.databinding.ActivityMainBinding class MainActivity : AppCompatActivity() { private lateinit var drawerLayout: DrawerLayout override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val binding :ActivityMainBinding = DataBindingUtil.setContentView(this,R.layout.activity_main) drawerLayout = binding.draweLayout val navController = this.findNavController(R.id.navHostFragment) NavigationUI.setupActionBarWithNavController(this,navController,drawerLayout) NavigationUI.setupWithNavController(binding.navView,navController) navController.addOnDestinationChangedListener{nc : NavController , nd : NavDestination , args : Bundle? -> if(nd.id == nc.graph.startDestination){ drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED) } else{ drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED) } } } override fun onSupportNavigateUp(): Boolean { val navController = this.findNavController(R.id.navHostFragment) return NavigationUI.navigateUp(navController,drawerLayout) } }
Full source code of the application can be downloaded from the github link https://github.com/arunkfedex/demoNavigation
Comments
Post a Comment