Android фрагменты или activity

Фрагменты

Существует два основных подхода в использовании фрагментов.

Первый способ основан на замещении родительского контейнера. Создаётся стандартная разметка и в том месте, где будут использоваться фрагменты, размещается контейнер, например, FrameLayout. В коде контейнер замещается фрагментом. При использовании подобного сценария в разметке не используется тег fragment, так как его нельзя менять динамически. Также вам придётся обновлять ActionBar, если он зависит от фрагмента. Здесь показан такой пример.

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

Второй подход является наиболее гибким и в целом предпочтительным способом использования фрагментов. Активность проверяет в каком режиме (свои размеры) он запущен и использует разную разметку из ресурсов. Графически это выглядит следующим образом.

Основные классы

Сами фрагменты наследуются от androidx.fragment.app.Fragment. Существует подклассы фрагментов: ListFragment, DialogFragment, PreferenceFragment, WebViewFragment и др. Не исключено, что число классов будет увеличиваться, например, появился ещё один класс MapFragment.

Для взаимодействия между фрагментами используется класс android.app.FragmentManager — специальный менеджер по фрагментам.

Как в любом офисе, спецманагер не делает работу своими руками, а использует помощников. Например, для транзакций (добавление, удаление, замена) используется класс-помощник android.app.FragmentTransaction.

Для сравнения приведу названия классов из библиотеки совместимости:

  • android.support.v4.app.FragmentActivity
  • android.support.v4.app.Fragment
  • android.support.v4.app.FragmentManager
  • android.support.v4.app.FragmentTransaction

Как видите, разница в одном классе, который я привёл первым. Он используется вместо стандартного Activity, чтобы система поняла, что придётся работать с фрагментами. На данный момент студия создаёт проект на основе ActionBarActivity, который является подклассом FragmentActivity.

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

В 2018 году Гугл объявила фрагменты из пакета androd.app устаревшими. Заменяйте везде на версию из библиотеки совместимости. В 2020 году уже используют пакет androidx.fragment.app.

В версии Support Library 27.1.0 появились новые методы requireActivity() и requireContext(), которые пригодятся при написании кода, когда требуется наличие активности и нужно избежать ошибки на null.

Общий алгоритм работы с фрагментами будет следующим:

У каждого фрагмента должен быть свой класс. Класс наследуется от класса Fragment или схожих классов, о которых говорилось выше. Это похоже на создание новой активности или нового компонента.

Также, как в активности, вы создаёте различные методы типа onCreate() и т.д. Если фрагмент имеет разметку, то используется метод onCreateView() — считайте его аналогом метода setContentView(), в котором вы подключали разметку активности. При этом метод onCreateView() возвращает объект View, который является корневым элементом разметки фрагмента.

Разметку для фрагмента можно создать программно или декларативно через XML.

Создание разметки для фрагмента ничем не отличается от создания разметки для активности. Вот отрывок кода из метода onCreateView():

Глядя на этот код, вы должные понять, что фрагмент использует разметку из файла res/layout/first_fragment.xml, которая содержит кнопку с идентификатором android:id=»@+id/button_first». Здесь также прослеживается сходство с подключением компонентов в активности. Обратите внимание, что перед методом findViewById() используется view, так как этот метод относится к компоненту, а не к активности, как мы обычно делали в программах, когда просто опускали имя активности. Т.е. в нашем случае мы ищем ссылку на кнопку не среди разметки активности, а внутри разметки самого фрагмента.

Читайте также:  Как посмотреть удаленные смс андроид

Нужно помнить, что в методе inflate() последний параметр должен иметь значение false в большинстве случаев.

FragmentManager

Класс FragmentManager имеет два метода, позволяющих найти фрагмент, который связан с активностью:

findFragmentById(int id) Находит фрагмент по идентификатору findFragmentByTag(String tag) Находит фрагмент по заданному тегу

Методы транзакции

Мы уже использовали некоторые методы класса FragmentTransaction. Познакомимся с ними поближе

add() Добавляет фрагмент к активности remove() Удаляет фрагмент из активности replace() Заменяет один фрагмент на другой hide() Прячет фрагмент (делает невидимым на экране) show() Выводит скрытый фрагмент на экран detach() (API 13) Отсоединяет фрагмент от графического интерфейса, но экземпляр класса сохраняется attach() (API 13) Присоединяет фрагмент, который был отсоединён методом detach()

Методы remove(), replace(), detach(), attach() не применимы к статичным фрагментам.

Перед началом транзакции нужно получить экземпляр FragmentTransaction через метод FragmentManager.beginTransaction(). Далее вызываются различные методы для управления фрагментами.

В конце любой транзакции, которая может состоять из цепочки вышеперечисленных методов, следует вызвать метод commit().

Аргументы фрагмента

