I have created a simple demo project to test the default back stack feature of the Android Jetpack Navigation component.
I'm having one main activity and two fragments. The home fragment is being displayed when the app runs. The home fragment has a button. On click on that navigates to the other fragment.
But whenever I'm pressing the system back button my complete application is finished rather than the usual back stack behavior where I should see the home fragment.
Any help is appreciated. Thanks in advance.
The code is as follows :
MainActivity.kt
package com.callsamik.jetpacknavigationcomponentdemo
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
activity_main.xml (layout for the main activity)
<?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=".MainActivity">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nave_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:navGraph="@navigation/nav_graph"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
HomeFragment.kt
package com.callsamik.jetpacknavigationcomponentdemo
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 com.callsamik.jetpacknavigationcomponentdemo.databinding.FragmentHomeBinding
class HomeFragment: Fragment() {
var binding: FragmentHomeBinding? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentHomeBinding.inflate(layoutInflater)
return binding?.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding?.loginButton?.setOnClickListener{
val action = HomeFragmentDirections.actionHomeFragmentToLoginFragment()
findNavController().navigate(action)
}
}
override fun onDestroyView() {
super.onDestroyView()
binding = null
}
}
fragment_home.xml (layout for HomeFragment)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
tools:context= ".HomeFragment">
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="Home Screen"
android:textAppearance="@style/TextAppearance.AppCompat.Large"/>
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/loginButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Login"/>
</LinearLayout>
LoginFragment.kt (the 2nd fragment)
package com.callsamik.jetpacknavigationcomponentdemo
import androidx.fragment.app.Fragment
class LoginFragment: Fragment(R.layout.fragment_login) {
}
fragment_login.xml (2nd fragment layout)
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat 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"
android:gravity="center"
android:padding="16dp"
android:orientation="vertical"
tools:context=".LoginFragment">
<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/usernameEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Username"/>
<androidx.appcompat.widget.AppCompatEditText
android:id="@+id/passwordEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Password"/>
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/confirmButton"
android:text="Confirm"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</androidx.appcompat.widget.LinearLayoutCompat>
nav_graph.xml (navigation graph)
<?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">
<fragment
android:id="@+id/homeFragment"
android:name="com.callsamik.jetpacknavigationcomponentdemo.HomeFragment"
android:label="fragment_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="com.callsamik.jetpacknavigationcomponentdemo.LoginFragment"
android:label="fragment_login"
tools:layout="@layout/fragment_login" />
</navigation>
Top-level build.gradle
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext.kotlin_version = "1.5.31"
ext.nav_version = "2.3.5"
repositories {
google()
mavenCentral()
}
dependencies {
classpath "com.android.tools.build:gradle:7.0.3"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
app module build.gradle
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'androidx.navigation.safeargs.kotlin'
}
android {
compileSdk 31
defaultConfig {
applicationId "com.callsamik.jetpacknavigationcomponentdemo"
minSdk 22
targetSdk 31
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildFeatures {
viewBinding true
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.1'
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
As per the documentation:
- The
app:defaultNavHost="true"
attribute ensures that yourNavHostFragment
intercepts the system Back button. Note that only oneNavHost
can be the default. If you have multiple hosts in the same layout (two-pane layouts, for example), be sure to specify only one defaultNavHost
.
You are missing that attribute so Navigation is not handling the system back button.
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nave_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:navGraph="@navigation/nav_graph"
app:defaultNavHost="true"
/>