70

I have an app with a ViewPager and three Fragments. I'm trying to figure out how to get the current Fragment being viewed so I can get at its arguments.

I have an OnPageChangeListener grabbing the current page index, but

ViewPager.getChildAt(int position);

returns a View. What's the relationship between this View and the current Fragment?

Suragch
  • 484,302
  • 314
  • 1,365
  • 1,393
MitchellSalad
  • 4,171
  • 8
  • 23
  • 24

11 Answers11

46

I finally found an answer that worked for me. Basically, you can access the fragment for a viewPager page by using the tag "android:switcher:"+R.id.viewpager+":0".

Community
  • 1
  • 1
James
  • 1,736
  • 2
  • 23
  • 25
  • 27
    The method in the linked answer doesn't work with FragmentStatePagerAdapter, but the [second answer](http://stackoverflow.com/a/8886019/237820) to the same linked question has a method that does. – Adam L. Aug 06 '12 at 21:59
  • 4
    Whilst this may theoretically answer the question, [it would be preferable](http://meta.stackoverflow.com/q/8259) to include the essential parts of the answer here, and provide the link for reference. –  Apr 15 '15 at 19:37
18

I've solved this problem the other way round. Instead of searching for the fragment from the activity, I'm registering the Fragment during it's onAttach() method at it's owner activity and de-registering it in the onStop() method. Basic Idea:

Fragment:

@Override
public void onAttach(Activity activity) {
    super.onAttach(activity);
    try{
        mActivity = (IMyActivity)activity;
    }catch(ClassCastException e){
        throw new ClassCastException(activity.toString() +" must be a IMyActivity");
    }

    mActivity.addFragment(this);
}

@Override
public void onStop() {
    mActivity.removeFragment(this);
    super.onStop();
}

IMyActivity:

public interface IFriendActivity {
    public void addFragment(Fragment f);
    public void removeFragment(Fragment f); 
}

MyActivity:

public class MyActivity implements IMyActivity{

    [...]

    @Override
    public void addFragment(Fragment f) {
        mFragments.add(f);
    }

    @Override
    public void removeFragment(Fragment f) {
        mFragments.remove(f);
    }

}
sdl
  • 181
  • 1
  • 3
5

Edit - Don't do this. If you're tempted to, read the comments for why it's a bad idea.

On the odd-chance you're still trying to solve this problem:

Extend FragmentPagerAdapter. In the constructor, build the Fragments you need and store them in a List (array/ArrayList) of Fragments.

private final int numItems = 3;
Fragment[] frags;

public SwipeAdapter (FragmentManager fm) {
    super(fm);

    //Instantiate the Fragments
    frags = new Fragment[numItems];

    Bundle args = new Bundle();
    args.putString("arg1", "foo");

    frags[0] = new MyFragment();
    frags[1] = new YourFragment();
    frags[2] = new OurFragment();
    frags[2].setArguments(args);
}

Then for getItem(int position), you can do something like

public Fragment getItem(int position) {
    return frags[position];
}

I'm not sure if this is the generally accepted way of doing it but it worked for me.

Edit

This is really not a good way to go. If you plan on handling orientation changes or your app going into the background, then this will probably break your code. Please read the comments below this answer for more info. Rather use @James 's answer

Mike T
  • 4,747
  • 4
  • 32
  • 52
  • 1
    your solution is fine if pre-creating every fragment isn't an objection. don't underestimate how costly your fragments can be though. – Rene Feb 27 '13 at 13:44
  • 2
    Good point. I've also had bad issues with this way when Activities restart, and the Fragments' `getActivity()` doesn't return the current activity, but some previous instance which has been destroyed – Mike T Feb 28 '13 at 05:53
  • 4
    This works until you rotate the device. Then the fragments will survive the rotation, but your adapter's cache won't. Now when you ask for the currently shown fragment you will create a new fragment that isn't used at all. – Mark Gjøl Mar 05 '13 at 09:33
  • 2
    Agreed. I don't think this is a great approach now that I've learned a bit more. Many apps lock screen orientation (such as games) but even then, if the app goes to the background and then comes back, the same problem will happen I think. – Mike T Mar 05 '13 at 18:39
  • 1
    Mike T is right, this does not work proper when rotating the screen because the ViewPager will recreate the fragments. And that is exactly why i came searching for this. This isn't a right answer. – Rolf ツ Jun 12 '13 at 17:38
  • 3
    @RolfSmit: I agree. I'm just leaving it up so that people read the discussion, and potentially for people who lock screen orientation – Mike T Jun 14 '13 at 06:47
4

Yes, it's possible if you are using FragmentStatePagerAdapter.

ViewPager vp;
//...
YourFragment fragment = (YourFragment) adapter.instantiateItem(vp, vp.getCurrentItem());
Volodymyr Kulyk
  • 6,455
  • 3
  • 36
  • 63
3

PLEASE DON'T USE THIS

Make your adapter extend the following FragmentStatePagerWithCurrentAdapter class and instead of implementing getItem implement the same code into getItemAtIndex

Set the ViewPager OnPageChangeListener, to the instance of the adapter.

When you need to access the current Fragment you just call adapter.getCurrentItem().

package your.package;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.view.ViewPager.OnPageChangeListener;
import android.util.SparseArray;
import android.view.ViewGroup;

public abstract class FragmentStatePagerWithCurrentAdapter 
extends FragmentStatePagerAdapter 
implements OnPageChangeListener {
    int currentPage = 0;

    private SparseArray<Fragment> mPageReferenceMap = new SparseArray<Fragment>();

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

    @Override
    public final Fragment getItem(int index) {
        Fragment myFragment = getItemAtIndex(index);
        mPageReferenceMap.put(index, myFragment);
        return myFragment;
    }

    public abstract Fragment getItemAtIndex(int index);

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {

        super.destroyItem(container, position, object);

        mPageReferenceMap.remove(Integer.valueOf(position));
    }

    public Fragment getCurrentItem() {
        return mPageReferenceMap.get(currentPage);
    }

    @Override
    public void onPageScrollStateChanged(int arg0) {

    }

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

    }

    @Override
    public void onPageSelected(int newPageIndex) {
        currentPage = newPageIndex;
    }

}