Фрагменты должны сохранять свою модульность и не должны общаться друг с другом напрямую. Если один фрагмент хочет докопаться до другого, он должен сообщить об этом своему менеджеру активности, а он уже передаст просьбу другому фрагменту. И наоборот. Это сделано специально для того, чтобы было понятно, что менеджер тут главный и он не зря зарплату получает. Есть три основных способа общения фрагмента с активностью.

  • Активность может создать фрагмент и установить аргументы для него
  • Активность может вызвать методы экземпляра фрагмента
  • Фрагмент может реализовать интерфейс, который будет использован в активности в виде слушателя

Фрагмент должен иметь только один пустой конструктор без аргументов. Но можно создать статический newInstance с аргументами через метод setArguments().

Доступ к аргументам можно получить в методе onCreate() фрагмента:

Динамически загружаем фрагмент в активность.

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

Вызываем метод в активности:

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

Управление стеком фрагментов

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

Чтобы добавить транзакцию в стек, вызовите метод FragmentTransaction.addToBackStack(String) перед завершением транзакции (commit). Строковый аргумент — опциональное имя для идентификации стека или null. Класс FragmentManager имеет метод popBackStack(), возвращающий предыдущее состояние стека по этому имени.

Если вы вызовете метод addToBackStack() при удалении или замещении фрагмента, то будут вызваны методы фрагмента onPause(), onStop(), onDestroyView().

Когда пользователь нажимает на кнопку возврата, то вызываются методы фрагмента onCreateView(), onActivityCreated(), onStart() и onResume().

Рассмотрим пример реагирования на кнопку Back в фрагменте без использования стека. Активность имеет метод onBackPressed(), который реагирует на нажатие кнопки. Мы можем в этом методе сослаться на нужный фрагмент и вызвать метод фрагмента.

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

Более желательным вариантом является использование интерфейсов. В некоторых примерах с фрагментами такой приём используется.

Интеграция Action Bar/Options Menu

Фрагменты могут добавлять свои элементы в панель действий или меню активности. Сначала вы должны вызвать метод Fragment.setHasOptionsMenu() в методе фрагмента onCreate(). Затем нужно задать настройки для методов фрагмента onCreateOptionsMenu() и onOptionsItemSelected(), а также при необходимости для методов onPrepareOptionsMenu(), onOptionsMenuClosed(), onDestroyOptionsMenu(). Работа методов фрагмента ничем не отличается от аналогичных методов для активности.

В активности, которая содержит фрагмент, данные методы автоматически сработают.

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

Читайте также:  Dynasty warriors для андроид

Код для активности:

Код для фрагмента:

Связь между фрагментом и активностью

Экземпляр фрагмента связан с активностью. Активность может вызывать методы фрагмента через ссылку на объект фрагмента. Доступ к фрагменту можно получить через методы findFragmentById() или findFragmentByTag().

Источник

Аргументы против использования фрагментов в Android

Недавно я выступал на конференции Droidcon в Париже с докладом (оригинал на французском), в котором рассматривал проблемы, возникшие у нас в Square при работе с фрагментами и возможности полного отказа от фрагментов.

В 2011-м мы решили использовать фрагменты по следующим причинам:

  • Тогда мы ещё не поддерживали планшеты, но знали, что когда-нибудь будем. Фрагменты помогают создавать адаптивный пользовательский интерфейс, и потому казались хорошим выбором.
  • Фрагменты являются контроллерами представлений, они содержат куски бизнес-логики, которые могут быть протестированы.
  • API фрагментов предоставляет возможность работы с backstack’ом (в общих чертах это выглядит так же, как и работа со стэком Activity , но при этом вы остаётесь в рамках одной Activity ).
  • Так как фрагменты построены на обычных представлениях (views), а представления могут быть анимированы средствами Android-фреймворка, то фрагменты могли в теории дать нам возможность использовать более интересные переходы между экранами.
  • Google рекомендовал фрагменты к использованию, а мы хотели сделать наш код как можно более стандартным.

С 2011-го года много воды утекло, и мы нашли варианты получше.

Чего ваши родители никогда не говорили вам о фрагментах

Жизненный цикл

Context в Android является божественным объектом, а Activity — это Context с дополнительным жизненным циклом. Божество с жизненным циклом? Ироничненько. Фрагменты в божественный пантеон не входят, но они с лихвой компенсируют этот недостаток очень сложным жизненным циклом.

Стив Помрой (Steve Pomeroy) сделал диаграмму всех переходов в жизненном цикле фрагмента, и особого оптимизма она не внушает:

Сделано Стивом Помроем, слегка изменено с целью удалить жизненный цикл Activity и выложено под лицензией CC BY-SA 4.0.

Жизненный цикл ставит перед вами множество интереснейших вопросов. Что можно, а что нельзя делать в каждой упомянутой выше функции обратного вызова? Они вызываются синхронно, или по очереди? Если по очереди, то в каком порядке?

Отладка затрудняется

Когда в вашу программу закрадывается ошибка, вы берёте отладчик и исполняете код инструкция за инструкцией, чтобы понять, что же происходит. И всё прекрасно, пока вы не дошли до класса FragmentManagerImpl . Осторожно, мина!

С этим кодом довольно сложно разобраться, что затрудняет процесс поиска ошибок в вашем приложении:

