Подробный обзор наиболее частых проблем, с которыми сталкиваются пользователи

Класс Fragment в Android используется для создания динамических пользовательских интерфейсов и должен использоваться в рамках действия. Самым большим преимуществом использования фрагментов является то, что они упрощают задачу создания пользовательского интерфейса для экранов разных размеров. Действие может содержать любое количество фрагментов.

Теперь это объяснение заставляет фрагменты звучать хорошо и легко, не так ли? Не так быстро, это еще не все. В этой статье мы рассмотрим основные потребности и типичные ошибки при использовании фрагментов.

Примечание. Я предполагаю, что у вас есть базовые знания о фрагментах и ​​обратных вызовах жизненного цикла фрагментов. Кроме того, я предполагаю, что вы знаете, как реализовать связь между двумя фрагментами. Эта статья выходит за рамки этого.

Препятствия

Вот несколько препятствий, связанных с фрагментами, с которыми некоторые из вас, должно быть, уже столкнулись или могут столкнуться в будущем:

  • FragmentManager: getSupportFragmentManager и getChildFragmentManager. Какой из них использовать, когда и избежать утечек памяти при их использовании
  • Обратный вызов от DialogFragment, ChildFragment, BottomSheetFragment к родительскому фрагменту
  • Фрагменты при использовании ViewPager и когда использовать FragmentStateAdapter против FragmentPagerAdapter.
  • Когда использовать FragmentTransaction add vs replace
  • Получатели фрагментов, трансляции и утечки памяти
  • Как обрабатывать эти фрагменты BottomBarNavigation и ящик
  • commit () и commitAllowingStateLoss ()
  • Меню опций фрагмента
  • Фрагмент исключений getActivity (), getView () и NullPointers
  • onActivityResult с вложенными фрагментами
  • Фрагмент и пакет
  • Назад Навигация

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

getSupportFragmentManager и getChildFragmentManager

FragmentManager - это класс, предоставляемый фреймворком, который используется для создания транзакций для добавления, удаления или замены фрагментов.

  • getSupportFragmentManager связан с действием. Считайте его FragmentManager для вашей деятельности.

Поэтому всякий раз, когда вы используете ViewPager, BottomSheetFragment и DialogFragment в действии, вы будете использовать getSupportFragmentManager.

Пример:

BottomDialogFragment bottomSheetDialog = BottomDialogFragment.getInstance();
bottomSheetDialog.show(getSupportFragmentManager(), "Custom Bottom Sheet");
  • getChildFragmentManager связан с фрагментами.

Всякий раз, когда вы находитесь во ViewPager внутри фрагмента, вы будете использовать getChildFragmentManager.

Пример:

FragmentManager cfManager=getChildFragmentManager();
viewPagerAdapter = new ViewPagerAdapter(cfManager);

Вот официальная ссылка для лучшего понимания.

Когда дело доходит до типичных ошибок, которые люди совершают при использовании ViewPager внутри фрагмента, они часто передают getSupportFragmentManager, , который является диспетчером фрагментов для действия, и это вызывает такие проблемы, как утечки памяти или ViewPager, который не обновляется. должным образом.

Самая важная проблема, вызванная использованием getSupportFragmentManager во фрагментах, - это утечка памяти. Но почему это происходит? Итак, у вас есть стек фрагментов, которые используются ViewPager, и все эти фрагменты складываются в активность, поскольку вы использовали getSupportFragmentManager. Теперь, если закрыть родительский фрагмент, он будет закрыт, но не будет уничтожен, потому что все дочерние фрагменты активны и все еще находятся в памяти, что вызывает утечку. Это приведет к утечке не только родительского фрагмента, но и всех дочерних фрагментов, поскольку ни один из них не может быть очищен из памяти кучи. Поэтому никогда не пытайтесь использовать getSupportFragmentManager во фрагменте.

Обратный вызов от DialogFragment, ChildFragment, BottomSheetFragment к родительскому фрагменту

Это очень распространенная проблема, с которой люди сталкиваются при использовании BottomSheetFragment, DialogFragment или ChildFragment.

Пример:

Добавьте дочерний фрагмент:

Другой пример bottomSheetFragment:

BottomSheetDialogFragment fragment = BottomSheetDialogFragment.newInstance();
fragment.show(getChildFragmentManager(), fragment.getTag());

