Fragment lifecycle overlap on navigate
U

4

17

I have a single Activity application with multiple Fragments that are being switched by using Navigation components. When I switch between two fragments their onCreate() and onDestroy() methods seem to overlap. Thus making it difficult for me to write initialization and clean up code for fragments when they access the same global objects.

Navigating from Framgent_A to Fragment_B has the following order of methods:

Fragment_B.onCreate()
Fragment_A.onDestroy()

In Fragment_A.onDestroy() I reverse the operations I do in Fragment_A.onCreate(). And in Fragment_B I expect things to be in a neutral state when onCreate() is called. However that is not the case since Fragment_A.onDestroy() has not yet been called.

Is the overlap normal on Android or did I configure something wrong in my Navigation components? Is there another way I could achieve what I am trying to do? I know I could couple both Fragments and make it work, but I don't want either Fragment to know about each other. To me it seems weird that Framgnet_A is still alive when Fragment_B is created, when Fragment_B is supposed to replace Fragment_A.

Any help is greatly appreciated!


Edit:

After groing through the source code while debugging I have found out that in FragmentNavigator.navigate() FragmentTransaction.setReorderingAllowed() is called, which allows reordering of operations, even allowing onCreate() of a new fragment to be called before onDestroy() of the previous. The question still remains, how can I solve my problem of correctly cleaning up global state in one Fragment before initializing the same global state in the next Fragment.

Urquhart answered 3/7, 2018 at 14:35 Comment(7)
what code is there inside onCreate and onDestroy of Fragment B and Fragment A? Is that time consuming computation done there?Deidradeidre
@Deidradeidre They can be empty and their order of execution remains the same.Urquhart
@Urquhart if you think about it, they both need to be alive to make the screen transition seemless, otherwise there is the potential of a black screen between the transition? Still doesn't answer the question, because I am in the same boat as you (with Media resources using onPause & onResume)Arresting
Without the code it is just guessing, but 1) Are you doing things in onCreate() that would be better done further down the lifecycle (onResume() / onActivityCreated()? 2) If both fragments are creating or cleaning up something in common then there some coupling between them. Could that creation and cleanup be coordinated by the parent activity or fragment instead?Overplus
What is your global object need to access/clean up? The idea that should you do it or not? Both fragments need to communicate with each other via activity and not depends on each other. It would be great if you can post your code hereNasho
you can use onPause() in fragment A and onResume() in fragment B?Tandratandy
@AbdulAziz onPause & onResume also overlap in the same wayArresting
D
8

The Android Fragment life-cycle is not really an appropriate callback host for your needs. The navigation controller will replace the two fragments with animation, so both are somehow visible the same time and eventually even onPause() of the exiting fragment is called after onResume() of the entering one.

Solution 1: Use OnDestinationChangedListener

The onDestinationChanged() callback is called before any of the life-cycle events. As a very simplified approach (look out for leaks) you could do the following:

    findNavController().addOnDestinationChangedListener { _, destination, _ ->
        if(shouldCleanupFor(destination)) cleanup()
    }

Solution 2: Abstract the global changes away

Instead of having single navigation points change the global state, have a single point of truth for it. This could be another fragment independent of the navigation hierarchy. This then observes the navigation as before:

findNavController(R.id.nav_graph).addOnDestinationChangedListener { _, destination, _ ->
    resetAll()
    when(distination.id) {
        R.id.fragment_a -> prepareForA()
        R.id.fragment_b -> prepareForB()
        else -> prepareDefault()
    }
}

As an additional advantage you could implement the state changes idempotently as well.

Distrait answered 17/1, 2019 at 14:33 Comment(2)
For better or worse, the navigation library changed the default behaviour of fragments during navigation, so you need use workarounds like above or not use the navigation library - issuetracker.google.com/issues/155574963Geostatics
Instead of changing behaviors of app using onViewCreated() onDestroyView(), this approach works better and it's a neater solution.Soukup
N
2

Since you have an activity that controls the inflation of your Fragments you can manually control the lifecycles of the fragment that are being inflated. By calling into below methods you can control which fragment is ready to use global data. You will at this point have to, some how pass data back to Mainactivity to establish which fragment is active since your asking about how to inflate 2 fragment simultaneously which will share an object. Better approach would be to have the MainActivity implement FragmentA and FragmentB-detail with specific classes to do Stuff this way you have to treat your app like Tablet and determine 2 pane mode and which point you can use appropriate classes out of those fragments controlled by your Activity. The included link matches what your trying to accomplish

private void addCenterFragments(Fragment fragment) {
        try {
            removeActiveCenterFragments();
            fragmentTransaction = fragmentManager.beginTransaction();
            fragmentTransaction.add(R.id.content_fragment, fragment);
            fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
            activeCenterFragments.add(fragment);
            fragmentTransaction.commit();
        }catch (Exception e){
            Crashlytics.logException(e);
        }
    }

    private void removeActiveCenterFragments() {
        if (activeCenterFragments.size() > 0) {
            fragmentTransaction = fragmentManager.beginTransaction();
            for (Fragment activeFragment : activeCenterFragments) {
                fragmentTransaction.remove(activeFragment);
            }
            activeCenterFragments.clear();
            fragmentTransaction.commit();
        }
    }
Neurogram answered 14/1, 2019 at 20:20 Comment(1)
I am using the new Navigation components, which does the fragment transactions for me. Otherwise a valid approach.Urquhart
I
2

Perhaps you could move some the code related to initialization where you assume a neutral state to that fragments onStart() or onCreateView() method. According to the developer documentation this is where initialization should take place.

Another option available is using an Observer /Observable pattern, where you could notify your Activity once onDestroy() in Fragment A is completed. The Activity would then notify Fragment B that it is safe to assume a cleaned up state and begin initialization.

Insuppressible answered 17/1, 2019 at 16:7 Comment(0)
C
0

My case was a little bit different, and I would like to share it in case anyone faced the same issue.

I wanted to do an action in onPause() of the current fragment, but not execute that code when one navigates from a fragment to another. What I had to do was to call isRemoving() method to check if the current fragment is being removed or not. It is set to true when NavController.navigate(...) method is called.

override fun onPause() {
    super.onPause()
    if (!isRemoving()) {
        // Write your code here
    }
}

Per Google's Fragment.isRemoving() documentation:

Return true if this fragment is currently being removed from its activity. This is not whether its activity is finishing, but rather whether it is in the process of being removed from its activity.

Conde answered 22/3, 2020 at 18:49 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.