Search code examples
androidkotlinandroid-architecture-navigationandroid-jetpack-navigationandroid-navigation-graph

Navigation component doesn't pop fragment


When we open the application, I show an intro fragment. In the intro fragment after some time, I navigate the user to the feed fragment. On the feed fragment when the user presses the back button, I need to show an exit dialog. When the user will click exit, I need to close the app.

This is my navigation graph.

<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/mobile_navigation"
app:startDestination="@+id/fragment_intro">

<fragment
    android:id="@+id/fragment_feed"
    android:name="FeedFragment"
    tools:layout="@layout/fragment_feed"/>

<fragment
    android:id="@+id/fragment_intro"
    android:name="IntroFragment"
    tools:layout="@layout/fragment_intro">

    <action
        android:id="@+id/action_introFragment_to_feedFragment"
        app:destination="@id/fragment_feed"
        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/mobile_navigation"
        app:popUpToInclusive="true" />

</fragment>
</navigation>

So here is it. My start destination is the Intro fragment. And I have an action, which navigates me to the feed fragment. And I pop my intro fragment, when I navigate to the feed fragment.

Regarding to the code, in the intro fragment, I navigate to the feed fragment after 3 seconds.

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)

    Handler(Looper.getMainLooper()).postDelayed({
        val action = IntroFragmentDirections.actionIntroFragmentToFeedFragment()
        findNavController().navigate(action)
    }, 3000)
}

So in the feed fragment, when the user clicks the back button, I need to show an exit dialog. That means, I need to catch the back press, which I've done in this way. In the onCreateView() of my feed fragment I've added this

requireActivity().onBackPressedDispatcher.addCallback(
        viewLifecycleOwner, object : OnBackPressedCallback(true) {
            override fun handleOnBackPressed() {
                // Show exit dialog
            }
        }
    )

And when the user clicks on the back button, simply I call this function

fun onExit() {
    findNavController().navigateUp()
}

But when I click on the exit button, it returns back to the intro fragment, which I've popped. So why it isn't popping, what am I doing wrong?

---EDIT---

One solution is to remove the dispatcherCallback and call onBackPressed(), but I don't think that's a good one.

val onBackPressedCallback = object : OnBackPressedCallback(true) {
        override fun handleOnBackPressed() {
            // Show exit dialog
        }
    }
    
requireActivity().onBackPressedDispatcher.addCallback(
    viewLifecycleOwner, onBackPressedCallback 
)
    
fun onExit() {
    onBackPressedCallback.remove()
    activity?.onBackPressed()
}

Thank you.


Solution

  • After some time I've found the solution, maybe this will help someone. So the issue that the fragment didn't pop is, because when we have only one fragment in our backstack, or we try to pop our start destination fragment, navigateUp() function will return false and the fragment will not be popped. So why is that happening? Because navigateUp navigates to the previous fragment in your navigation host. But if there is no fragment, where our navigation controller could navigate, it simply returns false. So we need a function to determine whether this is the only fragment in our graph, and we can't navigate up or we can. Because calling every time activity?.onBackPressed() is not a good solution. So to achieve this, we can create an extension function like this:

    fun NavController.navigateUpOrFinish(activity: FragmentActivity): Boolean {
        return if (navigateUp()) {
            true
        } else {
            activity.finish()
            true
        }
    }
    

    That's all. Now we can call this function on the dialog exit click

    fun onExit() {
        findNavController().navigateUpOrFinish(requireActivity())
    }
    

    And if there will be a fragment, where we can navigate, it will call navigateUp() in other cases, it will simply call backPressed and our app will be closed. Thank you.