16

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;
    }
}
Philipp Jahoda
  • 50,880
  • 24
  • 180
  • 187
prom85
  • 16,896
  • 17
  • 122
  • 242
  • Please see, http://stackoverflow.com/a/17738748/794088 . even though you are using rev 18 of the support library you will still need to account for devices that still have older buggy versions of FragmentManager code (anything below 4.2). HTHs! – petey Sep 09 '13 at 16:08
  • I tried this trick already, but it didn't help... the other point, relating to older devices... I'm pretty sure that's not true, the support library is part of my app and will be delivered with it... So the version I'm using is the only one that counts... – prom85 Sep 09 '13 at 16:24
  • It'll still happen on devices that don't use the support fragmentmanager class in lieu of the one bundled in 3.2 - 4.1.1 os's. – petey Sep 09 '13 at 17:37

1 Answers1

15

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

Philipp Jahoda
  • 50,880
  • 24
  • 180
  • 187
  • I tried using a handler already, but I will try this as well. Although, as far as I read, this shouldn't be a problem with ChildFragmentManager, only if I use nested Fragments without ChildFragmentManager... But I will try this though... – prom85 Sep 09 '13 at 15:28
  • You should use ChildFragmentManager. Also try setting it in the onResume() method of the Fragment. – Philipp Jahoda Sep 09 '13 at 15:29
  • Allright. I thought that would already fix it. Fortunately I have a working solution using Fragments and a nested Viewpager. Give me a view minutes to code an example. – Philipp Jahoda Sep 09 '13 at 15:33
  • ok, you read my answer before I could delete it and try the second hint... but now I testet both, using async task and reset it in `onResume` with no success... – prom85 Sep 09 '13 at 15:37
  • I updated my answer with a full example. I can also send you the demo project if you want. – Philipp Jahoda Sep 09 '13 at 16:20
  • I will test that in few minutes... for now, their is one thing I see, you are using ONLY FragmentPagerAdapter, that means, no fragment destroying... all fragments will be in memory as long as the activity exists... but I didn't get my app to work with this adapter either, so if this works, it may help me too... thanks for the effort so far at least,, I will test that... – prom85 Sep 09 '13 at 16:29
  • 1
    You are welcome. I just tested the app using FragmentStatePagerAdapter - It works just fine as well. – Philipp Jahoda Sep 09 '13 at 16:31
  • I still have problems, you may have a look at my more detailed new question, if you have time... thanks for the help though... it just does not work, no way, no matter what I do... If I use sub fragments manually... i created a complete example, based on the one you gave me... http://stackoverflow.com/questions/18706941/fragment-with-viewpager-inside-fragment-and-fragmentstatepageradapter-results-in – prom85 Sep 09 '13 at 21:01
  • Where is com.example.android.effectivenavigation.NonSwipeableViewPager? – Chris Sprague Dec 03 '14 at 19:35
  • This is it: http://stackoverflow.com/questions/9650265/how-do-disable-paging-by-swiping-with-finger-in-viewpager-but-still-be-able-to-s – Philipp Jahoda Dec 03 '14 at 20:57
  • @Philipp Jahoda Just wanted to thank you for this excellent example. I will need to spend some time analyzing it to figure out exactly how it applies to my use case, but I believe that you hit the high points (BTW: I needed to do some tweaking to get it working in the latest Android Studio/Gradle). – Chris Marshall Jul 30 '15 at 15:08