I used as reference the following blog post: http://tamsler.blogspot.com/2011/11/android-viewpager-and-fragments-part-ii.html

Jorge Garcia
  • 680
  • 6
  • 12
  • 3
    This is not right. In case of device rotation, the system will recreate the fragment by calling the constructor of the fragment without going through PagerAdapter.getItem. The only viable alternative is to save the fragment object in Fragment.onAttach like sdl shows. – Stephen Cheng Nov 26 '14 at 20:50
  • @StephenCheng You are right. I had a fixed portrait app when I used this, but then discovered the issue you mention when the activity was killed due to memory pressure and then the state was reloaded. I ended up rewriting this code to a much more complex one to handle all the scenarios we needed including this one. – Jorge Garcia Nov 27 '14 at 21:13
1

You can do so: - On the class extent of a view pager adapter (such as PagerAdapter , FragmentStatePagerAdapter...) override method instantiateItem :

@Override
public Object instantiateItem(ViewGroup container, int position) {
        final Fragment frag = (Fragment) super.instantiateItem(container, position);
        if(frag instanceof ListNoteOfTypeFragment){                
            final ListNoteOfTypeFragment listNoteOfTypeFragment = (ListNoteOfTypeFragment) frag;
            //do whatever you want with your fragment here
            listNoteOfTypeFragment.setNoteChangeListener(mListener);
        }
        return frag;
    }    
Anh Nguyen
  • 413
  • 4
  • 15
1

Definitive answer that works seamlessly (but small hack):

somewhere in page fragment's layout:

