Fragment con ViewPager dentro de Fragment y FragmentStatePagerAdapter da como resultado una excepción (con un ejemplo completo)

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>

preguntado el 09 de septiembre de 13 a las 22:09

Post your layout file, so that I can try it out. ;-) -

that's what I was just doing... :-) -

1 Respuestas

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 Pila posterior, there is no need for saving an instance of them. This is the reason why you app crashes. Adjust your onClick() método de la siguiente manera:

    @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.

Respondido el 09 de Septiembre de 13 a las 23:09

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... - prom85

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... - prom85

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. - Felipe Jahoda

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 me - prom85

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? - kevin lam

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.