someone can help me to understand why the lifecycle method Stop and Destroy are not called with fragment manager transaction when you use a single transaction add/ remove ?
I have a single container ( RelativeLayout ) in which I can add or remove many fragments. Case with only two fragments. Fragment_A is already added in previous transaction.
now if I do this below, everything works fine: The lifecycle methode OnPause and OnDestroy of fragment_A are called.
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.add(myId, fragmentB);
trasaction.commit();
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.remove(fragmentA);
trasaction.commit();
if I do this below, the lifecycle method OnPause, OnDestroy are never fired for fragment A ( I checked the fragment inside the manager and the fragments are well added / removed ):
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.add(myContainerId, fragmentB);
transaction.remove(fragmentA);
trasaction.commit();
I tried to use transaction.setResorderAllowed(false) but whatever, my lifecycle doesn't go until destroy with single transaction. ( I can not use replace, I really need add/remove )
Thanks for your help.
Related
Here is a sample code to repo this bug:
If I replace 3 fragments in a row, and disable the second one to be added into the backstack.
fragmentManager = supportFragmentManager
val fargmentA = FragmentA()
fragmentManager.beginTransaction().replace(R.id.container, fargmentA).addToBackStack("a").commitAllowingStateLoss();
val fargmentB = FragmentB()
fragmentManager.beginTransaction().replace(R.id.container, fargmentB).disallowAddToBackStack().commitAllowingStateLoss();
val fargmentC = FargmentC()
fragmentManager.beginTransaction().replace(R.id.container, fargmentC).addToBackStack("c").commitAllowingStateLoss();
After the above transaction, I called popBackStack():
val count = fragmentManager.backStackEntryCount;
for(i in 0 until count ){
fragmentManager.popBackStackImmediate()
}
I can still see the onCreateView() method is called from FragmentB which I disallow add to backstack.
Is this a known bug or just how fragment manager behaves?
Thanks!
There's two main things to keep in mind:
replace() is the equivalent to calling remove() on every fragment currently added to that container, then calling add() with your new fragment, so that transaction with fragmentC is going to remove fragmentB as part of its operation
popBackStack() puts you exactly in the state you were in before the transaction. This means that fragmentC gets removed (the opposite of add()) and fragmentB gets added (the opposite of remove())
This means that it is expected that popping your fragmentC transaction will bring fragmentB back - your disallowAddToBackStack() just means that your fragmentB transaction can never be reversed.
This actually has some serious implications when it comes to mixing addToBackStack() and non-addToBackStack() fragment transactions on the same container: after you do your fragmentB transaction, there's no way to get back to fragmentA.
Generally, this means that if that was actually want you wanted, you replace your non-back stack fragmentB transaction with
fragmentManager.popBackStack()
val fargmentB = FragmentB()
fragmentManager.beginTransaction().replace(R.id.container, fargmentB).addToBackStack("b").commitAllowingStateLoss();
This would ensure that all transactions use addToBackStack() and the reversal works as expected at each step.
Note that every one of your transactions should use setReorderingAllowed(true) as per the Fragment transaction guide. This will prevent cases where fragments are moved up to higher lifecycle levels then moved immediately back down and ensures that combined operations like a popBackStack() and commit() together run as a single atomic transition.
I have a problem with the backstack behaviour. That is what I am doing:
add(fragment1) + addToBackStack(null)
replace(fragment2) + addToBackStack(null)
What is happening:
Fragment 1 is added and in the backstack
Then the second fragment replaces the first one and it is added to the backstack.
Now I want to change my last backstacked fragment with a new transaction which put a new backstack fragment so:
[frag1, frag2] becomes [frag1, frag3]
but this transaction made by a popBackStack + replace is making the frag1 to load by calling its onCreateView and onActivityCreated. I know this is the expected behaviour since this is how backstack works, but I am trying to find a way to avoid this preload.
Edit
In this question I am using the concept of backstack fragment for the transaction to be more clear. Every transaction here is an add+remove (which is a replace).
The code for replace I am using is:
public int replaceFragment(BaseFragment newFragment, boolean addToBackStack, boolean animated, PopStackMode popMode) {
if (popMode != null) {
getSupportFragmentManager().popBackStack(newFragment.getFragmentTag(), popMode == PopStackMode.POP_INCLUSIVE ? FragmentManager
.POP_BACK_STACK_INCLUSIVE : 0);
}
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
if (animated) {
ft.setCustomAnimations(R.anim.slide_in_left, 0, R.anim.slide_out_right, 0);
}
ft.replace(R.id.fragment_container, newFragment, newFragment.getFragmentTag());
if (addToBackStack) {
ft.addToBackStack(newFragment.getFragmentTag());
}
return ft.commit();
}
You can see I am creating a navigation history based on the fragment backstack, as it was kind of a browser. When a "page" is added there is a fragment and a backstack transaction. In this context, I trying to:
Remove the current fragment.
Remove the transaction from the backstack.
Add a new fragment without poping and loading the previous backstack fragment.
I hope it is more clear.
Edit 2
I have filled a request feature for a flag that supports this behavior. Find it here.
First, you should understand that the backstack doesn't save fragments, but it saves transactions instead. When you call popBackStack what it actually does is revert the previous transaction. More on this here.
I think that you can do this:
Name your transactions by providing a unique name to your addToBackStack instead of null. i.e. addToBackStack("frag1").
Don't call popBackStack + replace, but instead just call replace.
Then, in your activity, override your onBackPressed and if the current fragment being displayed is Frag3 (you can check this using findFragmentByTag if you provided a tag in the replace method) you can call getSupportFragmentManager().popBackStackImmediate("frag1", FragmentManager.POP_BACK_STACK_INCLUSIVE); (otherwise call the super.onBackPressed)
Working with fragments I've always used replace() for my transactions, but I wish I didn't have to save instance states anymore to restore a fragment's view and prevent reloading when coming back to that fragment. So, I've decided to work with add(). The thing is when I add another fragment, the previous fragment view remains in the background and that's fine (that's the behavior I expected), but the problem is I can actually interact with the views in the background. Example:
Fragment A has a Button
Fragment B has a TextView
When I add Fragment A and later add Fragment B, I'm able to click on Fragment A's Button, even staying on Fragment B's view.
I'm using:
FragmentTransaction fragmentTransaction =
getSupportFragmentManager().beginTransaction().
add(getRootViewContainer(),fragment,fragment.getClass().getSimpleName());
if (shouldGoBack)
fragmentTransaction.addToBackStack(fragment.getClass().getSimpleName());
where getRootViewContainer() returns the id of the FrameLayout I'm using as my activity main container.
Now, is it really the default behavior of add()?
If so, is there a proper way to avoid this or one just has to use replace()?
What you can do here is just hide previous fragment at the time of transaction of current fragment.
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
Fragment newFragment= new MyFragment ();
ft.hide(CurrentFragment.this);
ft.show(newFragment);
ft.commit();
It worked for me just try it.
FragmentTransaction.hide(fragmentBehind); //works for me!
example :
//I have it globally available
FragmentTransaction trans = MainActivity.getManager().beginTransaction();
//not globally
FragmentTransaction trans = getFragmentManager().beginTransaction();
MapFragment newFragment = new newFragment();
trans.add(R.id.fragmentContainer, newFragment, tag);
trans.hide(this);
trans.addToBackStack(tag);
trans.commit();
Yes, this is a default behaviour of add().
If you really don't want to user replace(), you can try to disable views which are inside "old" fragment.
I have two fragments - Listing and detail. Initially I load the listing fragment in the container of the activity using
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.content_frame, listingFrag);
ft.commit();
On tapping an item in the listing fragment I load the detail fragment using
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.add(R.id.content_frame, detailFrag);
ft.addToBackStack(null);
ft.commit();
On loading detail fragment I do not get the onPause call of the listing fragment. Neither do I get the onResume call of the listing fragment on coming back from detail fragment (using the system back button).
Also, when I am in the detail fragment, putting the app to background calls the onPause of both the listing and detail fragments. On getting back the app from background, onResume of both listing and detail screens get called.
The above mentioned behaviors are quite unexpected.
I would want
1) listing fragment's onResume to be called on coming back from listing screen
2) listing fragment's onPause to be called on loading detail fragment
3) only detail fragment's onPause to be called when the app is put to background
4) only detail fragment's onResume to be called when the app is brought back from background
Can some one please explain a way to do this.
Thanks in advance!
onPause and onResume in fragment are just the reflection of onPause and onResume in Activity.
So every time you go from one activity to another all the fragments of the first activity will call onPause and all the fragments of the 2nd activity will call onResume.
This functions are not for fragment visible/add or remove.
When you display the detailed fragment, you add it which means that both the listing and the detailed fragments are active at the same time. Hence both of them receive onPause() and onResume().
You should instead try replacing the listing fragment with the details fragment. That will cause onPause() in the listing fragment and only one fragment will be active at a time.
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.content_frame, listingFrag);
ft.addToBackStack(null);
ft.commit();
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.content_frame, detailFrag);
ft.addToBackStack(null);
ft.commit();
I'm trying to decide and show a fragment in activity's onResume method, but in case a previously added fragment is chosen again, then the activity goes blank.
Sample code (with one fragment):
#Override
protected void onResume(){
FragmentTransaction trans = getSupportFragmentManager().beginTransaction();
trans.replace(R.id.myLayout, fragA);
trans.commit();
getSupportFragmentManager().executePendingTransactions();
}
With above code, when the activity is created for the first time, it shows fragA correctly, but in case I press Home Key and then switch back to my activity (in order to provoke onResume again), it all goes blank (seems like fragA is removed).
Is replacing a previously added fragment removes itself? or how not to loose a fragment if it is replaced by itself?
You can't replace a fragment with itself. The first half of a replace is a removal of the previous fragment at that id. Once a fragment is removed it can no longer be added or used by the fragment manager (so the add portion of the replace will not work properly).
Depending on your use case, you have two options:
Create a new fragment instead of reusing the existing instance
Use some other method to see if its necessary to replace your fragment
Finally, you probably don't need to call executePendingTransactions.
You can try:
if( !(getSupportFragmentManager().findFragmentById(R.id.myLayout) instanceof FragmentA) ) {
FragmentTransaction trans = getSupportFragmentManager().beginTransaction();
trans.replace(R.id.myLayout, fragA);
trans.commit();
}
And I assume that fragA is FragmentA class object.
Finally, I had to put a check before replacing fragments. In case, an (already added) fragment is requested for replace again, I had to check if its already added then ignore the replacement, else proceed. For example:
#Override
protected void onResume() {
if (!fragA.isAdded()) {
FragmentTransaction trans = getSupportFragmentManager().beginTransaction();
trans.replace(R.id.myLayout, fragA);
trans.commit();
//getSupportFragmentManager().executePendingTransactions(); //unnecessary
}
}
When referencing back to a created Fragment please do make sure to try adding the
fragmentTransaction.addToBackStack(null);
method right before committing so that your Fragment is resumed instead of destroyed as mentioned in the developer guides.
If you don't call addToBackStack() when you perform a transaction that removes a fragment, then that fragment is destroyed when the transaction is committed and the user cannot navigate back to it. Whereas, if you do call addToBackStack() when removing a fragment, then the fragment is stopped and is later resumed if the user navigates back.
You can find this at the end of this page.