Search code examples
androidandroid-fragmentsandroid-lifecycleandroid-architecture-componentsandroid-architecture-navigation

What is the correct way of implementing circular logic with the navigation component


I'm trying to implement circular logic with the Navigation component but I'm concerned that I'm not doing it right and lifecycle methods are unnecessarily being called.

I have 1 activity that has 3 fragments. Navigation between the fragments looks like this: A -> B -> C -> back to A etc..

A and B are regular Fragments while C is a DialogFragment. C has two buttons - Cancel and Done. If Cancel is pressed, a navigation action is called (using findNavController().navigate(<action>)) and the app will show A. If Done is pressed, C is dismissed and the app will show B and the user can then return to A by pressing back. This all works as I expect, however...

My concern is that each route back to A results in different lifecycle methods being called in A. If navigation returns to A after the user accepts C and presses back on B, onCreateView(), onViewCreated(), and onResume() is called by A. BUT, if navigation returns to A after C is cancelled, A calls many more lifecycle methods (onAttach(). onCreate(), onCreateView(), onViewCreated, onResume(), onDestroy(), onDetach()). Why is there a difference? Why is onCreate() being called again in A? Shouldn't it just use the existing instance of A instead of creating a new one?

I can't figure out why it's doing this or if it's even something I should be concerned about. I'm confident that the stack is appropriately managed as the user navigates between fragments because the navigation action between C and A uses the popUpTo and popUpToInclusive attributes (as recommended here in the docs). I've also tried setting the launchSingleTop attribute in the action between C and A but I get the same behaviour (extra lifecycle methods being called in A).

Here is the xml for the C fragment:

<dialog
    android:id="@+id/C"
    android:name="C"
    ... >
    <action
        android:id="@+id/C_to_A"
        app:destination="@id/A"
        app:popUpTo="@id/A"
        app:popUpToInclusive="true" />
</dialog>

I call action C_to_A from C when the user presses the cancel button.

Any help clearing up my confusion would be very welcome.


Solution

  • An action has two steps:

    1. popping any destinations set via popUpTo / popUpToInclusive
    2. Navigating (i.e., creating a new instance of) a destination set via app:destination

    Actions can be any combination of just the pop, just the navigate, or both - the UI in the Navigation Editor when you right click a destination and select New Action will give you each of these options.

    Therefore if you want an action that only pops back to a destination you know is on the back stack, then you can remove your app:destination attribute and only pop:

    <action
            android:id="@+id/C_to_A"
            app:popUpTo="@id/A"
            app:popUpToInclusive="false" />
    

    Note that by using app:popUpToInclusive="false", you ensure that A will be on the top of the stack after the action is executed.