Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ViewPager inside Fragment loses it's content when the Fragment is reshown

I have a simple Fragment with a ViewPager.

I'm using the up to date support library, v4 rev18!

If I show it the first time, everything works fine, if I go back and show it again, the app crashes with the following exception:

java.lang.NullPointerException
at android.support.v4.app.FragmentManagerImpl.getFragment(FragmentManager.java:569)
at android.support.v4.app.FragmentStatePagerAdapter.restoreState(FragmentStatePagerAdapter.java:211)
at android.support.v4.view.ViewPager.onRestoreInstanceState(ViewPager.java:1281)
at android.view.View.dispatchRestoreInstanceState(View.java:12043)
at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:2688)
at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:2694)
at android.view.View.restoreHierarchyState(View.java:12021)
at android.support.v4.app.Fragment.restoreViewState(Fragment.java:425)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:949)
at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1104)
at android.support.v4.app.BackStackRecord.run(BackStackRecord.java:682)
at android.support.v4.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1460)
at android.support.v4.app.FragmentManagerImpl$1.run(FragmentManager.java:440)
at android.os.Handler.handleCallback(Handler.java:615)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4800)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:798)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:565)
at dalvik.system.NativeStart.main(Native Method)

My simple class looks like following and should be, actually, fine...

public class RoutineDayFragment extends BaseFragment
{
    private RoutinesActivity mParent = null;

    @InjectView(R.id.pager)
    MyViewPager pager = null;
    @InjectView(R.id.indicator)
    PagerSlidingTabStrip indicator = null;

    private FragmentStatePagerAdapter mAdapter = null;

    @Override
    public void onAttach(Activity activity)
    {
        super.onAttach(activity);
        mParent = ((RoutinesActivity) getBaseActivity());
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
    {
        View v = inflater.inflate(R.layout.view_pager, container, false);
        Views.inject(this, v);

        mAdapter = new FragmentStatePagerAdapter(getChildFragmentManager())
        {
            @Override
            public int getCount()
            {
                if (mParent.getSharedData().selectedRoutine == -1)
                    return 0;
                return mParent.getSharedData().routines.get(mParent.getSharedData().selectedRoutine).getRWorkoutDay().size();
            }

            @Override
            public Fragment getItem(int pos)
            {
                return DayFragment.newInstance(pos);
            }

            @Override
            public CharSequence getPageTitle(int pos)
            {
                return Common.getString(R.string.day) + (pos + 1);
            }
        };

        pager.setAdapter(mAdapter);
        indicator.setViewPager(pager);
        return v;
    }
}
like image 987
prom85 Avatar asked Sep 09 '13 15:09

prom85


1 Answers

Use an AsyncTask to set the ViewPagerAdapter:

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

        @Override
        protected void onPostExecute(Void result) {

            if(mAdapter != null) mViewPager.setAdapter(mAdapter);
        }
    }   

Call it like this:

mAdapter = new PageAdapter(getChildFragmentManager());
new SetAdapterTask().execute();

And reset the adapter in the onResume() method of your Fragment.

UPDATE - How to nest a ViewPager inside a Fragment?

Allright, here it is. I modified the Google example of Effective Navigation to fit your concerns.

What did I create?

  • I created a simple application, containing a MainActivity with a ViewPager and 3 tabs.
  • Each of these tabs is represented by a Fragment that contains a ViewPager as well.
  • The ViewPager inside the Fragment contains 10 pages.
  • So we have 3 "main" tabs / fragments, each containing 10 more fragments.
  • For demonstration purposes, I made the top-level ViewPager non-swipeable, so you have to use the tabs to switch between the main Fragments (I created a CustomViewPager and made some changes to remove the ViewPagers swipe capability)
  • The ViewPager inside the main Fragments is swipe able, so you can swipe to switch between the sub-Fragments, and press the tabs to switch between the main-Fragments
  • If you click a sub-Fragment, a new Activity is started.
  • If you return to the old activity when the new activity is closed, the Fragments and ViewPager's state is preserved.
  • When switching the main-Fragments, their state is preserved as well

Here is the Main Activity.java

