70

I have a viewpager that pages through fragments. My FragmentPagerAdapter subclass creates a new fragment in the getItem method which seems wasteful. Is there a FragmentPagerAdapter equivalent to the convertView in the listAdapter that will enable me to reuse fragments that have already been created? My code is below.

public class ProfilePagerAdapter extends FragmentPagerAdapter {

    ArrayList<Profile> mProfiles = new ArrayList<Profile>();

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

    /**
     * Adding a new profile object created a new page in the pager this adapter is set to.
     * @param profile
     */
    public void addProfile(Profile profile){
        mProfiles.add(profile);
    }

    @Override
    public int getCount() {
        return mProfiles.size();
    }

    @Override
    public Fragment getItem(int position) {
        return new ProfileFragment(mProfiles.get(position));
    }
}
Nisse Engström
  • 4,738
  • 23
  • 27
  • 42
jwanga
  • 4,166
  • 4
  • 26
  • 27

6 Answers6

101

The FragmentPagerAdapter already caches the Fragments for you. Each fragment is assigned a tag, and then the FragmentPagerAdapter tries to call findFragmentByTag. It only calls getItem if the result from findFragmentByTag is null. So you shouldn't have to cache the fragments yourself.

JJD
  • 50,076
  • 60
  • 203
  • 339
Geoff
  • 2,892
  • 1
  • 22
  • 14
  • 4
    Bat I think that this is not solution. I have similar problem. I have 10 screens in ViewPager which all contain the same fragment only with different arguments. I don't need 10 instances. 3 is enought. When user scroll to right, most left fragment can be reused. – ATom Dec 30 '12 at 08:47
  • @ATom - when you scroll, fragments further than +/- 1 index are destroyed and recreated when you navigate to them anyway. Try putting a log output into the `onCreateView`method to see when each fragment is instantiated – Matt Taylor Jan 14 '13 at 13:16
  • 2
    @MattTaylor if you are using a ViewPager you can use setOffscreenPageLimit to keep the same instances – An-droid Jul 10 '13 at 13:57
  • @MattTaylor - this is slightly incorrect. The fragment's themselves should not be destroyed (unless your app is running low on available memory), however that is different than the fragment views being destroyed (which will happen as soon as the fragment in question is beyond the OffscreenPageLimint). Add a 2nd log statement to your Fragment's onCreate and you'll see it should not called every time onCreateView gets called. – Geoff Mar 25 '14 at 20:54
  • Fragment onCreateView method is the place where the data is set to the view. If getItem didn't called every time to create a new fragment. how the data for another Profile going to be set on a reused fragment!? – hasan May 05 '14 at 11:15
  • Also there is a getView method in the Fragment class and we can override it. shall we do this to reuse the same fragment with different data? – hasan May 05 '14 at 11:16
  • 1
    There's still no solution, right? Each solution still holds 10 fragments in memory after all. Instead of only 2 or 3. – Matthias Jul 06 '15 at 22:38
  • it is important to mention that instances of your fragments should be committed to the `FragmentPagerAdapter` cache using `finishUpdate` method as described here: http://stackoverflow.com/questions/14035090/how-to-get-existing-fragments-when-using-fragmentpageradapter/41345283#41345283 – morgwai Jan 09 '17 at 05:35
68

Appendix for Geoff's post:

You can get reference to your Fragment in FragmentPagerAdapter using findFragmentByTag(). The name of the tag is generated this way:

private static String makeFragmentName(int viewId, int index)
{
     return "android:switcher:" + viewId + ":" + index;
}

where viewId is id of ViewPager

Look at this link: http://code.google.com/p/openintents/source/browse/trunk/compatibility/AndroidSupportV2/src/android/support/v2/app/FragmentPagerAdapter.java#104

Wonil
  • 6,364
  • 2
  • 37
  • 55