Теперь предположим, что вам нужен обратный вызов от этих дочерних фрагментов к родительским фрагментам. Большинство людей создают связи между двумя фрагментами с помощью действия, немногие передают слушателей интерфейса в качестве параметра фрагментам (что является действительно плохой практикой, которой следует избегать). Лучший способ вызвать getParentFragment () из дочернего фрагмента - создать обратный вызов. Это очень просто. Рассмотрим пример ниже:

dialogFragment.show(ParentFragment.this.getChildFragmentManager(), "dialog_fragment");

Затем установите обратный вызов для родительского фрагмента, добавив следующий код в дочерний фрагмент:

Вот и все. Теперь вы можете легко выполнить обратный вызов родительскому фрагменту.

Используя тот же метод, вы можете создать обратный вызов от дочернего фрагмента внутри ViewPager к родительскому фрагменту, который содержит ViewPager.

Фрагменты при использовании ViewPager и когда использовать FragmentStateAdapter против FragmentPagerAdapter

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

Поэтому, когда у вас будет много фрагментов, используйте FragmentStateAdapter. Если ViewPager будет иметь менее трех фрагментов, используйте FragmentPagerAdapter.

Давайте посмотрим на некоторые часто встречающиеся проблемы.

Обновление ViewPager не работает:

Помните, что фрагменты ViewPager управляются FragmentManager либо из фрагмента, либо из активности, а FragmentManager содержит экземпляры всех фрагментов ViewPager.

Поэтому, когда люди говорят, что ViewPager не обновляется, это не что иное, как старые экземпляры фрагментов, которые все еще хранятся в FragmentManager. Вам нужно выяснить, почему FragmentManger хранит экземпляр фрагментов. Есть утечка или нет? В идеале для обновления ViewPager работает следующий код. Если это не так, вы делаете что-то не так.

List<String> strings = new ArrayList<>();
strings.add("1");
strings.add("2");
strings.add("3");
viewPager.setAdapter(new PagerFragAdapter(getSupportFragmentManager(), strings));
strings.add("4");
viewPager.getAdapter().notifyDataSetChanged();

Доступ к текущему фрагменту из ViewPager:

Это тоже очень распространенная проблема, с которой мы сталкиваемся. Если вы столкнулись с этим, либо создайте массив списка фрагментов внутри адаптера, либо попытайтесь получить доступ к фрагменту с помощью каких-либо тегов. Однако я предпочитаю другой вариант. FragmentStateAdapter и FragmentPagerAdapter предоставляют метод setPrimaryItem. Это можно использовать для установки текущего фрагмента, как показано ниже:

Я оставляю ссылку GitHub на этот простой проект ViewPager, чтобы все могли лучше понять.



FragmentTransaction Добавить против замены

В нашей деятельности у нас есть контейнер, внутри которого отображаются наши фрагменты.

Добавить просто добавит фрагмент в контейнер. Предположим, вы добавили в контейнер FragmentA и FragmentB. Контейнер будет иметь FragmentA и FragmentB, и если контейнер FrameLayout, фрагменты добавляются один над другим.

Replace просто заменит фрагмент поверх контейнера, поэтому, если я вызову create FragmentC и вызову replace FragmentB, который был наверху, FragmentB будет удален из контейнера (если вы не вызываете addToBackStack) и теперь FragmentC будет наверху.

Итак, какой из них использовать, когда? replace удаляет существующий фрагмент и добавляет новый фрагмент. Это означает, что когда вы нажимаете кнопку «Назад», заменяемый фрагмент будет создан с вызовом onCreateView. С другой стороны, add сохраняет существующие фрагменты и добавляет новый фрагмент, что означает, что существующий фрагмент будет активен, и они не будут находиться в состоянии «приостановлено». Следовательно, когда на CreateView нажимается кнопка «Назад», она не вызывается для существующего фрагмента (фрагмента, который был там до того, как был добавлен новый фрагмент). Что касается событий жизненного цикла фрагмента, onPause, onResume, onCreateView и другие события жизненного цикла будут вызываться в случае replace, но не в случае add.

Используйте замену фрагмента, если не нужно повторно посещать текущие фрагменты и текущие фрагменты больше не требуются. Также, если у вашего приложения есть ограничения памяти, рассмотрите возможность использования замены вместо добавления.

Приемники фрагментов, рассылки и утечки памяти

При использовании приемников внутри фрагмента распространенная ошибка - забыть отменить регистрацию приемника в onPause или OnDestroy. Если вы регистрируете фрагмент для прослушивания приемника внутри onCreate или OnResume, вам придется отменить регистрацию внутри onPause или onDestroy. В противном случае это вызовет утечку памяти.

