Search code examples
androidandroid-fragmentsandroid-architecture-navigation

Back button not working when using Navigation architecture component


I have the following Activity:

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

with the following layout:

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

    <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:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navGraph="@navigation/navigation_graph" />

</androidx.constraintlayout.widget.ConstraintLayout>

I also have two fragments: Fragment1 and Fragment2. Fragment1 is:

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

class Fragment1 : Fragment() {

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

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        findNavController().navigate(R.id.action_fragment1_to_fragment2)
    }

}

and its layout is:

<?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=".Fragment1">

    <TextView
        android:id="@+id/toFragment2Button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="Fragment 1" />

</FrameLayout>

Fragment2 is:

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

class Fragment2 : Fragment() {

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

}

with the following layout:

<?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=".Fragment2">

    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="Fragment 2" />

</FrameLayout>

and last but not least is the contents of navigation_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"
    xmlns:tools="http://schemas.android.com/tools"
    app:startDestination="@id/fragment1">

    <fragment
        android:id="@+id/fragment1"
        android:name="ir.fallahpoor.navigation.Fragment1"
        android:label="fragment_1"
        tools:layout="@layout/fragment_1">
        <action
            android:id="@+id/action_fragment1_to_fragment2"
            app:destination="@id/fragment2" />
    </fragment>
    <fragment
        android:id="@+id/fragment2"
        android:name="ir.fallahpoor.navigation.Fragment2"
        android:label="fragment_2"
        tools:layout="@layout/fragment_2" />
</navigation>

As you can see there is nothing fancy here. The only thing that I'm doing is navigating to Fragment2 in onViewCreated of Fragment1.

When I run the app Fragment2 is displayed as expected. When I press the back button I expect to return to Fragment1, but that doesn't happen. It is as if back button is disabled and pressing it doesn't do anything. I have to press the home button to exit the app.


Solution

  • The Reason for the reported behavior is your code simulated an infinite loop. You should not call

    findNavController().navigate(R.id.action_fragment1_to_fragment2)
    

    from onViewCreated() instead use a button or any form of trigger to perform the navigation.

    Your Flow:

    STEP 1: onViewCreated() of fragment1 is called when the app launched and the app is navigated to fragment2.

    STEP 2: As soon as the application navigates to fragment2, the fragment1 view is destroyed. i.e. onDistroyView() is called on fragment1.

    STEP 3: The moment you hit the back button from fragment2, fragment1 is pulled from the back stack, since its view is destroyed, it goes through the view creation cycle again. i.e. onViewCreated() is called again. as a result, your app is stuck with infinite loop.