55

I have a fragment which contains a ViewPager. The ViewPager is associated with an adapter that contains a set of fragments.

Upon loading the parent fragment, I am met with an IllegalStateException with the message: java.lang.IllegalStateException: Recursive entry to executePendingTransactions.

Some research has led me to the conclusion that the system is unable display fragments within another fragment, HOWEVER there seems to be some indication that it is possible to do exactly this with the use of a ViewPager (A bug in ViewPager using it with other fragment).

In fact, if I add a button to my parent fragment which calls mPager.setAdapter(mAdapter) on my ViewPager when pressed, the ViewPager successfully loads. This is not ideal.

The issue then must be related to the fragment lifecycle. My question therefore, is this: Has anybody else found a way around this issue, and if so, how?

Is there some way to delay settings the adapter on the ViewPager until after the fragment transaction?

Here is my parent fragment code:

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

    mView = inflater.inflate(R.layout.team_card_master, container, false);
    mViewPager = (ViewPager)mView.findViewById(R.id.team_card_master_view_pager);

    final Button button = (Button)mView.findViewById(R.id.load_viewpager_button);
    button.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            mViewPager.setAdapter(mAdapter);
            button.setVisibility(View.GONE);
        }
    });

    mAdapter = new ViewPagerAdapter(getFragmentManager());
 //     mViewPager.setAdapter(mAdapter);

    return mView;
}

And the adapter:

public class ViewPagerAdapter extends FragmentPagerAdapter {
    public ViewPagerAdapter(FragmentManager fm) {
        super(fm);
    }

    @Override
    public int getCount() {
        if (mCursor == null) return 0;
        else return mCursor.getCount();
    }

    @Override
    public Fragment getItem(int position) {
        Bundle b = createBundle(position, mCursor);
        return TeamCardFragment.newInstance(b);
    }
}
Community
  • 1
  • 1
howettl
  • 12,419
  • 13
  • 56
  • 91
  • 8
    The ability to nest fragments has been added in revision 11 of the Android Support library. The Fragment class now has the method "getChildFragmentManager" . I created a simple project similar to this which can be found at https://github.com/marsucsb/nested-fragments – Marco RS Nov 15 '12 at 04:18
  • http://stackoverflow.com/q/18097567/1503130 can you help me with this – Prateek Aug 07 '13 at 10:38

9 Answers9

60

use AsyncTask to set the adapter for viewPager. It works for me. The asyncTask is to make the original fragment complete it's transition. and then we proceed with viewPager fragments, basically to avoid recursion.

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

    mView = inflater.inflate(R.layout.team_card_master, container, false);
    mViewPager = (ViewPager)mView.findViewById(R.id.team_card_master_view_pager);

    final Button button = (Button)mView.findViewById(R.id.load_viewpager_button);
    button.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            mViewPager.setAdapter(mAdapter);
            button.setVisibility(View.GONE);
        }
    });

    mAdapter = new ViewPagerAdapter(getFragmentManager());
    new setAdapterTask().execute();

    return mView;
}

private class setAdapterTask extends AsyncTask<Void,Void,Void>{
      protected Void doInBackground(Void... params) {
            return null;
        }

        @Override
        protected void onPostExecute(Void result) {
                   mViewPager.setAdapter(mAdapter);
        }
}
Yashwanth Kumar
  • 28,931
  • 15
  • 65
  • 69
  • ok- not so perfect. I had an issue where the views in the viewpager were black when the view had been recreated - since the fragments inside are reused. I solved it with having this code in the fragment's onDestroyView: – Jords Jan 31 '12 at 00:56
  • 1
    int position = 0; for (TabInfo info : mTabsAdapter.mTabs) { String tag = FragmentPagerAdapter.makeFragmentName(mViewPager.getId(), position); Fragment fragment; FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); if ((fragment = getFragmentManager().findFragmentByTag(tag)) != null) { ft.remove(fragment); } ft.commit(); position++; } ... it's pretty horrible :O – Jords Jan 31 '12 at 00:57
  • Your solution is working fine but when i press the back button then again attach the fragment only first two fragments are visible and content of third fragment is invisible. Please help – Deepak Goel Mar 26 '12 at 04:23
  • 7
    Please just use `getChildFragmentManger()` this is much better solution – Chris.Jenkins Oct 22 '13 at 14:22
  • In addition: each time replacing one of the fragments with children should do the following: FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction(); for (int i = 0; i < NUM_SUNS_FRAGMENTS; i++) { Fragment fragment = getFragmentManager().findFragmentByTag(String.format("android:switcher:%d:%d", R.id.your_pager_view, i)); fragmentTransaction.remove(fragment); } fragmentTransaction.commitAllowingStateLoss(); getFragmentManager().executePendingTransactions(); – evya Nov 26 '14 at 14:03
  • @Chris.Jenkins Thanks +1 for getChildFragmentManger() – berserk Jan 09 '15 at 12:58
  • it worked fine for me, but now I am getting jerk while hiding navigation drawer. – Muhammad Adil Jun 16 '15 at 06:50
