Support Fragmentpageradapter Holds Reference to Old Fragments

support FragmentPagerAdapter holds reference to old fragments

You are running into a problem because you are instantiating and keeping references to your fragments outside of PagerAdapter.getItem, and are trying to use those references independently of the ViewPager. As Seraph says, you do have guarantees that a fragment has been instantiated/added in a ViewPager at a particular time - this should be considered an implementation detail. A ViewPager does lazy loading of its pages; by default it only loads the current page, and the one to the left and right.

If you put your app into the background, the fragments that have been added to the fragment manager are saved automatically. Even if your app is killed, this information is restored when you relaunch your app.

Now consider that you have viewed a few pages, Fragments A, B and C. You know that these have been added to the fragment manager. Because you are using FragmentPagerAdapter and not FragmentStatePagerAdapter, these fragments will still be added (but potentially detached) when you scroll to other pages.

Consider that you then background your application, and then it gets killed. When you come back, Android will remember that you used to have Fragments A, B and C in the fragment manager and so it recreates them for you and then adds them. However, the ones that are added to the fragment manager now are NOT the ones you have in your fragments list in your Activity.

The FragmentPagerAdapter will not try to call getPosition if there is already a fragment added for that particular page position. In fact, since the fragment recreated by Android will never be removed, you have no hope of replacing it with a call to getPosition. Getting a handle on it is also pretty difficult to obtain a reference to it because it was added with a tag that is unknown to you. This is by design; you are discouraged from messing with the fragments that the view pager is managing. You should be performing all your actions within a fragment, communicating with the activity, and requesting to switch to a particular page, if necessary.

Now, back to your problem with the missing activity. Calling pagerAdapter.getItem(1)).update(id, name) after all of this has happened returns you the fragment in your list, which has yet to be added to the fragment manager, and so it will not have an Activity reference. I would that suggest your update method should modify some shared data structure (possibly managed by the activity), and then when you move to a particular page it can draw itself based on this updated data.

How to get existing fragments when using FragmentPagerAdapter

Summary of the problem

Note: In this answer I'm going to reference FragmentPagerAdapter and its source code. But the general solution should also apply to FragmentStatePagerAdapter.

If you're reading this you probably already know that FragmentPagerAdapter/FragmentStatePagerAdapter is meant to create Fragments for your ViewPager, but upon Activity recreation (whether from a device rotation or the system killing your App to regain memory) these Fragments won't be created again, but instead their instances retrieved from the FragmentManager. Now say your Activity needs to get a reference to these Fragments to do work on them. You don't have an id or tag for these created Fragments because FragmentPagerAdapter set them internally. So the problem is how to get a reference to them without that information...

Problem with current solutions: relying on internal code

A lot of the solutions I've seen on this and similar questions rely on getting a reference to the existing Fragment by calling FragmentManager.findFragmentByTag() and mimicking the internally created tag: "android:switcher:" + viewId + ":" + id. The problem with this is that you're relying on internal source code, which as we all know is not guaranteed to remain the same forever. The Android engineers at Google could easily decide to change the tag structure which would break your code leaving you unable to find a reference to the existing Fragments.

Alternate solution without relying on internal tag

Here's a simple example of how to get a reference to the Fragments returned by FragmentPagerAdapter that doesn't rely on the internal tags set on the Fragments. The key is to override instantiateItem() and save references in there instead of in getItem().

public class SomeActivity extends Activity {
private FragmentA m1stFragment;
private FragmentB m2ndFragment;

// other code in your Activity...

private class CustomPagerAdapter extends FragmentPagerAdapter {
// other code in your custom FragmentPagerAdapter...

public CustomPagerAdapter(FragmentManager fm) {
super(fm);
}

@Override
public Fragment getItem(int position) {
// Do NOT try to save references to the Fragments in getItem(),
// because getItem() is not always called. If the Fragment
// was already created then it will be retrieved from the FragmentManger
// and not here (i.e. getItem() won't be called again).
switch (position) {
case 0:
return new FragmentA();
case 1:
return new FragmentB();
default:
// This should never happen. Always account for each position above
return null;
}
}

// Here we can finally safely save a reference to the created
// Fragment, no matter where it came from (either getItem() or
// FragmentManger). Simply save the returned Fragment from
// super.instantiateItem() into an appropriate reference depending
// on the ViewPager position.
@Override
public Object instantiateItem(ViewGroup container, int position) {
Fragment createdFragment = (Fragment) super.instantiateItem(container, position);
// save the appropriate reference depending on position
switch (position) {
case 0:
m1stFragment = (FragmentA) createdFragment;
break;
case 1:
m2ndFragment = (FragmentB) createdFragment;
break;
}
return createdFragment;
}
}

public void someMethod() {
// do work on the referenced Fragments, but first check if they
// even exist yet, otherwise you'll get an NPE.

if (m1stFragment != null) {
// m1stFragment.doWork();
}

if (m2ndFragment != null) {
// m2ndFragment.doSomeWorkToo();
}
}
}

