Поле фрагмента равно нулю после поворота устройства в Android

1

Когда я запускаю приложение, все работает нормально, но когда я поворачиваюсь в альбомную ориентацию, он вылетает, потому что во Fragment есть поле NULL.

Я не использую setRetainInstance(true) или добавление фрагментов во FragmentManager Я создаю новые фрагменты при запуске приложения и при его повороте.

В Activity OnCreate() я создаю Fragment и добавляю их в viewPager следующим образом.

   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));
    }

Тогда у меня есть тестовая кнопка в приложении, которое, когда я нажимаю, будет работать как

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

Это сработает, и текущие Fragments в ViewPager имеют ViewPager метод activityNotifiDataChange(), и все в порядке.

Когда я поворачиваю приложение и делаю то же самое, нажимая кнопку, ArrayList<Fragment> mFragment activityNotifiDataChange() вызывается нормально, но есть исключение нулевого указателя, потому что ArrayList<Fragment> mFragment теперь равен NULL.

Вот небольшой пример проекта Android Studio, демонстрирующий это поведение: https://drive.google.com/file/d/1Swqu59HZNYFT5hMTqv3eNiT9NmakhNEb/view?usp=sharing

Запустите приложение и нажмите кнопку с названием "PRESS TEST", затем поверните устройство, нажмите кнопку еще раз и наблюдайте за падением приложения

ОБНОВЛЕНИЕ РЕШЕНИЕ спасибо @GregMoens и @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);
        }
    }
}
Теги:
android-fragments

4 ответа

3
Лучший ответ

Основная проблема, которую я вижу с вашим приложением, заключается в том, что вы неправильно понимаете, как работает FragmentPagerAdapter. Я вижу это много, и это из-за отсутствия хороших javadocs в классе. Адаптер должен быть реализован так, чтобы при вызове getItem (position) возвращал новый экземпляр фрагмента. И тогда getItem (position) будет вызываться пейджером только тогда, когда ему нужен новый экземпляр для этой позиции. Вы не должны предварительно создавать фрагменты и затем переходить в адаптер. Вы также не должны держать сильные ссылки на фрагменты из вашей деятельности или из родительских фрагментов (например, ParentBasicInfoFragment). Помните, что менеджер фрагментов управляет фрагментами, а вы также управляете фрагментами, обновляя их и сохраняя ссылки на них. Это вызывает конфликт, и после ротации вы пытаетесь вызвать activityNotifiDataChange() для фрагмента, который фактически не инициализирован (onCreate() не был вызван). Использование отладчика и идентификаторов объектов отслеживания подтвердит это.

Если вы измените свой код так, чтобы FragmentPagerAdapter создавал фрагменты, когда они необходимы, и не сохранял ссылки на фрагменты или списки фрагментов, вы увидите гораздо лучшие результаты.

  • 1
    Да, хорошее объяснение. Почти проблема в том, что люди имеют базовое недопонимание с жизненным циклом и как компонент реагирует на него.
  • 1
    Не помогает то, что javadoc для FragmentPagerAdapter.getItem (position) очень вводит в заблуждение, если не полностью ошибается. Пример реализации, который они показывают в javadocs для FragmentPagerAdapter, является правильным.
Показать ещё 3 комментария
0
this.mFragments = new ArrayList<>();

Потому что это неправильно. Вы никогда не должны содержать ссылку на список фрагментов, если вы используете ViewPager. Просто верните новый экземпляр Fragment из getItem(int position) FragmentPagerAdapter и все готово.

Но чтобы исправить ваш код, вы должны полностью удалить mFragments.

  • 0
    this.mFragments могут принадлежать к FragmentAdapter .
  • 0
    Это не должно, super.onCreate управляет фрагментом воссоздания после процесса смерти. Вы не должны вручную создавать их в другом месте.
Показать ещё 7 комментариев
0

Использование setRetainInstance(true) не очень хороший подход. Если вам нужна такая же простая информация, как: позиция recyclerView, выбранный элемент recyclerView, возможно, какая-то модель (Parcelable), вы можете сделать это с помощью метода onSaveInstanceState/onRestoreInstanceState, есть одно ограничение - 1 onRestoreInstanceState. Вот пример с анимацией, как это работает.

Для более длительного использования используйте SharedPreferences, Room (Google ORM) или вы можете попробовать использовать ViewModel с LiveData (лучше всего подходить к некоторым данным, которые должны храниться во время сеанса пользователя).

  • 0
    добавление большей сложности не решит актуальную проблему.
  • 0
    Я не хочу сохранять какие-либо данные, кроме, может быть, для текущей вкладки ViewPager . Я хочу, чтобы все было воссоздано, поскольку они представляют собой dum-представления, поддерживаемые LiveData . Мои LiveData будут после поворота просто продолжать кормить мои фрагменты. В моем простом примере установка LiveData не включена для простоты
-3
//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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню