FragmentStatePagerAdapter not removing fragments if parent fragment removed - android

I am facing memory leak because some fragments are not removed. I have Activity that populates view with Fragment F1. This fragment has ViewPager and FragmentStatePagerAdapter associated with it. Adapter is feeling pager with another Fragment F2. Problem starts when in Activity I remove Fragment F1. Existing Fragments F2 are not removed.
any suggestions?

Not sure if it is the same problem, but some time ago I had a problem with viewpager where the fragment lifecycle methods were never called and the problem was with the fragment manager.
So, when you create the adapter try passing childFragmentManager instead of supportFragmentManager and see if it fixes your issue.

I have the exact same problem. Try to move your fragment F1 back into an activity. That might helps you determine if the problem is to have a fragment in a fragment. Android doesn't really like that. If I find something on my side, I will let you know

I was facing the same issue here. After researching for a long time about how to correct it in a "right" way I couldn't get it working. So I was forced to explicit release all F2s fragments before removing the F1 parent fragment (with a fragment transaction) from my activity.
// Remove all content from the FragmentStatePagerAdapter instance.
myAdapter.Content.Clear (); // Content here may be an ArrayList in Java or a List in C#.
myAdapter.NotifyDataSetChanged ();
// Reset current empty adapter inside the ViewPager (this will make all existing F2 fragments to be released).
myViewPager.Adapter = myAdapter;
// Do the transaction removing the parent F1 fragment from the main Activity.
FragmentManager.BeginTransaction ()...
By following these steps, all F2 fragments started being released form memory and they are also having their OnPause, OnDestroyView ... OnDestroy callbacks being invoked as well.

Related

FragmentManager popBackStack doesn't remove fragment

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.

What is the correct way to restore a fragment?

First of all I'm sorry if this explanation seems unclear, I'm new to Android.
I have a ViewPager in main activity showing fragments added dynamically by user. Fragments are created initially on activity start up and are added to ViewPager via Adapter i.e. adapter simply returns proper fragment and as I understand correctly fragment's content is created at this time when ViewPager 'retrieves' a fragment first time.
The problem is when main activity gets restored after orientation changing all fragments are resurrected as well and when Adapter tries to return newly created by user Fragment method createView() is no longer called and it fails with NullPointerException. It seems ViewPager retains fragments attached to it initially and doesn't call createView() for newly added ones for the same position.
I have a feeling I'm missing vital point on the Fragment lifecycle. I wouldn't like to change the design. My main question is what the correct way is to return a Fragment added to ViewPage after activity is restored? Is there any way to locate recently attached fragments?
If the fragment already exists, it will be re-used. However the state of the fragment will not. You should take a look at http://developer.android.com/training/basics/activity-lifecycle/recreating.html for more information.
In particular you should look at overriding onSavedInstanceState and onRestoreInstanceState as a method to repopulate the field that is generating that nullpointerexception
To determine if an instance of a fragment has already been created you can use 'findFragmentByTag' like so:
String fragmentTag = MyFramgment.getClass().getName();
MyFragment frag = getFragmentManager().findFragmentByTag(fragmentTag);
if(frag == null)
frag = new MyFragment();
Whatever ends up being referenced in 'frag', show this to the user.

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().

Call Fragments onCreateView in context with Tabs

I have 2 Tabs and 2 Corresponding Fragments. On calling the LAUNCH Activity both Tabs were added and then the first one added will be shown. Thus the first Fragments onCreateView is called the second Fragments ones not.
In my case this is an issue because the first Fragment has to call methods on the second Fragment. Inside the second Fragment there is an Objectreference which will be set by calling the onCreateView in the second Fragment.
Therefore I used following code snippet to solve this
actionBar.setSelectedNavigationItem(1);
actionBar.setSelectedNavigationItem(0);
It works but in my opinion there must be another possibility to solve this issue. Like calling the onCreateView of the second Fragment?
Here is the relevant code snippet. The listener is implemented as in android-dev Sample only with small changes not affecting my issue.
simplexFragment corresponds to the first Fragment
graphicFragment corresponds to the second Fragment
// adds two tabs
actionBar.addTab(actionBar.newTab().setText("Input").setTabListener(new TabListener(null, "input_fragment")));
graphicFragment = new GraphicFragment();
actionBar.addTab(actionBar.newTab().setText("Graphic").setTabListener(new TabListener(graphicFragment, "graphic_fragment")));
simplexFragment.setGraphics(graphicFragment); // sets the internal reference!
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
// selects the Tab so the GraphicFragments onCreateView will be called
actionBar.setSelectedNavigationItem(1);
actionBar.setSelectedNavigationItem(0);
Thanks for support.
In my case this is an issue because the first Fragment has to call methods on the second Fragment.
This is not how Fragments are meant to work. The idea is a Fragment should be self-contained and re-usable and one Fragment shouldn't know that another exists (or at least shouldn't rely the existence any other Fragment).
For example, suppose you have 3 Fragments and ActivityA uses FragmentA and FragmentB but you have another Activity (ActivityB) which uses FragmentA and FragmentC. In that case, FragmentA doesn't know what the other Fragment is (B or C) and shouldn't even expect there to be another Fragment at all.
The way to achieve what you want is to use callback methods on the Activity and have it perform the actions on any other Fragments (if they exist). It can do this by either calling public methods on the other Fragments or by passing data in the 'arguments' Bundle when it creates the other Fragments.

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.

Categories

Resources