Если вы хоть раз обнаруживали, что у вас на руках созданный заново после поворота экрана и не присоединённый к Activity фрагмент, то вы понимаете о чём я говорю (и ради всего святого, не испытывайте судьбу и не упоминайте при мне про вложенные фрагменты).

Закон обязывает меня (по крайней мере я читал об этом на Coding Horror) приложить к посту следующее изображение, так что не обессудьте:

После нескольких лет глубокого анализа я пришёл к тому, что количество WTF в минуту при отладке Android-приложения равно 2 fragment_count .

Магия создания фрагментов

Фрагмент может быть создан вами или классом FragmentManager . Взгляните на следующий код, всё просто и понятно, да?

Однако, когда происходит восстановление состояния Activity , FragmentManager может попытаться создать фрагмент заново через рефлексию. Так как мы наверху создали анонимный класс, в его конструкторе есть скрытый аргумент, ссылающийся на внешний класс. Бамс:

Что мы поняли, поработав со фрагментами

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

  • Нет никакой необходимости создавать одну Activity для каждого экрана. Мы можем разнести наш интерфейс по отдельным виджетам и компоновать их как нам нужно. Это упрощает анимации интерфейса и жизненный цикл. Мы также можем разделить наши виджеты на классы-представления и классы-контроллеры.
  • Backstack не является чем-то, имеющим отношение к нескольким Activity ; можно спокойно создать его и внутри одной-единственной Activity .
  • Не нужно создавать новые API; всё, что нам нужно ( Activity , Views , Layout Inflaters ), было в Android с самого начала.
Читайте также:  Караоке для андроид тв бокс

Адаптивный интерфейс: фрагменты против представлений

Фрагменты

Давайте посмотрим на простой пример с фрагментами: интерфейс, состоящий из списка и детализированного представления каждого элемента списка.

HeadlinesFragment представляет из себя список элементов:

Переходим к более интересному: ListFragmentActivity должна разбираться с тем, показывать ли детали на том же экране что и список, или нет:

Представления

Давайте перепишем этот код, используя самописные представления. Во-первых, мы введём понятие контейнера ( Container ), который может показать элемент списка, а также обрабатывает нажатия назад:

Activity знает, что у неё всегда есть на руках контейнер, и просто делегирует ему нужную работу:

Реализация списка тоже является довольно тривиальной:

Переходим к интересному: загрузке разных разметок в зависимости от классификаторов ресурсов:

Реализация этих контейнеров:

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

Представления и презентеры

Самописные представления — это хорошо, но хочется большего: хочется выделить бизнес-логику в отдельные контроллеры. Мы будем называть подобные контроллеры презентерами ( Presenters ). Введение презентеров позволит нам сделать код более читабельным и упростит дальнейшее тестирование:

Давайте посмотрим на код, взятый с экрана редактирования скидок приложения Square Register.

Презентер осуществляет высокоуровневую манипуляцию представлением:

Тестировать этот презентер очень просто:

Управление backstack’ом

Мы написали библиотеку Flow, чтобы упростить себе работу с backstack’ом, а Ray Rayan написал о ней очень хорошую статью. Не вдаваясь особо в подробности, скажу, что код получился довольно простым, так как асинхронные транзакции больше не нужны.

Я глубоко завяз в спагетти из фрагментов, что мне делать?

Вынесите из фрагментов всё, что можно. Код, относящийся к интерфейсу, должен уйти в ваши собственные представления, а бизнес-логику нужно убрать в презентеры, которые знают, как работать с вашими представлениями. После этого у вас останется почти пустой фрагмент, создающий ваши собственные представления (а те, в свою очередь, знают как и с какими презентерами себя связать):

Всё, фрагмент можно удалить.

Мигрирование с фрагментов было не простым, но мы прошли его — благодаря отличной работе Dimitris Koutsogiorgas и Ray Ryan.

А для чего нужны Dagger и Mortar?

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

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

Mortar работает поверх Dagger’а, и у него есть два важных преимущества:

  • Он предоставляет инжектированным компонентам функции обратного вызова, привязанные к жизненному циклу Android-приложения. Таким образом, вы можете написать презентер, который будет синглетоном, не будет разрушаться при повороте экрана, но при этом сможет сохранить своё состояние в Bundle , чтобы пережить смерть процесса.
  • Он управляет подграфами Dagger’а, и позволяет привязывать их к жизненному циклу Activity . Таким образом вы можете создавать области видимости: как только представление появляется на экране, Dagger/Mortar создают его презентер и остальные зависимости. Когда представление уходит с экрана, вы уничтожаете эту область видимости (содержащую презентер и зависимости) и сборщик мусора принимается за дело.

Заключение

Мы интенсивно использовали фрагменты, но со временем передумали и избавились от них:

  • Большинство наиболее сложных падений наших приложений были связаны с жизненным циклом фрагментов.
  • Для создания адаптивного интерфейса, backstack’а и анимированных переходов между экранами нам нужны только представления.

Источник

Оцените статью