Replace Fragment inside a ViewPager Replace Fragment inside a ViewPager android android

Replace Fragment inside a ViewPager


There is another solution that does not need modifying source code of ViewPager and FragmentStatePagerAdapter, and it works with the FragmentPagerAdapter base class used by the author.

I'd like to start by answering the author's question about which ID he should use; it is ID of the container, i.e. ID of the view pager itself. However, as you probably noticed yourself, using that ID in your code causes nothing to happen. I will explain why:

First of all, to make ViewPager repopulate the pages, you need to call notifyDataSetChanged() that resides in the base class of your adapter.

Second, ViewPager uses the getItemPosition() abstract method to check which pages should be destroyed and which should be kept. The default implementation of this function always returns POSITION_UNCHANGED, which causes ViewPager to keep all current pages, and consequently not attaching your new page. Thus, to make fragment replacement work, getItemPosition() needs to be overridden in your adapter and must return POSITION_NONE when called with an old, to be hidden, fragment as argument.

This also means that your adapter always needs to be aware of which fragment that should be displayed in position 0, FirstPageFragment or NextFragment. One way of doing this is supplying a listener when creating FirstPageFragment, which will be called when it is time to switch fragments. I think this is a good thing though, to let your fragment adapter handle all fragment switches and calls to ViewPager and FragmentManager.

Third, FragmentPagerAdapter caches the used fragments by a name which is derived from the position, so if there was a fragment at position 0, it will not be replaced even though the class is new. There are two solutions, but the simplest is to use the remove() function of FragmentTransaction, which will remove its tag as well.

That was a lot of text, here is code that should work in your case:

public class MyAdapter extends FragmentPagerAdapter{    static final int NUM_ITEMS = 2;    private final FragmentManager mFragmentManager;    private Fragment mFragmentAtPos0;    public MyAdapter(FragmentManager fm)    {        super(fm);        mFragmentManager = fm;    }    @Override    public Fragment getItem(int position)    {        if (position == 0)        {            if (mFragmentAtPos0 == null)            {                mFragmentAtPos0 = FirstPageFragment.newInstance(new FirstPageFragmentListener()                {                    public void onSwitchToNextFragment()                    {                        mFragmentManager.beginTransaction().remove(mFragmentAtPos0).commit();                        mFragmentAtPos0 = NextFragment.newInstance();                        notifyDataSetChanged();                    }                });            }            return mFragmentAtPos0;        }        else            return SecondPageFragment.newInstance();    }    @Override    public int getCount()    {        return NUM_ITEMS;    }    @Override    public int getItemPosition(Object object)    {        if (object instanceof FirstPageFragment && mFragmentAtPos0 instanceof NextFragment)            return POSITION_NONE;        return POSITION_UNCHANGED;    }}public interface FirstPageFragmentListener{    void onSwitchToNextFragment();}

Hope this helps anyone!


As of November 13th 2012, repacing fragments in a ViewPager seems to have become a lot easier. Google released Android 4.2 with support for nested fragments, and it's also supported in the new Android Support Library v11 so this will work all the way back to 1.6

It's very similiar to the normal way of replacing a fragment except you use getChildFragmentManager. It seems to work except the nested fragment backstack isn't popped when the user clicks the back button. As per the solution in that linked question, you need to manually call the popBackStackImmediate() on the child manager of the fragment. So you need to override onBackPressed() of the ViewPager activity where you'll get the current fragment of the ViewPager and call getChildFragmentManager().popBackStackImmediate() on it.

Getting the Fragment currently being displayed is a bit hacky as well, I used this dirty "android:switcher:VIEWPAGER_ID:INDEX" solution but you can also keep track of all fragments of the ViewPager yourself as explained in the second solution on this page.

So here's my code for a ViewPager with 4 ListViews with a detail view shown in the ViewPager when the user clicks a row, and with the back button working. I tried to include just the relevant code for the sake of brevity so leave a comment if you want the full app uploaded to GitHub.

