FragmentManager popBackStack doesn't remove fragment - android

I'm implementing menu navigation using Fragments. So I begin with Home, and then users can navigate to diferent sections and details of each section.
When a user changes section, then I call pop on the fragmentmanager backstack until I reach Home, and then load the new section.
This is all working as expected. But I'm getting this problem:
load a section that calls setHasOptionsMenu(true) on onResume()
loads another section (old section it's suposed to get out of the stack). I see it OK. No menu is shown
leave the application (for example, go to Android Laucher activity) and then when I return, I see the correct section, but it's showing the Menu of the old Fragment.
I've iterated the backstack and printed each fragment, and there it's not the fragment with the menu.
I put a debug mark on the onResume() method (where the setHasOptionsMenu(true) is flagged) and it indeed enters here, so the Fragment it's still somewhere.
I want to know if I'm doing something wrong and how could I solve it, thx
Update:
I'm using this code to load new fragments
fm.beginTransaction()
.add(container, sectionFragment.getFragment())
.addToBackStack(sectionFragment.getFragmentName())
.commit();
And for remove:
private void clearStack(){
int count = fm.getBackStackEntryCount();
while(count > 1){
fm.popBackStack();
count--;
}
}
NOTE 1: I'm using add instead replace because I don't want to loose the state of my fragment when I navigate back from detail section. When I load another different section, then I call clearStack to pop the stack up to 1, and then loads new fragment. At the end, I'm calling executePendingTransactions() to finish to remove the fragments from the transaction.
NOTE 2: I'm seeing that it is entering on my fragment onDestroy() method, so it is suposed to be destroyed. But I don't know why it is getting called again when the Main activity resumes.

I found that the problem was not in the logic of adding and removing fragment of the stack.
The problem was that some of the fragment loaded another fragments inside of it (it had ViewPager component). Then I thought that when the fragment was removed then these fragments were removed too.
This is true ONLY if you use getChildFragmentManager() method. This method MUST be used when loading fragments inside other fragmets. If not, then the fragments are asociated with the fragments activity.

popBackStack will just revert your last FragmentTransaction.
If you use FragmentTransaction.add, popBackStack will just call FragmentTransacetion.remove.
But if you call FragmentTransaction.replace, popBackStack will call FragmentTransaction.remove and FragmentTransaction.add
For your "NOTE 1" :
FragmentTransaction.replace will not change your fragment state.

I found this question, because after calling
fragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
this code fragmentManager.getFragments().size() returns me the maximum number of fragments, which were in the stack. I checked every fragment on null. And I found that some fragment is null in my case. Maybe it will help someone)

If you are really looking to remove fragments at once then follow:
How to replace Fragments of different types?
Otherwise use replace transaction for fragments to smooth transitiona and hassel free approach, see https://stackoverflow.com/a/23013075/3176433
Also understand Fragment lifecycle,
http://developer.android.com/guide/components/fragments.html

I had a similar problem where the popBackStack() didn't remove my fragment.
However, I noticed that I called the wrong FragmentManager, where I had to call getSupportFragmentMananger() instead of getFragmentManager().

Maybe there is a <fragment> or <androidx.fragment.app.FragmentContainerView> in an activity with android:name="androidx.navigation.fragment.NavHostFragment", app:defaultNavHost="true" and app:navGraph="#navigation/nav_graph".
In this case navigation is held by nav_graph. If you don't want to use NavController and NavHostFragment, maybe you should remove navigation and clean <fragment> tag.

Related

Fragment does not call lifecycle methods

