Fragment with ViewPager inside Fragment and FragmentStatePagerAdapter results in Exception (with complete example)
M

1

9

I have a simple Fragment with a ViewPager.

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

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

I have a complete example which shows, WHEN the following exception is occuring:

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)

I am able to use the ViewPager in a child fragment in every other way, but I can't get it working if I add/remove the sub fragments manually and use a FragmentStatePagerAdapter in the sub fragments...

Following example should work, but it doesn't... i already added some code which solves some problems, but it does not solve all problems...

import java.lang.reflect.Field;

import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.app.FragmentTransaction;
import android.support.v4.view.ViewPager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.TextView;

public class TestActivity extends FragmentActivity implements OnClickListener
{
    private Fragment fragment1;
    private Fragment fragment2;

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

        fragment1 = getSupportFragmentManager().findFragmentByTag("fragment1");
        fragment2 = getSupportFragmentManager().findFragmentByTag("fragment2");
    }

    @Override
    public void onClick(View v)
    {
        Fragment nextFragment = null;
        String nextFragmentTag = null;
        if (v.getId() == R.id.button1)
        {
            if (fragment1 == null)
                fragment1 = ContainerFragment.newInstance(1);
            nextFragment = fragment1;
            nextFragmentTag = "fragment1";
        }
        else if (v.getId() == R.id.button2)
        {
            if (fragment2 == null)
                fragment2 = ContainerFragment.newInstance(2);
            nextFragment = fragment2;
            nextFragmentTag = "fragment2";
        }

        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
        transaction.replace(R.id.main, nextFragment, nextFragmentTag);
        transaction.addToBackStack(null);
        transaction.commit();
    }

    public static class MainPagerAdapter extends FragmentStatePagerAdapter
    {
        public int button;

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

        @Override
        public Fragment getItem(int i)
        {
            return SubFragment.newInstance(button, i);
        }

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

    public static class ContainerFragment extends Fragment
    {

        static ContainerFragment newInstance(int pos)
        {
            ContainerFragment f = new ContainerFragment();
            Bundle args = new Bundle();
            args.putInt("pos", pos);
            f.setArguments(args);
            return f;
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                Bundle savedInstanceState)
        {
            View rootView = inflater.inflate(R.layout.view_pager, container, false);
            ViewPager pager = (ViewPager) rootView.findViewById(R.id.pager);
            MainPagerAdapter adapter = new MainPagerAdapter(getChildFragmentManager());
            adapter.button = getArguments().getInt("pos");
            pager.setAdapter(adapter);
            return rootView;
        }

        // ---------------------------------------------------------------
        // HACK FIX für java.lang.IllegalStateException: No activity
        // ---------------------------------------------------------------

        private static final Field sChildFragmentManagerField;
        static
        {
            Field f = null;
            try
            {
                f = android.support.v4.app.Fragment.class.getDeclaredField("mChildFragmentManager");
                f.setAccessible(true);
            }
            catch (NoSuchFieldException e)
            {
            }
            sChildFragmentManagerField = f;
        }

        @Override
        public void onDetach()
        {
            super.onDetach();

            if (sChildFragmentManagerField != null)
            {
                try
                {
                    sChildFragmentManagerField.set(this, null);
                }
                catch (Exception e)
                {
                }
            }
        }
    }

    public static class SubFragment extends Fragment
    {
        static SubFragment newInstance(int button, int pos)
        {
            SubFragment f = new SubFragment();
            Bundle args = new Bundle();
            args.putString("key", "Button " + button + "Fragment " + pos);
            f.setArguments(args);
            return f;
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                Bundle savedInstanceState)
        {
            View rootView = inflater.inflate(R.layout.test_subfragment, container, false);
            ((TextView) rootView.findViewById(R.id.tv1)).setText(getArguments().getString("key"));
            return rootView;
        }
    }
}

for the sake of completeness, I add the xml files as well:

test_main.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical" >

        <Button
            android:id="@+id/button1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="onClick"
            android:text="Button1" />

        <Button
            android:id="@+id/button2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="onClick"
            android:text="Button2" />
    </LinearLayout>

</FrameLayout>

test_subfragment.xml

<?xml version="1.0" encoding="utf-8"?>
<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" />

view_pager.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/llContainer"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical" >

    <android.support.v4.view.ViewPager
        android:id="@+id/pager"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

</LinearLayout>
Ms answered 9/9, 2013 at 21:0 Comment(2)
Post your layout file, so that I can try it out. ;-)Fanti
that's what I was just doing... :-)Ms
F
3

Add an additional FrameLayout to your layout of the main activity. This is where you put the Fragment.

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/main"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical" >

        <Button
            android:id="@+id/button1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="onClick"
            android:text="Button1" />

        <Button
            android:id="@+id/button2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:onClick="onClick"
            android:text="Button2" />
 <!-- add this -->
        <FrameLayout
            android:id="@+id/fragment_frame"
            android:layout_width="match_parent"
            android:layout_height="match_parent" >

        </FrameLayout>

    </LinearLayout>

</FrameLayout>

Since you are adding your Fragments to the BackStack, there is no need for saving an instance of them. This is the reason why you app crashes. Adjust your onClick() method as follows:

    @Override
    public void onClick(View v)
    {
        Fragment nextFragment = null;
        String nextFragmentTag = null;
        if (v.getId() == R.id.button1)
        {
            nextFragment = ContainerFragment.newInstance(1);
            nextFragmentTag = "fragment1";
        }
        else if (v.getId() == R.id.button2)
        {
            nextFragment = ContainerFragment.newInstance(2);
            nextFragmentTag = "fragment2";
        }

        FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
        transaction.replace(R.id.fragment_frame, nextFragment, nextFragmentTag);
        transaction.addToBackStack(null);
        transaction.commit();
    }

I tested the code. Changing the Fragments with the buttons works, and the state the ViewPager was left when returing to it is preserved.

Fanti answered 9/9, 2013 at 21:17 Comment(5)
yes, I'm getting the crash when showing a fragment AGAIN... the first time it works fine... And I'm reading and trying already two days to get this to work somehow...Ms
yes, I know (although I'm not 100% sure, I think, I saw it crashing, but I can't reproduce that now... so I may remember that wrong) anyway, the problem is, I need this all in a place, where I don't know the count of pages at all... so I would really prefer the FragmentPagerStateAdapter...Ms
Allright, I managed to get it working. Im using a FragmentStatePagerAdapter. Since you are adding your Fragments to the backstack anyway, there is no need for saving an instance of them. See my updated answer.Fanti
you're right, didn't see that logical error... that seems to solve the problem... just tested it. Thanks so much for helping me and for taking the time to help meMs
Doesn't this result in having a lot of instances of ContainerFragment that never get cleaned up? I thought if you have added (via replace) a fragment to the fragment manager, you are supposed to look it up by tag and reuse it instead of creating a new instance every time?Revalue

© 2022 - 2024 — McMap. All rights reserved.