24

I'm working with a FragmentStatePagerAdapter using this example.

The MyAdapter class is implemented as follows:

public static class MyAdapter extends FragmentStatePagerAdapter {
        public MyAdapter(FragmentManager fm) {
            super(fm);
        }

        @Override
        public int getCount() {
            return NUM_ITEMS;
        }

        @Override
        public Fragment getItem(int position) {
            return ArrayListFragment.newInstance(position);
        }
    }

The ListFragment class includes the following method to create a new instance:

    /**
     * Create a new instance of CountingFragment, providing "num"
     * as an argument.
     */
    static ArrayListFragment newInstance(int num) {
        ArrayListFragment f = new ArrayListFragment();

        // Supply num input as an argument.
        Bundle args = new Bundle();
        args.putInt("num", num);
        f.setArguments(args);

        return f;
    }

When I create a new fragment state pager adapter in my activity, getItem is called, which in turn calls the newInstance method in the ListFragment class. This is great when I want to create a new fragment.

But it's not clear to me how to modify getItem (if even needed) to get the fragment object when it already exists and the user pages from, for example, page 2 to page 1. I'd like my Activity to retrieve that existing, previously created fragment so that it can run an inner class AsyncMethod, which resides in the fragment class.

yprez
  • 14,854
  • 11
  • 55
  • 70
mraviator
  • 4,034
  • 9
  • 38
  • 51
  • 2
    `getItem()` has a weird name, it doesn't provide the functionality you're thinking of. Please check my own question on the same topic: http://stackoverflow.com/questions/10574114/referencing-fragments-inside-viewpager – Michał Klimczak May 18 '12 at 17:36
  • This looks very useful. But I'm using a ListFragment and was hoping to use getSelectedItemPosition instead of making my own getPosition. I need to play with it some more and see if I can figure out how to do it. – mraviator May 18 '12 at 18:46
  • I used ListFragment too, but it simply doesn't work this way. However if you find something, I'd be glad to know – Michał Klimczak May 19 '12 at 12:49
  • @mraviator Instead of newInstance(), why don't you create a getInstance() method in your ArrayListFragment - which returns a static singleton? – IgorGanapolsky May 28 '14 at 16:21
  • i was trying to do it with a singleton call to static getInstance() on my fragment. cant attach the same fragment again though – filthy_wizard Dec 17 '15 at 13:08

6 Answers6

44

I think the solution is much simpler than the answers provided.

If you take a look at the source of FragmentStatePagerAdapter.instantiateItem(), you'll notice that instantiateItem() handles this logic for you, and thus your implementation of getItem() should always return a new instance.

So in order to return an existing Fragment, simply do (assuming you're calling from ViewPager):

Fragment f = (Fragment) getAdapter().instantiateItem(this, getCurrentItem());
Flimm
  • 136,138
  • 45
  • 251
  • 267
imiric
  • 8,315
  • 4
  • 35
  • 39
  • 4
    The best answer was hiding at the bottom !!! public void onPageScrolled(int i, float v, int i2) { Fragment fragment = (Fragment)imagePagerAdapter.instantiateItem(mImageViewPager,i); – Ryan Heitner Oct 20 '13 at 18:41
  • 2
    Only after looking at the source did I realise the logic of the problem. And see that this is the only logical answer on the page. Thank you very much. – IT-Dan Jul 09 '14 at 13:27
  • This should be the selected answer (along with @RyanHeitner's comment on where to use this method). Thanks. – Firanto Dec 23 '16 at 10:53
9

While @imiric's solution is very concise, it still bothers me that it requires knowledge of the implementation of the class. My solution involves adding the following to your adapter, which should behave nicely with a FragmentStatePagerAdapter possibly destroying fragments:

    private SparseArray<WeakReference<Fragment>> mFragments = new SparseArray<>();

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        Fragment f = (Fragment) super.instantiateItem(container, position);
        mFragments.put(position, new WeakReference<>(f));  // Remember what fragment was in position
        return f;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        super.destroyItem(container, position, object);
        mFragments.remove(position);
    }

    public Fragment getFragment(int position) {
        WeakReference<Fragment> ref = mFragments.get(position);
        Fragment f = ref != null ? ref.get() : null;
        if (f == null) {
            Log.d(TAG, "fragment for " + position + " is null!");
        }
        return f;
    }
qix
  • 7,228
  • 1
  • 55
  • 65
  • 3
    You should not keep references to Fragments as they can be destroyed and recreated at any time. Your references will be invalid if that happens and the Fragments cannot be garbage collected. – wkarl Nov 18 '14 at 11:12
  • Indeed! Wouldn't updating that to use WeakReferences be sufficient though? – qix Nov 18 '14 at 21:40
6

I have implemented something similar to what you have. I extended the FragmentPagerAdapter class like so:

public class ContactsFragmentPagerAdapter extends FragmentPagerAdapter {
    ActionBar mActionBar;
    private List<Fragment> mFragments;

    public ContactsFragmentPagerAdapter(FragmentManager fm, List<Fragment> fragments) {
        super(fm);
        mFragments = fragments;
    }

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

    @Override
    public Fragment getItem(int position) {
        return mFragments.get(position);
    }

    public void setActionBar(ActionBar bar) {
        mActionBar = bar;
    }
}

Notice I have added an argument to the constructor to pass in the List of Fragment objects. This way the getItem() method of this class can return any class that extends Fragment or any of its subclasses and not just one specific class ArrayListFragment like you have done.

In the Activity where I instantiate my subclass of FragmentPagerAdapter I have passed in the list of Fragment objects:

Class the instantiates the FragmentPagerAdapter

public final class ContactManager extends Activity {
    private ContactsFragmentPagerAdapter mAdapter;
    private ViewPager mPager;
    public ActionBar mActionBar;

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.contact_manager);

        List<Fragment> fragments = new Vector<Fragment>();
        fragments.add(Fragment.instantiate(this, ContactsListFragment.class.getName()));
        fragments.add(Fragment.instantiate(this, GroupsListFragment.class.getName()));
        mAdapter = new ContactsFragmentPagerAdapter(this.getFragmentManager(), fragments);

        mPager = (ViewPager) findViewById(R.id.pager);
        mPager.setAdapter(mAdapter);

        mPager.setOnPageChangeListener(new OnPageChangeListener() {
            @Override
            public void onPageScrollStateChanged(int arg0) {}

            @Override
            public void onPageScrolled(int arg0, float arg1, int arg2) {}

            @Override
            public void onPageSelected(int arg0) {
                mActionBar.getTabAt(arg0).select();
            }
        });

    }

}