It contains two adapters, one for the main Fragments, one for the sub Fragments. Furthermore, their are two Fragment classes, one is the Fragment containing the ViewPager (the main-Fragment), the other one is the sub-Fragment (inside the nested ViewPager)

public class MainActivity extends FragmentActivity implements ActionBar.TabListener {

    AppSectionsPagerAdapter mAppSectionsPagerAdapter;
    NonSwipeableViewPager mViewPager;

    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mAppSectionsPagerAdapter = new AppSectionsPagerAdapter(getSupportFragmentManager());

        // Set up the action bar.
        final ActionBar actionBar = getActionBar();
        actionBar.setHomeButtonEnabled(false);
        actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);

        // Set up the ViewPager, attaching the adapter and setting up a listener for when the
        // user swipes between sections.
        mViewPager = (NonSwipeableViewPager) findViewById(R.id.pager);
        mViewPager.setAdapter(mAppSectionsPagerAdapter);
        mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
            @Override
            public void onPageSelected(int position) {
                actionBar.setSelectedNavigationItem(position);
            }
        });

        // For each of the sections in the app, add a tab to the action bar.
        for (int i = 0; i < mAppSectionsPagerAdapter.getCount(); i++) {
            actionBar.addTab(
                    actionBar.newTab()
                            .setText(mAppSectionsPagerAdapter.getPageTitle(i))
                            .setTabListener(this));
        }
    }

    @Override
    public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {}

    @Override
    public void onTabSelected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {
        // When the given tab is selected, switch to the corresponding page in the ViewPager.
        mViewPager.setCurrentItem(tab.getPosition());
    }

    @Override
    public void onTabReselected(ActionBar.Tab tab, FragmentTransaction fragmentTransaction) {}

    public static class AppSectionsPagerAdapter extends FragmentPagerAdapter {

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

        @Override
        public Fragment getItem(int i) {
            switch (i) {

                default:
                    // The other sections of the app are dummy placeholders.
                    Fragment fragment = new ViewPagerContainerFragment();
                    return fragment;
            }
        }

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

        @Override
        public CharSequence getPageTitle(int position) {
            return "Tab " + (position +1);
        }
    }

    public static class ViewPagerAdapter extends FragmentPagerAdapter {

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

        @Override
        public Fragment getItem(int i) {
            switch (i) {

                default:
                    // The other sections of the app are dummy placeholders.
                    Fragment fragment = new ViewPagerFragment();
                    Bundle b = new Bundle();
                    b.putString("key", "I am fragment nr " + i);
                    fragment.setArguments(b);
                    return fragment;
            }
        }

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

    /**
     * THIS FRAGMENT CONTAINS A VIEWPAGER
     */
    public static class ViewPagerContainerFragment extends Fragment {

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                Bundle savedInstanceState) {
            View rootView = inflater.inflate(R.layout.main_fragment, container, false);

            ViewPager pager = (ViewPager) rootView.findViewById(R.id.nestedViewPager);

            pager.setAdapter(new ViewPagerAdapter(getChildFragmentManager()));

            return rootView;
        }
    }

    /**
     * THIS FRAGMENT IS INSIDE THE VIEWPAGER
     */
    public static class ViewPagerFragment extends Fragment {

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                Bundle savedInstanceState) {
            View rootView = inflater.inflate(R.layout.sub_fragment, container, false);

            ((TextView) rootView.findViewById(R.id.tv1)).setText(getArguments().getString("key"));
            ((TextView) rootView.findViewById(R.id.tv1)).setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    Intent intent = new Intent(getActivity(), CollectionDemoActivity.class);
                    startActivity(intent);
                }
            });

            return rootView;
        }
    }
}

activity_main.xml

<com.example.android.effectivenavigation.NonSwipeableViewPager 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/pager"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

main_fragment.xml

<android.support.v4.view.ViewPager xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/nestedViewPager"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:textSize="24sp" />

sub_fragment.xml

<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/tv1"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:textSize="50sp" />

The end result looks like this:

We have 3 top-level Fragments each containing a ViewPager with 10 Fragments.

enter image description here

like image 108
Philipp Jahoda Avatar answered Oct 17 '22 01:10

Philipp Jahoda