androidkotlinnavigationfragment-backstack

How do I pop android navigation backstack up to a certain fragment?


I am once again asking for your intellectual support.

I have a fragment that puts a number of copies of itself on the navigation backstack like this:

Navigation.findNavController(button).navigate(
    TaskCollectFragmentDirections.actionTaskCollectFragmentSelf(args.taskIndex, args.screenIndex + 1)
)

The code above works as intended.

The problem comes, when I want to pop a specified number of these fragments off the navigation backstack. (Triggered by a buttonpress.)

I've tried to do it like this:

Navigation.findNavController(button).popBackStack(lastRecurringFragmentId, false)

My code compiles and runs, but simply does nothing. When I give no parameters to the popBackStack function, it properly pops off the topmost fragment. I can also use the back arrow to navigate back all the way to the starting fragment.

If it is of any importance, the lastRecurringFragmentId comes from this function:

fun getLastRecurringFragmentId(recurringFragmentCount: Int): Int {
    val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment)!!
    val backStackEntryCount = navHostFragment.childFragmentManager.backStackEntryCount
    val indexOfRequested = backStackEntryCount - recurringFragmentCount
    val requestedEntry = navHostFragment.childFragmentManager.getBackStackEntryAt(indexOfRequested)
    return requestedEntry.id
}

The navigation graph is as follows. There are supposed to be several TaskCollectFragments and lastly one TaskEndFragment on the top of the backstack. I want to pop some of the TaskCollectFragments off the stack when the user presses a button on the TaskEndFragment:

<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/nav_home">

    <fragment
        android:id="@+id/nav_home"
        android:name="ui.home.HomeFragment"
        android:label="@string/menu_home"
        tools:layout="@layout/fragment_home" >
        <action
            android:id="@+id/action_nav_home_to_taskCollectFragment"
            app:destination="@id/taskCollectFragment" />
    </fragment>

    <fragment
        android:id="@+id/taskCollectFragment"
        android:label="TaskCollectFragment"
        tools:layout="@layout/task_collect_fragment"
        >
        <action
            android:id="@+id/action_taskCollectFragment_to_taskEndFragment"
            app:destination="@id/taskEndFragment" />
        <action
            android:id="@+id/action_taskCollectFragment_self"
            app:destination="@id/taskCollectFragment" />
        <argument
            android:name="taskIndex"
            app:argType="integer" />
        <argument
            android:name="screenIndex"
            app:argType="integer" />
    </fragment>
    <fragment
        android:id="@+id/taskEndFragment"
        android:name="ui.task.TaskEndFragment"
        android:label="TaskEndFragment"
        tools:layout="@layout/task_end_fragment">
        <argument
            android:name="taskIndex"
            app:argType="integer" />
    </fragment>
</navigation>

Is there a way I can pop fragments off the backstack until a certain condition is met?


Solution

  • I created two almost identical fragments, forSessionTaskCollectFragment and recurringTaskCollectFragment with the same viewModel. They look the same and from the user's point of view they do the same. The forSessionTaskCollectFragments are the ones I want to keep and the recurringTaskCollectFragments are the ones I want to pop back later.

    From the home fragment, my navigation graph goes to the forSessionTaskCollectFragment, from there it loops back on itself or goes to the recurringTaskCollectFragment which also loops back to itself or finally goes to the TaskEndFragment

    <fragment
        android:id="@+id/nav_home"
        android:name="ui.home.HomeFragment"
        android:label="@string/menu_home"
        tools:layout="@layout/fragment_home" >
        <action
            android:id="@+id/action_nav_home_to_orSessionTaskCollectFragment"
            app:destination="@id/forSessionTaskCollectFragment" />
    </fragment>
    
    <fragment
        android:id="@+id/forSessionTaskCollectFragment"
        android:name="ui.task.ForSessionTaskCollectFragment"
        android:label="ForSessionTaskCollectFragment" >
        <action
            android:id="@+id/action_forSessionTaskCollectFragment_self"
            app:destination="@id/forSessionTaskCollectFragment" />
        <action
            android:id="@+id/action_forSessionTaskCollectFragment_to_recurringTaskCollectFragment"
            app:destination="@id/recurringTaskCollectFragment" />
        <argument
            android:name="taskIndex"
            app:argType="integer" />
        <argument
            android:name="screenIndex"
            app:argType="integer" />
    </fragment>
    
    <fragment
        android:id="@+id/recurringTaskCollectFragment"
        android:name="ui.task.RecurringTaskCollectFragment"
        android:label="TaskCollectFragment"
        tools:layout="@layout/recurring_task_collect_fragment"
        >
        <action
            android:id="@+id/action_recurringTaskCollectFragment_to_taskEndFragment"
            app:destination="@id/taskEndFragment" />
        <action
            android:id="@+id/action_recurringTaskCollectFragment_self"
            app:destination="@id/recurringTaskCollectFragment" />
        <argument
            android:name="taskIndex"
            app:argType="integer" />
        <argument
            android:name="screenIndex"
            app:argType="integer" />
    </fragment>
    

    I put forSessionTaskCollectFragments on the stack until I get to the point to which I want to return.

    Navigation.findNavController(button).navigate(
        if(args.screenIndex < viewModel.forSessionScreenCount) {
            ForSessionTaskCollectFragmentDirections.actionForSessionTaskCollectFragmentSelf(args.taskIndex, args.screenIndex + 1)
        } else {
            ForSessionTaskCollectFragmentDirections.actionForSessionTaskCollectFragmentToRecurringTaskCollectFragment(args.taskIndex, args.screenIndex + 1)
        }
    )
    

    After that I put the fragments I later want to pop back, and finally go to the TaskEndFragment from where I want to pop the fragments I need to:

    Navigation.findNavController(button).navigate(
        if(args.screenIndex < viewModel.screenCount - 2) {
            RecurringTaskCollectFragmentDirections.actionRecurringTaskCollectFragmentSelf(args.taskIndex, args.screenIndex + 1)
        } else {
            RecurringTaskCollectFragmentDirections.actionRecurringTaskCollectFragmentToTaskEndFragment(args.taskIndex)
        }
    )
    

    So on the TaskEndFragment's buttonpress I can pop back to the exact place on the backstack that I marked with changing from "session" fragments to "recurring" fragments:

    Navigation.findNavController(button).popBackStack(R.id.forSessionTaskCollectFragment, false)
    

    This was a lot of hassle, I did not find any example for the thing I wanted to do, but finally managed to come up with a working solution. Ianhanniballake thanks again, you triggered my idea.