I've a fragment A. I add() it with tag like this:
fragmentTransaction.addToBackStack(special_tag);
Then I simply add() fragment B on top of fragment A. After that, I decide to remove fragment B and go back to fragment A using:
activity.fragmentManager.popBackStackImmediate(special_tag, 0)
When I reach the fragment A, it seems that fragment doesn't re-run it's lifecycle methods: onAttach(), onResume(), onCreate() ect.
Can someone explain this behavior and maybe suggest an alternative?
(I need to "refresh" the data when I come back to fragment A second time)
What is causing this result?
Is there a clean solution/work-around?
Update
Fragment B is GuidedStepFragment and does not have a .replace() function. I found that it has finishGuidedStepFragments(), but it behaves the same (it does not call fragment life cycle functions)
Situation (again):
Fragment A (Simple fragment) -> .add(Fragment B) (GuidedStepFragment) -> popBackStackImmediate() or finishGuidedStepFragments()
I add Fragment B like this:
GuidedStepFragment.add(activity.fragmentManager, fragmentB.createInstance())
Using fragmentTransaction.add(Fragment) doesn't remove Fragment A. What is actually happening is that Fragment A is still running behind Fragment B. Since Fragment A never stopped running, it's lifecycle has no need to retrigger.
Consider using fragmentTransaction.replace(Fragment) and replace the fragment in the container (fragment A) with fragment B. If you pop that transaction from the back stack, then Fragment A will reattach and follow your expected lifecycle.
Update
Since you seem to be using GuidedStepFragments from the leanback library, this is a little tricky. GuidedStepFragment actually performs replace(...) under the hood, but you're adding fragment B to a different container so the original behavior I mentioned doesn't apply.
I'm not super familiar with leanback (since it's usually only used for android tv), but I do know that you can at least do the following. If you keep track of your backstack size, when all of the GuidedStepFragments have been popped, you will have returned to your original fragment. For example, let's assume your backstack starts at zero:
activity.fragmentManager.addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
#Override
public void onBackStackChanged() {
if (activity.fragmentManager.getBackStackEntryCount() == 0){
// handle your updates
}
}
});
// the next line of code will add an entry to the backstack
GuidedStepFragment.add(activity.fragmentManager, fragmentB.createInstance());
// eventually when back is pressed and the guided fragment is removed, the backstack listener should trigger

How to handle fragments backstack changes?

I have some king of header in my activity, which says what kind of fragment is opened now. It's ok, when I'm just replacing one fragment by another, but I have a problem with handling backstack changes in onBackPressed. That's a part of my code in onBackPressed method:
Fragment fragment = fragmentManager.findFragmentById(R.id.main_fragment);
fragmentManager.popBackStack();
fragment = fragmentManager.findFragmentById(R.id.main_fragment);
in first row, fragment=FormFragment{41f01d58 #3 id=0x7f05005f}, and after calling popBackStack I have fragment=FormFragment{41f01d58 #3 id=0x7f05005f} again (but it should be another fragment, even not FormFragment instance).
Is there any way how to find out what fragment is popped from backstack after calling popBackStack?
First of all, usually you don't have to pop the fragment back stack yourself. If your activity is a FragmentActivity, its default onBackPressed() will do the work for you.
To update your header when the fragment is popped from the back stack, put the header update code in the fragment's onResume().

Android Fragment View State Loss When Using FragmentTransaction.replace()

I am having a pretty big issue and I am not quite understanding what is happening. I am developing an application that uses Fragments (from the support library) and am using FragmentTransaction.replace() to place new Fragments on to the back stack and replace the old one. The code looks as follows:
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = ft.beginTransaction();
// Animations in my res/anim folder
ft.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left, R.anim.slide_in_left, R.anim.slide_out_right);
ft.replace(R.id.fragment_container, newFragment, tag);
ft.addToBackStack(null);
ft.commit();
This is successful in replacing my fragment. My issue is the following. In one Fragment, I have a list of items that is built from user input. Now, when the user clicks next and then clicks the back button (to return to the list), the list is empty because the view is destroyed. Now, I have noted the following:
onSaveInstanceState is not called. I believe this is because that is only called when the parent Activity tells it to. Based on the docs: " There are many situations where a fragment may be mostly torn down (such as when placed on the back stack with no UI showing), but its state will not be saved until its owning activity actually needs to save its state.". Apparently, performing a replace on the FragmentTransaction is not one of those times. Does anyone have confirmation on this or a better explanation?
setOnRetainInstanceState(true) is not helpful in this situation. Again, I believe this has to do with info from the docs: "Control whether a fragment instance is retained across Activity re-creation (such as from a configuration change)". I am not performing any action in re-creating the activity so this is of no use.
So, I guess my main question is: is there a way to preserve the View state (simply retain the Fragment) when using replace? There is FragmentTransaction.add(), but there are a few issues with this as well. One being that the exit animation is not performed, thus the animation is not correct. Another is that the new Fragment that the old fragment (the one that is being put into a non-visible state) is still clickable. For example, if I have a ListFragment, and I place a content fragment on top of that by using add, I can still click the list items in the ListFragment.
Without being able to see the code of your fragments this is a bit of a guess, but in the past I've run into this same issue and I've found that resetting the adapter in your ListFragment in onViewStateRestored seems to do the trick.
public void onViewStateRestored (Bundle savedInstanceState)
{
super.onViewStateRestored (savedInstanceState);
setListAdapter(new ArrayAdapter(Activity, R.layout.nav_item, objects));
}
Which is weird considering the documentation states that this method is called after onActivityCreated but before onStart. But it seems that it is also called at other times because when the most recent fragment transaction is popped off the back stack this method is called before the previously replaced fragment is displayed. The activity that owns the fragments has not been paused or obscured in any way, so according to the docs onViewStateRestored should not be called since just the fragments were modified. But this seems to work anyway.
It sounds like you simply need to make sure you have properly implemented onCreateView and onDestroyView. The situation you are describing seems to indicate that when the list fragment is put on the back stack (as a result of the replace transaction) Android is calling onDestroyView to free up some resources. However, it apparently has not destroyed the list fragment because when you tap back you are getting back the same instance of the fragment.
Assuming this is all true then, when the user taps back Android will call onCreateView. Any state that you have stored in the fragment's instance variables should still be there and all you need to do is repopulate the view...perhaps set the adapter on the ListView or whatever.
Also make sure your onSaveInstanceState() callback actually does save any instance state that you need to rebuild the view. That way if the fragment actually does get completely destroyed the FragmentManager can restore the state when it needs to recrete the fragment later.