By accessing the variable "fragments", you can access a previously created Fragment so that you can run methods of that Fragment.

Etienne Lawlor
  • 6,817
  • 18
  • 77
  • 89
  • 19
    It would be nice if there were a solution that didn't keep all fragments always instantiated to work well with `FragmentStatePagerAdapter`, since that's really the whole point of that adapter. – blahdiblah Jan 17 '13 at 05:00
4
public class MyPagerAdapter extends FragmentStatePagerAdapter {
    final Context m_context;
    final WeakReference<Fragment>[] m_fragments;

    public DetailPagerAdapter(FragmentManager fm) {
        super(fm);
        m_context = ...
                m_fragments = new WeakReference[enter size here];
    }

    @Override
    public Fragment getItem(int position) {
        final Fragment fragment = instantiate your fragment;
        m_fragments[position] = new WeakReference<Fragment>(fragment);
        return fragment;
    }

    @Override
    public int getCount() {
        return ...
    }

    public Fragment getFragment(final int position) {
        return m_fragments[position] == null ? null :
            m_fragments[position].get();
    }
}
jnthnjns
  • 8,962
  • 4
  • 42
  • 65
farid_z
  • 1,673
  • 21
  • 11
0

Do not need modify the FragmentPagerAdapter, because fragments are cached by the FragmentManager. So you must need find inside it. Use this function to find the fragment by pager adapter position.

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
  • Is this included in the sample code for the support api or did you write it yourself for the support api? (If it's there, I would be at least 'semi-official') – Patrick Jun 18 '13 at 17:47
  • I write this by myself, based on the source code of the Android API, and I meaning by "sample code for v4" is because this code use "getSupportFragmentManager" that is available when you use the support library, In case you do not use the 'support library', you will need at least change "getSupportFragmentManager" for "getFragmentManager". FYI: http://developer.android.com/tools/extras/support-library.html – Daniel De León Jun 18 '13 at 18:04
  • 1
    Thx. I hoped they had made it official somehow. This could break in the future... Ah well, but thanks! – Patrick Jun 18 '13 at 18:05
  • Don't worry about that, because Android is based on Java philosophi. "Write once, run anywhere"... forever. – Daniel De León Jun 18 '13 at 18:09
0

I think this could help:

class HomePagerAdapter(fragmentManager: FragmentManager) : FragmentStatePagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {

    private val fragmentConstructors: List<() -> Fragment> by lazy {
        listOf(
                ::AuctionLotsFragment,
                ::ChatListFragment,
                ::MarketHomeFragment,
                ::WalletSectionsFragment,
                ::SettingsFragment
        )
    }

    private val fragments: HashMap<Int, WeakReference<Fragment>> by lazy {
        HashMap()
    }

    override fun getCount(): Int = fragmentConstructors.size

    override fun getItem(position: Int): Fragment {
        return fragments[position]?.get() ?: this.createFragmentForPosition(position)
    }

    private fun createFragmentForPosition(position: Int): Fragment {
        val fragment = fragmentConstructors[position].invoke()
        fragments[position] = WeakReference(fragment)
        return fragment
    }

}
Tamim Attafi
  • 2,253
  • 2
  • 17
  • 34