petrnohejl
  • 7,581
  • 3
  • 51
  • 63
  • 20
    Note for people like me: viewId is the id of the ViewPager – nspo Apr 02 '13 at 12:34
  • @Kopfgeldjaeger I saw you code `getFragmentByPosition`. What is the position of fragment? – gkiko May 26 '14 at 22:31
  • Seconding @GregEnnis does not work with State pager, any thoughts? – trippedout Feb 18 '15 at 15:50
  • 1
    @trippedout @GregEnnis try [**my answer**](http://stackoverflow.com/a/29287415/708906) below. You can save references to the `Fragments` created by `FragmentStatePagerAdapter` and use those instead of trying to find them with `tags` (which `FragmentStatePagerAdapter` doesn't use). – Tony Chan Mar 26 '15 at 19:44
  • first, the code in this answer depends on compatibility with the internal naming: if this mechanism changes, this code will stop to work. Second there's really no reason to obtain refs to Fragments by tag as you can simply store them when you call `instantiateItem` in your `onCreate` as described here: http://stackoverflow.com/questions/14035090/how-to-get-existing-fragments-when-using-fragmentpageradapter/41345283#41345283 – morgwai Jan 09 '17 at 05:40
24

Seems a lot of the people viewing this question are looking for a way to reference the Fragments created by FragmentPagerAdapter/FragmentStatePagerAdapter. I would like to offer my solution to this without relying on the internally created tags that the other answers on here use.

As a bonus this method should also work with FragmentStatePagerAdapter. See notes below for more detail.


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...
}
Tony Chan
  • 8,017
  • 4
  • 50
  • 49
  • The first strategy he suggested with `m1stFragment = (FragmentA) createdFragment;` worked for me and I think it is the best solution here. For some reason the alternate strategy `String firstTag = createdFragment.getTag();` did not work for me. Thank you so much @Turbo, this saved me. – Dick Lucas Aug 20 '15 at 14:03
  • 1
    RE: problems with current solutions... It's worth noting, that if you're using the support library for fragments, then you're not actually relying on the device's "internal code" to do this. You'd be relying on the support library code which is shipped with your app (and you can even check the source into your repo if you're paranoid or need to make changes). – Geoff Feb 02 '16 at 16:20
  • 1
    @geoff good point, but is this even a documented feature? Not to mention it looks bad - you have nice, meaningful function-based tags you define yourself... and then you have this. – kaay Apr 20 '16 at 12:19
  • as described in http://stackoverflow.com/questions/14035090/how-to-get-existing-fragments-when-using-fragmentpageradapter/41345283#41345283 there's really no reason to override `instantiateItem` to store references as you are supposed to _call_ `instantiateItem` in your `onCreate` so you can store a reference there by the way. – morgwai Jan 09 '17 at 05:28
17

If the fragment still in memory you can find it with this function.

public Fragment findFragmentByPosition(int position) {
    FragmentPagerAdapter fragmentPagerAdapter = getFragmentPagerAdapter();
    return getSupportFragmentManager().findFragmentByTag(
            "android:switcher:" + getViewPager().getId() + ":"
                    + fragmentPagerAdapter.getItemId(position));
}

Sample code for v4 support api.

Daniel De León
  • 13,196
  • 5
  • 87
  • 72
  • first, the code in this answer depends on compatibility with the internal naming: if this mechanism changes, this code will stop to work. Second there's really no reason to obtain refs to Fragments by tag as you can simply store them when you call instantiateItem in your onCreate as described here: http://stackoverflow.com/questions/14035090/how-to-get-existing-fragments-when-using-fragmentpageradapter/41345283#41345283 – morgwai Jan 09 '17 at 05:42
1

For future readers!

If you are thinking of reusing fragments with viewpager, best solution is to use ViewPager 2, since View Pager 2 make use of RecyclerView.

user158
  • 12,852
  • 7
  • 62
  • 94
0

I know this is (theoretically) not an answer to the question, but a different approach.

I had an issue where I needed to refresh the visible fragments. Whatever I tried, failed and failed miserably...

After trying so many different things, I have finally finish this using BroadCastReceiver. Simply send a broadcast when you need to do something with the visible fragments and capture it in the fragment.

If you need some kind of a response as well, you can also send it via broadcast.

cheers

Sabo
  • 1,635
  • 1
  • 19
  • 24