HomeActivity.java

 public class HomeActivity extends SherlockFragmentActivity {FragmentAdapter mAdapter;ViewPager mPager;TabPageIndicator mIndicator;@Overrideprotected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);    setContentView(R.layout.main);    mAdapter = new FragmentAdapter(getSupportFragmentManager());    mPager = (ViewPager)findViewById(R.id.pager);    mPager.setAdapter(mAdapter);    mIndicator = (TabPageIndicator)findViewById(R.id.indicator);    mIndicator.setViewPager(mPager);}// This the important bit to make sure the back button works when you're nesting fragments. Very hacky, all it takes is some Google engineer to change that ViewPager view tag to break this in a future Android update.@Overridepublic void onBackPressed() {    Fragment fragment = (Fragment) getSupportFragmentManager().findFragmentByTag("android:switcher:" + R.id.pager + ":"+mPager.getCurrentItem());    if (fragment != null) // could be null if not instantiated yet    {        if (fragment.getView() != null) {            // Pop the backstack on the ChildManager if there is any. If not, close this activity as normal.            if (!fragment.getChildFragmentManager().popBackStackImmediate()) {                finish();            }        }    }}class FragmentAdapter extends FragmentPagerAdapter {            public FragmentAdapter(FragmentManager fm) {        super(fm);    }    @Override    public Fragment getItem(int position) {        switch (position) {        case 0:            return ListProductsFragment.newInstance();        case 1:            return ListActiveSubstancesFragment.newInstance();        case 2:            return ListProductFunctionsFragment.newInstance();        case 3:            return ListCropsFragment.newInstance();        default:            return null;        }    }    @Override    public int getCount() {        return 4;    } }}

ListProductsFragment.java

public class ListProductsFragment extends SherlockFragment {private ListView list;public static ListProductsFragment newInstance() {    ListProductsFragment f = new ListProductsFragment();    return f;}@Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {    View V = inflater.inflate(R.layout.list, container, false);    list = (ListView)V.findViewById(android.R.id.list);    list.setOnItemClickListener(new OnItemClickListener() {        public void onItemClick(AdapterView<?> parent, View view,            int position, long id) {          // This is important bit          Fragment productDetailFragment = FragmentProductDetail.newInstance();          FragmentTransaction transaction = getChildFragmentManager().beginTransaction();          transaction.addToBackStack(null);          transaction.replace(R.id.products_list_linear, productDetailFragment).commit();        }      });           return V;}}


Based on @wize 's answer, which I found helpful and elegant, I could achieve what I wanted partially, cause I wanted the cability to go back to the first Fragment once replaced. I achieved it bit modifying a bit his code.

This would be the FragmentPagerAdapter:

public static class MyAdapter extends FragmentPagerAdapter {    private final class CalendarPageListener implements            CalendarPageFragmentListener {        public void onSwitchToNextFragment() {            mFragmentManager.beginTransaction().remove(mFragmentAtPos0)                    .commit();            if (mFragmentAtPos0 instanceof FirstFragment){                mFragmentAtPos0 = NextFragment.newInstance(listener);            }else{ // Instance of NextFragment                mFragmentAtPos0 = FirstFragment.newInstance(listener);            }            notifyDataSetChanged();        }    }    CalendarPageListener listener = new CalendarPageListener();;    private Fragment mFragmentAtPos0;    private FragmentManager mFragmentManager;    public MyAdapter(FragmentManager fm) {        super(fm);        mFragmentManager = fm;    }    @Override    public int getCount() {        return NUM_ITEMS;    }    @Override    public int getItemPosition(Object object) {        if (object instanceof FirstFragment && mFragmentAtPos0 instanceof NextFragment)            return POSITION_NONE;        if (object instanceof NextFragment && mFragmentAtPos0 instanceof FirstFragment)            return POSITION_NONE;        return POSITION_UNCHANGED;    }    @Override    public Fragment getItem(int position) {        if (position == 0)            return Portada.newInstance();        if (position == 1) { // Position where you want to replace fragments            if (mFragmentAtPos0 == null) {                mFragmentAtPos0 = FirstFragment.newInstance(listener);            }            return mFragmentAtPos0;        }        if (position == 2)            return Clasificacion.newInstance();        if (position == 3)            return Informacion.newInstance();        return null;    }}public interface CalendarPageFragmentListener {    void onSwitchToNextFragment();}

To perfom the replacement, simply define a static field, of the type CalendarPageFragmentListener and initialized through the newInstance methods of the corresponding fragments and call FirstFragment.pageListener.onSwitchToNextFragment() or NextFragment.pageListener.onSwitchToNextFragment() respictevely.