Skip to main content

Navigation Android kotlin

 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



screenshots of our sample application implementing android navigation 


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"
we are also using viewBinding . So add the folowing in the app level build.gradle file. 
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

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 can do all other navigation in our application like this.
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

we are also passing data(score) from scoreFragmet to gameWonFragment . For passing data between fragments we use Safe Args. we already made necessary changes in build.gradle file. For passing score to gameWonfragments we need to add that argument in navigation graph

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

Popular posts

Simple Calculator With ViewModel and LIveData

This is a simple calculator with basic mathematical operations. You can download full source code of this project from Github https://github.com/arunkfedex/SimpleCalculator We are using ViewModel and LiveData so we need to add those dependencies in build.gradle file. build.gradle plugins { id 'com.android.application' id 'kotlin-android' } android { compileSdk 30 defaultConfig { applicationId "com.arun.androidtutsforu.simplecalculator" minSdk 21 targetSdk 30 versionCode 1 versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 ta...

How to Open Android Emulator form Command Line and install Apk in Emulator

  You can also view this on my youtube channel How to Open Android Emulator from Commad Line 1.Open Command line 2.Change working directory to android sdk directory    cd  appdata/local/android/sdk/emulator 3.List all available Android virtual devices     emulator -list-avds 4. All your avds will be shown choose the avd_name you want to open    emulator -avd avd_name   5.Your Android virtual device will open up How to install APk file to emulator Drag the APK to Android emulator it will install automatically

Android List View using Custom Adapter and SQLite

following is a simple applicaton to create ListView using  Custom adapter.screenshot of the application  is like this . ListView is not used in android Anymore . We use  RecyclerView  and  CardView   in Android RecyclerView Demo is available on the following link http://androidtuts4u.blogspot.in/2017/04/android-recyclerview-example.html RecyclerView with Cardview Example is available on the following link http://androidtuts4u.blogspot.in/2017/09/android-card-view-example.html The ListView below the submit button is populated using Custom Adapter.Data is stored and retrieved using SQLite databsase. you can download the source code of this project from  google drive   https://drive.google.com/folderview?id=0BySLpWhqmbbdUXE5aTNhazludjQ&usp=sharing click on the above link ->sign into  your  google account ->add this to your google drive -> open it in google drive and download it. To create a simple ...