or if you prefer to work with tags instead of class member variables/references to the Fragments you can also grab the tags set by FragmentPagerAdapter in the same manner:
NOTE: this doesn't apply to FragmentStatePagerAdapter since it doesn't set tags when creating its Fragments.

@Override
public Object instantiateItem(ViewGroup container, int position) {
Fragment createdFragment = (Fragment) super.instantiateItem(container, position);
// get the tags set by FragmentPagerAdapter
switch (position) {
case 0:
String firstTag = createdFragment.getTag();
break;
case 1:
String secondTag = createdFragment.getTag();
break;
}
// ... save the tags somewhere so you can reference them later
return createdFragment;
}

Note that this method does NOT rely on mimicking the internal tag set by FragmentPagerAdapter and instead uses proper APIs for retrieving them. This way even if the tag changes in future versions of the SupportLibrary you'll still be safe.


Don't forget that depending on the design of your Activity, the Fragments you're trying to work on may or may not exist yet, so you have to account for that by doing null checks before using your references.

Also, if instead you're working with FragmentStatePagerAdapter, then you don't want to keep hard references to your Fragments because you might have many of them and hard references would unnecessarily keep them in memory. Instead save the Fragment references in WeakReference variables instead of standard ones. Like this:

WeakReference<Fragment> m1stFragment = new WeakReference<Fragment>(createdFragment);
// ...and access them like so
Fragment firstFragment = m1stFragment.get();
if (firstFragment != null) {
// reference hasn't been cleared yet; do work...
}

ViewPager holds fragment's instance even after setting a new adapter

I found the solution.

The solution is very simple. I just had to set manually null to the Child's adapter. With this, the ViewPager is forced to destroy every fragment.

So into the onDestroyView of Fragment's father I added:

@Override
public void onDestroyView() {
super.onDestroyView();
mViewpagerChild.removeOnPageChangeListener(mOnPaymentMethodsPageChangeListener);
mViewpagerChild.setAdapter(null); // <-- This is what I added
}

FragmentPagerAdapter doesn't recreate Fragments on orientation change?

Then I was debugging to see when FragmentPagerAdapter invokes getItem() and it happens the method is not called after orientation change event. So looks like it ViewPager holds references to old Fragments.

The fragments should be automatically recreated, just like any fragment is on an configuration change. The exception would be if you used setRetainInstance(true), in which case they should be the same fragment objects as before.

So, is there any way to enforce the ViewPager to call getItem() again, so I can use proper references to current Fragments?

What is wrong with the fragments that are there?

How to destroy old fragments in FragmentStatePagerAdapter

You should not try to interfere with how Android manages your Fragment implementations. The default for the setOffScreenPageLimit should already be one. This means that Android will destroy old fragments when memory runs low. As long as you do not have a memory issue, just leave it be.

The reason why your memory increases is because Android keeps Fragment instances in memory to be able to reconnect to them instead of having to instantiate them. I recommend you account for the contingency of your Fragment instances being destroyed by the OS, saving their state if that happens, and let the OS do its job.

The delay you are experiencing could be due to some intensive computation on the UI thread. If it is, I suggest moving that out to, for example, an AsyncTask. Without the code it is, however, just a guess as to what might cause the issue. But there being only an initial delay suggests that you are loading something which might block the UI thread.

Update: Have a look at https://stackoverflow.com/a/9646622/170781 which outlines very neatly how the ViewPager handles Fragment instances.

ViewPager and FragmentPagerAdapter with Fragments (not support library)

ViewPager is a Java class. It can be found in the android-support-v4 and android-support-v13 libraries. android-support-v13 is a superset of android-support-v4, adding in some classes that are only relevant if your android:minSdkVersion is 13 or higher, such as the native fragment version of FragmentPagerAdapter.

My guess is that you think that you need both android-support-v4 and android-support-v13, which is incorrect -- you only need android-support-v13.

You are, of course, welcome to roll your own PagerAdapter implementation that happens to use native fragments.

What causes a fragment created by a FragmentPagerAdapter to still be known to FragmentManager after the adapter calls onDestroy?

I found out that's why it's FragmentPagerAdapter and not FragmentStatePagerAdapter.

Duh!



Related Topics



Leave a reply



Submit