Когда я запускаю приложение, все работает нормально, но когда я поворачиваюсь в альбомную ориентацию, он вылетает, потому что во 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);
}
}
}
Основная проблема, которую я вижу с вашим приложением, заключается в том, что вы неправильно понимаете, как работает FragmentPagerAdapter. Я вижу это много, и это из-за отсутствия хороших javadocs в классе. Адаптер должен быть реализован так, чтобы при вызове getItem (position) возвращал новый экземпляр фрагмента. И тогда getItem (position) будет вызываться пейджером только тогда, когда ему нужен новый экземпляр для этой позиции. Вы не должны предварительно создавать фрагменты и затем переходить в адаптер. Вы также не должны держать сильные ссылки на фрагменты из вашей деятельности или из родительских фрагментов (например, ParentBasicInfoFragment). Помните, что менеджер фрагментов управляет фрагментами, а вы также управляете фрагментами, обновляя их и сохраняя ссылки на них. Это вызывает конфликт, и после ротации вы пытаетесь вызвать activityNotifiDataChange() для фрагмента, который фактически не инициализирован (onCreate() не был вызван). Использование отладчика и идентификаторов объектов отслеживания подтвердит это.
Если вы измените свой код так, чтобы FragmentPagerAdapter создавал фрагменты, когда они необходимы, и не сохранял ссылки на фрагменты или списки фрагментов, вы увидите гораздо лучшие результаты.
this.mFragments = new ArrayList<>();
Потому что это неправильно. Вы никогда не должны содержать ссылку на список фрагментов, если вы используете ViewPager. Просто верните новый экземпляр Fragment из getItem(int position)
FragmentPagerAdapter
и все готово.
Но чтобы исправить ваш код, вы должны полностью удалить mFragments
.
this.mFragments
могут принадлежать к FragmentAdapter
.
super.onCreate
управляет фрагментом воссоздания после процесса смерти. Вы не должны вручную создавать их в другом месте.
Использование setRetainInstance(true)
не очень хороший подход. Если вам нужна такая же простая информация, как: позиция recyclerView, выбранный элемент recyclerView, возможно, какая-то модель (Parcelable), вы можете сделать это с помощью метода onSaveInstanceState
/onRestoreInstanceState
, есть одно ограничение - 1 onRestoreInstanceState
. Вот пример с анимацией, как это работает.
Для более длительного использования используйте SharedPreferences, Room (Google ORM) или вы можете попробовать использовать ViewModel с LiveData (лучше всего подходить к некоторым данным, которые должны храниться во время сеанса пользователя).
ViewPager
. Я хочу, чтобы все было воссоздано, поскольку они представляют собой dum-представления, поддерживаемые LiveData
. Мои LiveData
будут после поворота просто продолжать кормить мои фрагменты. В моем простом примере установка LiveData не включена для простоты
//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