Is there any way, how to clear backStack of support FragmentManager without calling onCreateView() in stored fragments?
I understand fragment lyfe cycle and calling onDestroyView() and onCreateView() when it is popped.
http://developer.android.com/guide/components/fragments.html#Creating
Also I know how to pop all fragments from backstack with
mFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
or
for(int i = 0; i < mFragmentManager.getBackStackEntryCount(); ++i) {
mFragmentManager.popBackStack();
}
but both ways are calling onCreateView() and other lyfe cycle methods until to onDestroyView() and onDestroy().
But is there any way, how to clear this backstack with calling only from onDestroyView() and not from onCreateView() (inside of fragments)?
Or is there any way how to do replace transaction with clearing previous fragments?
For example, I want clear backstact before I do transaction:
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
fragmentManager.beginTransaction().replace(R.id.content, fragment).commit();
I haven't found a simple solution to this issue. I'm almost certain there is no feature of FragmentManager or Fragment that allows you to control which lifecycle method are called when a fragment is popped from the stack. I'll outline two possible approaches. Each has some undesirable aspects.
The first approach assumes the fragments you are popping out of the backstack are children of an activity. If they are children of a fragment, the method still applies, just a different type of parent object.
Add a boolean member mClearingBackStack to the activity (or parent fragment) with a getter method. Set the boolean only when you are starting a complete clear of the backstack. In the fragment lifecycle methods where you want to disable processing, get the flag and modify the processing accordingly. For onCreateView() through onDestroyView(), the fragment will be attached and the host activity available with getActivity(). Cast it to whatever your activity class is to use the flag's getter method.
Because popBackStack() is asynchronous, clearing the flag must be done only after the stack unwinding completes. I haven't tried it, but I think posting a Runnable after calling popBackStack() to clear the flag should work. Because the Runnable needs to go at the end of the queue, View.post() must be used instead of Activity.runOnUiThread(). An alternative is to call executePendingTransactions() to wait for the stack unwinding to complete.
The second approach is cleaner, if your design can accommodate it. Create a place-holder fragment that is a child of your activity and parent to all your other fragments. For all the fragment transactions you have now, use the new fragment's FragmentManager, obtained using getChildFragmentManager(). When you want to clear all those transactions, instead of popping the child fragment manager's stack, remove or replace the parent fragment in the activity's fragment manager. When the parent fragment is removed, all of its children are destroyed and go through the teardown steps, onDestroyView(), onDestroy(), etc. but not all the steps that would occur if its backstack were unwound. This approach is much simpler and more maintainable than the first, but requires you to rework your fragment hierarchy. An additional problem with this approach is that you must add some code to handle the Back action with a fragment hierarchy. The problem and various solutions are described here.
Related
I am adding and removing Views to/from my Activity dynamically. Each of these Views is assigned an id and acts as a container for a particular Fragment. I add a Fragment to each one of these Views with conditional logic as follows:
if (supportFragmentManager.findFragmentById(R.id.someView) == null) {
supportFragmentManager.beginTransaction()
.add(R.id.someView, SomeFragment())
.commit()
}
This conditional logic ensures that a given View only has a Fragment added to it once during the lifetime of the Activity.
This logic works fine except when the Activity is recreated (due to a configuration change for example). When the Activity is recreated, the Views are not automatically recreated but the Fragments appear to survive the recreation. (I see that the Fragments have survived the recreation because the supportFragmentManager.findFragmentById(id:) calls return a non-null Fragment.)
I find that if I re-add Views to my Activity in the Activity.onCreate(savedInstanceState:) method, then the retained Fragments re-attach fine to the Views and everything is fine. However, if I delay adding the Views to a later point in the Activity lifecycle, then the Fragments do not re-attach to the Views (and the Views show up as blank).
Ultimately, this leads to confusing logic in my Activity.onCreate(savedInstanceState:) method when savedInstanceState is non-null to work around this. Either I have to re-add Views as they were at the point when the Activity was destroyed (I would prefer to do this elsewhere in the Activity) or I have to call FragmentTransaction.remove(fragment:) to remove each Fragment which survived the recreation.
Is there a way to add a Fragment to an Activity such that the Fragment does not survive Activity recreation? I see in the deprecation notice for the Fragment.setRetainInstance(retain:) method that the guidance is: "Instead of retaining the Fragment itself, use a non-retained Fragment and keep retained state in a ViewModel attached to that Fragment." However, this guidance does not give any instruction on how to define a non-retained Fragment.
There are a couple of dimensions to this answer.
Firstly, I could not find any documentation or any methods in the FragmentManager or FragmentTransaction classes which offer a means of creating a non-retained Fragment. The documentation in the deprecated Fragment.setRetainInstance(retain:) method says to use a "non-retained Fragment" but I could not find anywhere that explains what this means.
Secondly, the workaround for this problem is to remove the retained Fragment in the containing Activity's onCreate(savedInstanceState:) method so that the problematic Fragment can be recreated and attached to its containing view in a later lifecycle method, as follows:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.some_activity)
supportFragmentManager.findFragmentById(R.id.someView)?.let {
supportFragmentManager.beginTransaction().remove(it).commit()
}
}
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
Update:
I think the leak is coming from getActivity().getSupportLoaderManager().restartLoader(getLoaderId(), null, this);
where i have my object implement LoaderCallback. Is there a way for me to clear the callback i tired setting it to
getActivity().getSupportLoaderManager().restartLoader(getLoaderId(), null, null);
but this crashes
Orig:
I have a list of objects in one of my fragments(A). When I navigate forward I add fragment A to the backstack. After I have navigated to a new fragment and I dump the heap. I still see my object in the heap. When I get the shortest path in the dump it looks like below. I can see that in FragmentManagerImpl there is a reference to fragment A in mActive fragments which is keeping my lists object alive.
Is my fragment supposed to stay in mActive fragments or is this a leak?
Adding to backstack
FragmentTransaction transaction = mFragmentManager.beginTransaction();
updateTransactionWith(info.getReplacement(), transaction, "replace");
transaction.addToBackStack(info.getReplacement().getClass().toString());
transaction.commit();
mFragmentManager.executePendingTransactions();
By calling addToBackStack(), you're requesting the FragmentManager that the Fragment being replaced be just stopped and not destroyed because you're either anticipating that a back button press is very likely or, the Fragment is heavy on initialization and you would still like to avoid doing it again even though the user is not very likely to go back.
The docs clearly state that
If you do not 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 will be resumed if the user navigates
back.
Hence, it's not a memory leak and your observations are quite in line with the expected behaviour.
However, just like an Activity, the system may still choose to destroy this Fragment, if it's running out of memory. But, that's expected behaviour too.
It's not a memory leak. You need to decide how to deal with your fragment's state.
Ideally you implement onSaveInstanceState and onViewStateRestored saving your state to the bundle and restoring it from the bundle respectively.
Alternatively, if you're able to re-create your state easily, you may want to save the bother of (re)storing it using the bundle and just null your references in the onPause method and create them during the onResume method. Be aware that onResume gets called even if the fragment has just been created, so be careful not to do that work more than once.
Either way, be sure to null your references to ensure your objects are marked for GC.
The FragmentManager will decide if it needs to discard and recreate the fragment as necessary in order to allow the user to go back to the fragment you added to the stack. In conditions where there's very little else on the stack and/or there's lots of spare memory it will probably just keep a direct reference to the fragment you added to the back stack.
Given all that, you also need to be careful about keeping references to other fragments, activities, etc as that kind of state is difficult to recreate.
The following approach is recommended for providing proper back navigation:
// Works with either the framework FragmentManager or the
// support package FragmentManager (getSupportFragmentManager).
getSupportFragmentManager().beginTransaction()
.add(detailFragment, "detail")
// Add this transaction to the back stack
.addToBackStack()
.commit();
More info:
http://developer.android.com/guide/components/fragments.html
http://developer.android.com/training/implementing-navigation/temporal.html
API docs: http://developer.android.com/reference/android/app/Fragment.html
I want to replace fragment and destroy previous.
Here my code :
FragmentManager manager = getFragmentManager();
FragmentTransaction newT = manager.beginTransaction();
newT.replace(R.id.activity_content currentFragment, CE_TAG);
newT.commit();
I read this article : http://sapandiwakar.in/replacing-fragments/
I've not put the fragment in back stack, but my fragement are not destroyed !
onPause(), onDestroy(), onStop() are never called.
Thanks.
Try
Fragment f = new YOurFragment();
fragmentTransaction.remove(f).commit();
Hope it helps
A fragment is maintained in memory even after it is no longer visible. If you call it back into existence, onCreateView is called and the visual part of the fragment is recreated but the actual object is reused. From the Docs:
onDestroy() = The final call you receive before your activity is
destroyed. This can happen either because the activity is finishing
(someone called finish() on it, or because the system is temporarily
destroying this instance of the activity to save space. You can
distinguish between these two scenarios with the isFinishing() method.
You should consider onDestroyView instead, or call finish if that suits you better.
http://developer.android.com/reference/android/app/Fragment.html
Try adding this line of code after committing the transaction.
getChildFragmentManager().executePendingTransactions();
Check this executePendingTransactions.
If you are replacing a fragment with the same tag, it may cause the issue of overlaping. Also, if your fragment container is a LinearLayout, it cannot update after replace (from my experience only).
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.