<FrameLayout android:layout_width="0dp" android:layout_height="0dp" android:visibility="gone" android:id="@+id/fragment_reference">
     <View android:layout_width="0dp" android:layout_height="0dp" android:visibility="gone"/>
</FrameLayout>

in fragment's onCreateView():

...
View root = inflater.inflate(R.layout.fragment_page, container, false);
ViewGroup ref = (ViewGroup)root.findViewById(R.id.fragment_reference);
ref.setTag(this);
ref.getChildAt(0).setTag("fragment:" + pageIndex);
return root;

and method to return Fragment from ViewPager, if exists:

public Fragment getFragment(int pageIndex) {        
        View w = mViewPager.findViewWithTag("fragment:" + pageIndex);
        if (w == null) return null;
        View r = (View) w.getParent();
        return (Fragment) r.getTag();
}
Hox
  • 21
  • 1
1

It's been explained here : http://developer.android.com/guide/topics/fundamentals/fragments.html

In OnCreateView you must return a view to draw a UI for your fragment, I think that's the relationship.

Also this question might be similar: Get focused View from ViewPager

Community
  • 1
  • 1
Dante
  • 1,104
  • 1
  • 10
  • 15
  • Hi Dante, yeah, I understand that, but I'm trying to get the Fragment, given the View. – MitchellSalad Dec 21 '11 at 18:03
  • Ok, what about `getFragmentManager().findFragmentById(R.id.fragment_container);` then? You'll get the current fragment in that container I think. – Dante Dec 21 '11 at 21:14
  • 1
    Found this page because I have the same problem. findFragmentById isn't working for me. – James Dec 23 '11 at 02:57
0

Jorge Garcia's FragmentStatePagerWithCurrentAdapter is a very good solution but it needs a minor improvement. In case the activity gets destroyed and re-created in response to a configuration change or something like that the getItem will not be called for the fragments that were saved and retrieved by the fragment manager. So I override getItem normally in my subclass and I put the following in the FragmentStatePagerWithCurrentAdapter

@Override
public Object instantiateItem(ViewGroup container, int position) {
    Object item = super.instantiateItem(container, position);
    if ( item instanceof Fragment ) {
        pageReferenceMap.put(position, (Fragment)item);
    }
    return item;
}

The instantiateItem is called every time the fragment in that position is accessed.

Community
  • 1
  • 1
dmapr
  • 349
  • 1
  • 7
0

Or just save all Fragments in a map:

public class MyFragment extends Fragment implements OnPageChangeListener {

    private ViewPager viewPager;
    private FragmentStatePagerAdapter viewAdapter;
    private View rootView;
    private Map<Integer, Fragment> fragments = new HashMap<Integer, Fragment>();

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

        rootView = inflater.inflate(R.layout.introdution, container, false);

        viewPager = (ViewPager) rootView.findViewById(R.id.pager);
        viewAdapter = new ViewAdapter(getFragmentManager());
        viewPager.setAdapter(viewAdapter);
        viewPager.addOnPageChangeListener(this);

        return rootView;

    }

    private class ViewAdapter extends FragmentStatePagerAdapter {

        public ViewAdapter(FragmentManager fragmentManager) {
            super(fragmentManager);
        }

        @Override
        public Fragment getItem(int position) {

            Fragment result = null;

            switch (position) {
                case 0: result = Fragment1.newInstance(); break;
                case 1: result = Fragment2.newInstance(); break;
            }

            if (result != null)
                fragments.put(position, result);

            return result;

        }

        @Override
        public int getCount() {

            return 2;
        }

    }

    @Override
    public void onPageScrollStateChanged(int arg0) {
    }

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

    @Override
    public void onPageSelected(int position) {

        Fragment currentFragment = fragments.get(position);

    }

}
almisoft
  • 2,153
  • 2
  • 25
  • 33
-2

I think there is the better way by using this

Log.i(TAG, "getCurrentItem " + mViewPager.getCurrentItem());

Can get the current display fragment page.

Huy Tower
  • 7,769
  • 16
  • 61
  • 86