Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

FragmentStatePagerAdapter, childFragmentManager and orientation change a bad combination?

Here is my the problem, I have an activity, which includes a Fragment that has a ViewPager using (FragmentStatePagerAdapter), all works perfect when the Activity loads for the first time, but when setting setRetainInstance(true) to the parent fragment (the one with the pager), and orientation changes on the activity, it causes

java.lang.IllegalStateException: No activity

When trying to add the saved fragment, here is the code:

Activity:

public class DetailActivity{

...
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.frame_layout_with_progress_container);

    ...

    // However, if we're being restored from a previous state,
    // then we don't need to do anything and should return or else
    // we could end up with overlapping fragments.
    FragmentManager fm = getSupportFragmentManager();
    if (savedInstanceState != null) {
        Fragment f = fm.findFragmentById(R.id.container);
        if(f instanceof DetailPagerFragment){
            detailPagerFragment = (DetailPagerFragment) f;
        }
    }
}

@Override
protected void onPostResume() {
    super.onPostResume();

    //If fragment is null, create a new instance
    if(detailPagerFragment==null){
        detailPagerFragment =
            DetailContainerFragment.newInstance(details, initialPosition);
    }

    FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
    ft.replace(R.id.container, detailPagerFragment);
    ft.commit();
}

Note: that i have to save the fragment instance on onCreate because when the code reaches onStart the reference for this fragment was null (this issue is not important for the time it has something to do with the NavigationDrawer), so i need to manually save the instance of the fragment.

Activity layout:

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent">

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/container"/>

</RelativeLayout>

ViewPager Fragmentm this class extends for DetailPagerFragment which is a custom pager fragment it only wraps common code for pagers (for example view inflation, uses inflateView method), this is why the pager is added on a fragment instead of directly to the activity:

public class DetailContainerFragment extends DetailPagerFragment {

    List<Detail> details;

    public static DetailContainerFragment newInstance(List<Detail> details,int selectedPosition) {
        DetailContainerFragment df = new DetailContainerFragment();
        df.setSelectedPosition(selectedPosition);
        df.setDetails(details);
        return df;
    }

    public void setDetails(List<Detail> details) {
        this.details = details;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRetainInstance(true);
    }


    /**
     * Inflates the view to be used by this fragment
     *
     * @param inflater
     *  Inflater to use
     * @return Inflated view
     */
    @Override
    public View inflateView(LayoutInflater inflater) {
        return inflater.inflate(R.layout.detail_pager_fragment, null);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        DetailStatePagerAdapter detailStatePagerAdapter = new DetailStatePagerAdapter(getChildFragmentManager());
        setPagerAdapter(detailStatePagerAdapter);
        return super.onCreateView(inflater, container, savedInstanceState);
    }

    private class DetailStatePagerAdapter extends FragmentStatePagerAdapter {

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

        @Override
        public Fragment getItem(int position) {
            return DetailFragment.newInstance(details.get(position));
        }

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

        @Override
        public int getCount() {
            return details.size();
        }
    }
}
like image 814
JavierSP1209 Avatar asked Mar 20 '23 12:03

JavierSP1209


1 Answers

I had a similar problem with child fragments in a ViewPager.

The parent fragment created new instances of the child fragments to be used with the ViewPager while the adapter kept instances of the old child fragments used before the configuration change.

I ended up cleaning the ChildFragmentManager before initializing the adapter with it:

@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    mPager = (ViewPager) view.findViewById(R.id.pager);

    cleanChildFragments(getChildFragmentManager());

    mPagerAdapter = new MyPagerAdapter(getChildFragmentManager(), getTabFragments());
    mPager.setAdapter(mPagerAdapter);
}

/**
 * This is necessary to have a clean ChildFragmentManager, old fragments might be called otherwise
 * @param childFragmentManager
 */
private void cleanChildFragments(FragmentManager childFragmentManager) {
    List<Fragment> childFragments = childFragmentManager.getFragments();
    if (childFragments != null && !childFragments.isEmpty()) {
        FragmentTransaction ft = childFragmentManager.beginTransaction();
        for (Fragment fragment : childFragments) {
            ft.remove(fragment);
        }
        ft.commit();
    }
}

Maybe this helps someone...

like image 154
botzek13 Avatar answered Mar 23 '23 17:03

botzek13