Tord Larsen
Tord Larsen

Reputation: 2840

Fragment Field is NULL after rotate device in Android

When I start the app everything works ok but when I rotate to landscape it crashes because in the Fragment there is a field that is NULL.

I dont use setRetainInstance(true) or adding Fragments to FragmentManagerI create new Fragments on app start and when app rotate.

In the Activity OnCreate() I create the Fragment and adding them to the viewPager like this.

   protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         ParentBasicInfoFragment parentBasicInfoFragment = new ParentBasicInfoFragment();
         ParentUTCFragment parentUTCFragment = new ParentUTCFragment();
         ParentEventsFragment parentEventsFragment = new ParentEventsFragment();
         this.mFragments = new ArrayList<>();
         this.mFragments.add(parentBasicInfoFragment);
         this.mFragments.add(parentUTCFragment);
         this.mFragments.add(parentEventsFragment);
         this.viewpage.setOffscreenPageLimit(3);
         setCurrentTab(0);
         this.viewpage.setAdapter(new MainActivityPagerAdapter(getSupportFragmentManager(), this.mFragments));
    }

Then I have a test button on the app that when I press it will do like

  public void test(View view) {
      ((BaseFragment) MainActivity.this.mFragments.get(MainActivity.this.viewpage.
                getCurrentItem())).activityNotifiDataChange("hello");
  }

This will work and the current Fragments in the ViewPager have the method, activityNotifiDataChange() that are being called and all is ok.

When I rotate the app and do the same thing pressing the button the activityNotifiDataChange() is being called alright but there a null pointer exception because the ArrayList<Fragment> mFragment is now NULL.

Here´s a small sample Android Studio project showing this behavior: https://drive.google.com/file/d/1Swqu59HZNYFT5hMTqv3eNiT9NmakhNEb/view?usp=sharing

Start app and press button named "PRESS TEST", then rotate device and press the button again and watch the app crash

UPDATE SOLUTION thanks @GregMoens and @EpicPandaForce

public class MainActivityPagerAdapter extends PersistenPagerAdapter<BaseFragment> {

    private static int NUM_ITEMS = 3;

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

    public int getCount() {
        return NUM_ITEMS;
    }

    @Override
    public Fragment getItem(int position) {
        switch (position) {
            case 0:
                return ParentBasicInfoFragment.newInstance(0, "Page # 1");
            case 1:
                return ParentUTCFragment.newInstance(1, "Page # 2");
            case 2:
                return ParentEventsFragment.newInstance(2, "Page # 3");
            default:
                return null;
        }
    }
}

public abstract class PersistenPagerAdapter<T extends BaseFragment> extends FragmentPagerAdapter {
    private SparseArray<T> registeredFragments = new SparseArray<T>();

    public PersistenPagerAdapter(FragmentManager fragmentManager) {
        super(fragmentManager);
    }

    @Override
    public T instantiateItem(ViewGroup container, int position) {
        T fragment = (T)super.instantiateItem(container, position);
        registeredFragments.put(position, fragment);
        return fragment;
    }

    @Override
    public void destroyItem(ViewGroup container, int position, Object object) {
        registeredFragments.remove(position);
        super.destroyItem(container, position, object);
    }

    public T getRegisteredFragment(ViewGroup container, int position) {
        T existingInstance = registeredFragments.get(position);
        if (existingInstance != null) {
            return existingInstance;
        } else {
            return instantiateItem(container, position);
        }
    }
}

Upvotes: 2

Views: 1532

Answers (4)

EpicPandaForce
EpicPandaForce

Reputation: 81539

this.mFragments = new ArrayList<>();

Because this is wrong. You should never hold a reference to the fragment list if you are using ViewPager. Just return new instance of Fragment from getItem(int position) of FragmentPagerAdapter and you are good to go.

But to fix your code, you must delete mFragments entirely.

See https://stackoverflow.com/a/58605339/2413303 for more details.

Upvotes: 1

Greg Moens
Greg Moens

Reputation: 1825

The main problem I see with your app is your misunderstanding with how FragmentPagerAdapter works. I see this a lot and it's due to lack of good javadocs on the class. The adapter should be implemented so that getItem(position) returns a new fragment instance when called. And then getItem(position) will only be called by the pager when it needs a new instance for that position. You should not pre-create the fragments and pass then into the adapter. You should also not be holding strong references to the fragments from either your activity or from parent fragments (like ParentBasicInfoFragment). Because remember, the fragment manager is managing fragments and you are also managing fragments by newing them and keeping references to them. This is causing a conflict and after rotation, you are trying to invoke activityNotifiDataChange() on a fragment that is not actually initialized (onCreate() was not called). Using the debugger and tracking object IDs will confirm this.

If you change your code so that the FragmentPagerAdapter creates the fragments when they are needed and don't store references to fragments or lists of fragments, you will see much better results.

Upvotes: 5

Dmytro Ivanov
Dmytro Ivanov

Reputation: 1310

Use setRetainInstance(true) is not a good approach. If you need to same some simple information such as: position of recyclerView, selected item of recyclerView, maybe some model(Parcelable) you could do it with method onSaveInstanceState / onRestoreInstanceState, there is one limitation is 1MB. Here is an example with animations how it works.

For more durable persistance use SharedPreferences, Room(Google ORM) or you could try to use ViewModel with LiveData (best approach some data which should live while user session).

Upvotes: 0

Amit
Amit

Reputation: 72

//change in AndroidManifest.xml file,
<activity
        android:name=".activity.YourActivity"
        android:configChanges="orientation|keyboardHidden|screenSize"
        android:screenOrientation="sensor"
         />

//YourActivity in which you defines fragment.
 //may be it helps

Upvotes: -3

Related Questions