LocalBroadcastManager.getInstance(getActivity()).unregisterReceiver(mYourBroadcastReceiver);

Кроме того, если один и тот же широковещательный приемник прослушивает несколько фрагментов, убедитесь, что вы зарегистрировались в onResume и отменили регистрацию в onPause. Если вы используете onCreate и onDestroy для регистрации и отмены регистрации, другие фрагменты не будут получать широковещательную рассылку, поскольку этот фрагмент не уничтожается.

Как работать с фрагментом BottomBarNavigation и NavigationDrawer

Когда мы используем BottomBarNavigation и NavigationDrawer, часто возникают такие проблемы, как воссоздание фрагментов или добавление одного и того же фрагмента несколько раз.

В таком случае вы можете использовать отображение и скрытие транзакций фрагмента вместо добавления или замены.

Существует также красивая библиотека, которая заботится о навигации и избегает воссоздания фрагментов под названием FragNav. Я связал это ниже.



commit () и commitAllowingStateLoss ()

Если ваша активность не находится в состоянии возобновления, и вы пытаетесь зафиксировать фрагмент, ваше приложение выйдет из строя. Чтобы этого избежать, вам нужно проверить, находится ли активность или фрагмент в состоянии возобновления isAdded() / isResumed()

Другое решение, если вас не очень заботит состояние фрагмента, заключается в том, что вы можете вызвать commitAllowingStateLoss. Это гарантирует, что фрагменты будут добавлены или заменены, несмотря на то, что действие завершается или не находится в возобновить состояние.

Меню параметров фрагмента

При использовании меню параметров внутри фрагмента не забудьте добавить следующую строку. Люди часто забывают добавить это и задаются вопросом, где эта опция на панели инструментов.

@Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setHasOptionsMenu(true);
    }

При использовании панели инструментов внутри фрагмента меню можно раздувать с помощью кода:

getToolbar().inflateMenu(R.menu.toolbar_menu_gmr);

В качестве альтернативы вы можете переопределить createOptionsMenu, но я предпочитаю вышеуказанный метод, поскольку он не полагается на суперкласс

Фрагмент getActivity (), getView () Исключения NullPointers

Если какой-либо фоновый процесс отправляет результат, а фрагмент отсутствует в стеке или в состоянии возобновления, доступ к представлению фрагмента вызовет исключение NullPointer. Итак, всякий раз, когда вы получаете доступ к getView или getActivity после фоновой операции или задержки, убедитесь, что вы отменили все фоновые операции при завершении.

Пример:

Вложенный фрагмент onActivityResult

Да, onActivityResult() во вложенном фрагменте не вызывается.

Последовательность вызова onActivityResult (в библиотеке поддержки Android):

  1. Activity.dispatchActivityResult().
  2. FragmentActivity.onActivityResult().
  3. Fragment.onActivityResult().

Вам нужно будет использовать onActivityResult() в родительских фрагментах или активности и передать результат во вложенный фрагмент, как показано ниже:

Фрагмент и пакет

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

В документации Android говорится:

Каждый фрагмент должен иметь пустой конструктор, чтобы его можно было создать при восстановлении состояния его активности. Настоятельно рекомендуется, чтобы подклассы не имели других конструкторов с параметрами, поскольку эти конструкторы не будут вызываться при повторном создании экземпляра фрагмента; вместо этого аргументы могут быть предоставлены вызывающей стороной с помощью setArguments (Bundle), а затем получены фрагментом с помощью getArguments ().

Вот почему для установки параметров фрагмента лучше использовать бандл, чтобы системе было проще восстановить его значения при повторном создании экземпляра фрагмента.

Назад Навигация

Вы должны убедиться, что нажатие кнопки Назад на подробном экране возвращает пользователя на главный экран. Для этого перед фиксацией транзакции позвоните addToBackStack():

Когда в заднем стеке есть объекты FragmentTransaction и пользователь нажимает кнопку Назад, FragmentManager извлекает самую последнюю транзакцию из заднего стека и выполняет обратное действие (например, удаление фрагмента, если транзакция добавила его).

Заключение

Сначала фрагменты могут показаться довольно простыми, но это еще не все. При использовании фрагментов нужно быть осторожным со многими вещами, такими как память, навигация, обратные вызовы и пакет. Я надеюсь, что в этой статье были рассмотрены наиболее часто встречающиеся проблемы и наиболее частые ошибки.