39

You can use FragmentStatePagerAdapter instead of FragmentPageAdapter. It will remove fragments for you.

Jean-François Fabre
  • 137,073
  • 23
  • 153
  • 219
Leszek
  • 6,568
  • 3
  • 42
  • 53
26

I just ran into this same problem. I had a Fragment that needed to host a ViewPager of Fragments. When I stacked another Fragment on top of my ViewPagerFragment, and then hit back, I would get an IllegalStateException. After checking the logs (when stacking a new Fragment), I found that my ViewPagerFragment would go through its lifecycle methods to stop and destroy, however its children Fragments in the the ViewPager would stay in onResume. I realized that the children Fragments should be managed by the FragmentManager for the ViewPagerFragment, not the FragmentManager of the Activity.

I understand at the time, the answers above were to get around the limitations of Fragments not being able to have children Fragments. However, now that the latest support library has support for Fragment nesting, you no longer need to hack around setting the adapter for your ViewPager, and passing getChildFragmentManager() is all you need. This has been working perfectly for me so far.

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

    mView = inflater.inflate(R.layout.team_card_master, container, false);
    mViewPager = (ViewPager) mView.findViewById(R.id.team_card_master_view_pager);

    mAdapter = new ViewPagerAdapter(getChildFragmentManager());
    mViewPager.setAdapter(mAdapter);

    return mView;
}
Steven Byle
  • 13,149
  • 4
  • 45
  • 57
19

I used Handler.post() to set adapter outside of original fragment transaction:

@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    mHandler.post(new Runnable() {
        @Override
        public void run() {
            mViewPager.setAdapter(mAdapter);     
        }
    });        
}
inazaruk
  • 74,247
  • 24
  • 188
  • 156
  • 1
    Where is that mHandler coming from? – Dandre Allison Jul 09 '12 at 23:48
  • It should be a member and you can instantiate it in `onCreate()`: `mHandler = new Handler();` – inazaruk Jul 10 '12 at 07:01
  • 2
    Does this approach cause any issues with switching between Fragments? I'm currently having an issue if I go to a different Fragment then return to the one containing the ViewPager again. It's not showing the content and it's not scrolling properly. – Dandre Allison Aug 21 '12 at 16:31
  • @inazaruk can you please look at this [link](http://stackoverflow.com/questions/17161159/class-members-in-fragment-become-null-after-home-button-press-and-wait) – Viktor Apoyan Jun 18 '13 at 06:42
0

I have faced this problem when trying to initialize the viewPager adapter on the TabIndicator in on ViewCreated. After reading about it online I realised that this could be just because the fragment was not yet added to the activity. So I moved the initialization code to onStart() of the fragment, this should solve your problem. But not for me I still was getting same issue again.

But that was because in my code I was trying the bypass the normal async execution of pending task by explicitly call to executePendingTransactions(), after blocking that everything worked well

Sanket
  • 11
  • 1
0

Upon loading the parent fragment, I am met with an IllegalStateException with the message: java.lang.IllegalStateException: Recursive entry to executePendingTransactions.

Some research has led me to the conclusion that the system is unable display fragments within another fragment, HOWEVER there seems to be some indication that it is possible to do exactly this with the use of a ViewPager (A bug in ViewPager using it with other fragment).

set Id for ViewPager explicitly.

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
      mView = inflater.inflate(R.layout.team_card_master, container, false);
      mViewPager = (ViewPager)mView.findViewById(R.id.team_card_master_view_pager);
      mViewPager.setId(100);

      final Button button = (Button)mView.findViewById(R.id.load_viewpager_button);
      button.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
          mViewPager.setAdapter(mAdapter);
          button.setVisibility(View.GONE);
        }
      });
      mAdapter = new ViewPagerAdapter(getFragmentManager());
      mViewPager.setAdapter(mAdapter);

      return mView;
    }