How to keep only first added Fragment in back stack (fragment overlapping)?

Scenario what i'm trying to achieve:
Loading activity with two frame containers (for list of items and for details).
At the app launch time add listFragment in listFrame and some initial infoFragment in detailsFrame containers.
Navigating through list items without adding each detail transaction to back stack (want to keep only infoFragment in stack).
As soon as user hit back button (navigate back) he falls back to intial infoFragment what was added in launch time.
If sequential back navigation fallows then apps exit.
My code:
protected override void OnCreate(Bundle savedInstanceState)
{
...
var listFrag = new ListFragment();
var infoFrag = new InfoFragment();
var trans = FragmentManager.BeginTransaction();
trans.Add(Resource.Id.listFrame, listFrag);
trans.Add(Resource.Id.detailsFrame, infoFrag);
trans.Commit();
...
}
public void OnItemSelected(int id)
{
var detailsFrag = DetailFragment.NewInstance(id);
var trans = FragmentManager.BeginTransaction();
trans.Replace(Resource.Id.detailsFrame, detailsFrag);
if (FragmentManager.BackStackEntryCount == 0)
{
trans.AddToBackStack(null);
}
trans.Commit();
}
My problem:
After back button has been hit, infoFrag is overlapped with previous detailFrag! Why?
You can do this:
if (getSupportFragmentManager().getBackStackEntryCount() > 0) {
getSupportFragmentManager().popBackStack(getSupportFragmentManager().getBackStackEntryAt(0).getId(), getSupportFragmentManager().POP_BACK_STACK_INCLUSIVE);
} else {
super.onBackPressed();}
In your activity, so you to keep first fragment.
You shouldn't have, in your first fragment, the addToBackStack. But, in the rest, yes.
Very nice explanation by Budius. I read his advice and implemented similar navigation, which I would like to share with others.
Instead of replacing fragments like this:
Transaction.remove(detail1).add(detail2)
Transaction.remove(detail2).add(detail3)
Transaction.remove(detail3).add(detail4)
I added a fragment container layout in the activity layout file. It can be either LinearLayout, RelativeLayot or FrameLayout etc.. So in the activity on create I had this:
transaction.replace(R.id.HomeInputFragment, mainHomeFragment).commit();
mainHomeFragment is the fragment I want to get back to when pressing the back button, like infoFrag. Then, before EVERY NEXT transaction I put:
fragmentManager.popBackStackImmediate();
transaction.replace(R.id.HomeInputFragment, frag2).addToBackStack(null).commit();
or
fragmentManager.popBackStackImmediate();
transaction.replace(R.id.HomeInputFragment, frag3).addToBackStack(null).commit();
That way you don't have to keep track of which fragment is currenty showing.
The problem is that the transaction that you're backing from have two steps:
remove infoFrag
add detailsFrag (that is the first1 detail container that was added)
(we know that because the documentation This is essentially the same as calling remove(Fragment) for all currently added fragments that were added with the same containerViewId and then add(int, Fragment, String) with the same arguments given here. )
So whenever the system is reverting that one transaction is reverting exactly those 2 steps, and it say nothing about the last detailFrag that was added to it, so it doesn't do anything with it.
There're two possible work arounds I can think on your case:
Keep a reference on your activity to the last detailsFrag used and use the BackStackChange listener to whenever the value change from 1 to 0 (you'll have to keep track of previous values) you also remove that one remaining fragment
on every click listener you'll have to popBackStackImmediatly() (to remove the previous transaction) and addToBackStack() on all transactions. On this workaround you can also use some setCustomAnimation magic to make sure it all looks nice on the screen (e.g. use a alpha animation from 0 to 0 duration 1 to avoid previous fragment appearing and disappearing again.
ps. I agree that the fragment manager/transaction should be a bit more clever to the way it handles back stack on .replace() actions, but that's the way it does it.
edit:
what is happening is like this (I'm adding numbers to the details to make it more clear).
Remember that .replace() = .remove().add()
Transaction.remove(info).add(detail1).addToBackStack(null) // 1st time
Transaction.remove(detail1).add(detail2) // 2nd time
Transaction.remove(detail2).add(detail3) // 3rd time
Transaction.remove(detail3).add(detail4) // 4th time
so now we have detail4 on the layout:
< Press back button >
System pops the back stack and find the following back entry to be reversed
remove(info).add(detail1);
so the system makes that transaction backward.
tries to remove detail1 (is not there, so it ignores)
re-add(info) // OVERLAP !!!
so the problem is that the system doesn't realise that there's a detail4 and that the transaction was .replace() that it was supposed to replace whatever is in there.
You could just override onBackPressed and commit a transaction to the initial fragment.
I'm guessing but:
You've added the transaction to replace infoFrag with 1st detailsFrag into the backstack.
But then you replace 1st detailsFrag with 2nd detailsFrag.
At this point when you click back, the fragment manager cannot cleanly replace 1st detailsFrag with infoFrag as 1st detailsFrag has already been removed and replaced.
Whether the overlapping behaviour is expected or not I don't know.
I would suggest debugging the Android core code to see what it is doing.
I'm not sure whether you can achieve without say overriding Activity::onBackPressed() and doing the pops yourself having added all transactions to the backstack.

How to pop back stack for Activity with multiple Fragments?

Assume I have an Activity which contains two FrameLayouts (let's call them FrameA and FrameB) which in turn each contain a Fragment (let's call them FragmentA1 and FragmentB1 respectively). Now, I commit a series of individual fragment transactions using code similar to the following...
getFragmentManager()
.beginTransaction()
.replace(frameId, fragment)
.addToBackStack(null)
.commit();
... such that I replace FragmentA1 in FrameA with FragmentA2, then I replace FragmentB1 in FrameB with FragmentB2, then I replace FragmentA2 in FrameA with FragmentA3, then I replace FragmentB2 in Frame2 with FragmentB3, and the final state looks like the picture above (where only FragmentA3 and FragmentB3 are visible).
If I understood correctly how the back stack works, pressing 'back' will interleave popping of the Fragments between FrameA and FrameB (reflecting how I added them).
Does anyone know if it is possible to pop the last transaction on FrameA or FrameB selectively? (i.e. if I pressed 'Pop FrameA' then FrameA would be transitioned back from FragmentA3 to FragmentA2 and, instead, if I pressed 'Pop FrameB' then FrameB would be transitioned back from FragmentB3 to FragmentB2)
Supplement: I know I can get the Fragment last added to a given FrameLayout using the FragmentManager.findFragmentById(int framelayoutId) method, but calling FragmentTransaction.remove(fragment).commit() only removes the Fragment from the View and does not transition the View back to the Fragment it previously displayed.
Basically, no, there is only one back stack for an activity.
You will just need to implement your own separate back stacks.
As of Android 4.0 (and the associated support library) there are APIs that should make this relatively easy -- FragmentTransaction.detach(Fragment) lets you put a fragment into the same state it is when in the back stack, and FragmentManager.saveFragmentInstanceState(Fragment) lets you go further and completely throw away the Fragment object. Not coincidentally, these are used to implement ViewPager's FragmentPagerAdapter and FragmentStatePagerAdapter, respectively, so you could look at the code for these as an example of how to use them.
FragmentManager.popBackStack(String name, FragmentManager.POP_BACK_STACK_INCLUSIVE)
Here is the simplest answer, and the explanation is very clear: Well there are a few ways to go about this depending on the intended behavior, but this link should give you all the best solutions and not surprisingly is from Dianne Hackborn...

Categories

Resources