Reputation: 4232
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.
Upvotes: 17
Views: 6140
Reputation: 6707
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.
Upvotes: 0
Reputation: 654
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.
Upvotes: 2
Reputation: 39863
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.
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()
}
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.
Upvotes: 8
Reputation: 582
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();
}
}
Upvotes: 2