You can use id in your xml resources instead of a magic number 100.

<resources>
  <item name="Id_For_ViewPager" type="id"/>
</resources>

and setId(R.Id_For_ViewPager).

See FragmentManager.java source for more detail.

https://github.com/android/platform_frameworks_support/blob/master/v4/java/android/support/v4/app/FragmentManager.java#L900

Or see this page(Japanese). The original idea is from her, actually, not me. http://y-anz-m.blogspot.jp/2012/04/androidfragment-viewpager-id.html

Kugyo
  • 1
  • 2
  • 4
0

Using this brilliant post by Thomas Amssler : http://tamsler.blogspot.nl/2011/11/android-viewpager-and-fragments-part-ii.html

Iv'e created a adapter that can be used to create all types of fragments. The adapter itself look like this :

public abstract class EmbeddedAdapter extends FragmentStatePagerAdapter {

private SparseArray<WeakReference<Fragment>>    mPageReferenceMap   = new SparseArray<WeakReference<Fragment>>();
private ArrayList<String> mTabTitles;

public abstract Fragment initFragment(int position);

public EmbeddedAdapter(FragmentManager fm, ArrayList<String> tabTitles) {
    super(fm);
    mTabTitles = tabTitles;
}

@Override
public Object instantiateItem(ViewGroup viewGroup, int position) {
    mPageReferenceMap.put(Integer.valueOf(position), new WeakReference<Fragment>(initFragment(position)));
    return super.instantiateItem(viewGroup, position);
}

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

@Override
public CharSequence getPageTitle(int position) {
    return mTabTitles.get(position);
}

@Override
public Fragment getItem(int pos) {
    return getFragment(pos);
}

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

public Fragment getFragment(int key) {

    WeakReference<Fragment> weakReference = mPageReferenceMap.get(key);

    if (null != weakReference) {

        return (Fragment) weakReference.get();

    } else {
        return null;
    }
}

@Override
public int getItemPosition(Object object) {
    return POSITION_NONE;
}

}

To use the adapter you will have to do the following :

private void setAdapter() {

    if(mAdapter == null) mAdapter = new EmbeddedAdapter(getActivity().getSupportFragmentManager(), mTabTitles) {

        @Override
        public Fragment initFragment(int position) {

            Fragment fragment;

            if(position == 0){

                // A start fragment perhaps?
                fragment = StartFragment.newInstance(mBlocks);

            }else{

                // Some other fragment
                fragment = PageFragment.newInstance(mCategories.get((position)));
            }
            return fragment;
        }
    };

    // Have to set the adapter in a handler
    Handler handler = new Handler();
    handler.post(new Runnable() {

        @Override
        public void run() {

            mViewPager.setVisibility(View.VISIBLE);
            mPagerTabStrip.setVisibility(View.VISIBLE);
            mAdapter.notifyDataSetChanged();
        }
    });
}

Note that all the fragments use the :

setRetainInstance(true);
Slickelito
  • 1,786
  • 20
  • 28
0

According with the documentation: https://developer.android.com/reference/android/app/Fragment.html#getChildFragmentManager()

If you want to manage the nesting fragment you must use getChildFragmentManager instead of getFragmentManager.

0

Instead of setting the adapter in AsyncTask or any Handler just write that part of code in onResume() section

Doing Such you will:

  • Let the transition of the main fragment to complete.
  • Display the child fragment without use of any background task.

The code of onResume() is as follows in your case:

@Override
public void onResume() {
    super.onResume();
    if(mViewPager.getAdapter()==null||mViewPager.getAdapter().getCount()==0)
    mViewPager.setAdapter(mAdapter);
}

Also in onCreateView Instead of

mAdapter = new ViewPagerAdapter(getFragmentManager());

Replace with

mAdapter = new ViewPagerAdapter(getChildFragmentManager());
Karan
  • 1
  • 5