- Android Fragment Result Listener
- Как это работает?
- Как это выглядит в коде?
- Передача данных
- Получение данных
- Parent Fragment Manger
- Тестирование
- Передача данных
- Получение данных
- Вывод
- Полный список
- Доступ к фрагменту из Activity
- Доступ к Activity из фрагмента
- Обработка в Activity события из фрагмента
- Android Fragments Common Queries & Common Mistakes
- getSupportFragmentManager and getChildFragmentManager
- Callback from DialogFragment, ChildFragment, BottomSheetFragment to parent fragment
- Fragments when using ViewPager and adapters FragmentStateAdapter and FragmentPagerAdapter which one one to use when
- amodkanthe/ViewPagerTest
- Contribute to amodkanthe/ViewPagerTest development by creating an account on GitHub.
- FragmentTransaction add vs replace
- Fragment receivers, broadcasts and memory leaks
- Fragment BottomBarNavigation and drawer how to handle these
Android Fragment Result Listener
В Android передача данных между фрагментами может осуществляться разными способами: передача через родительскую Activity, используя ViewModel или даже Fragments API. Fragment Target API с недавних пор получил статус Deprecated и вместо него Google рекомендует использовать Fragment result API.
Что такое Fragment result API? Это новый инструмент от Google который позволяет передавать данные между фрагментами по ключу. Для этого используется FragmentManager, который в свою очередь реализует интерфейс FragmentResultOwner. FragmentResultOwner выступает в качестве центрального хранилища для данных, которые мы передаем между фрагментами.
Как это работает?
Как упоминалось выше, наш FragmentManager реализует интерфейс FragmentResultOwner, который хранит в себе ConcurrentHashMap . Эта HashMap хранит наши Bundle-ы по строковому ключу. Как только один из фрагментов подписывается (или уже подписан) то он получает результат по тому самому ключу.
Что важно знать:
- Если какой-либо фрагмент подписывается на результат методом setResultFragmentListener() после того, как отправляющий фрагмент вызовет setFragmentResult() , то он немедленно получит результат
- Каждую связку “Key + Result (Bundle)“ фрагмент получает только 1 раз
- Фрагменты которые находятся в бек стеке получат результат только после того как перейдут в состояние STARTED
- После того как фрагмент перейдет в состояние DESTROYED мы больше не сможем подписываться на ResultListener
Как это выглядит в коде?
Передача данных
Для передачи данных в другой фрагмент нам необходимо вызвать метод:
В параметры метода мы кладем ключ, который и будет нашим идентификатором для получения данных и сам Bundle. Этот Bundle будет содержать в себе передаваемые данные.
Получение данных
Для получения данных через FragmentManager мы регистрируем наш FragmentResultListener и задаем ключ по которому мы будем получать данные. Тот самый ключ который мы указывали в методе FragmentManager.setFragmentResult()
Здесь мы видим 2 аргумента: key: String и bundle: Bundle.
Первый — это тот самый ключ, по которому мы передаем сюда данные. Второй — Bundle, в котором лежат переданные данные.
Parent Fragment Manger
Выбор FragmentManager-а для передачи данных между фрагментами зависит от принимающего фрагмента:
- Если оба фрагмента находятся в одном и том же FragmentManager (например оба фрагмента находятся в Activity), то мы должны использовать родительский FragmentManager, который хранит в себе Activity
- Если у нас один фрагмент вложен в другой фрагмент, то для передачи данных мы используем childFragmentManager (он же родительский фрагмент для принимающего фрагмента)
Важно понимать, что наш FragmentResultListener должен находиться в общем для двух фрагментов FragmentManager-е.
Тестирование
Для тестирования отправки/получения данных через FragmentResultListener, мы можем использовать FragmentScenario API, который предоставляет нам все преимущества тестирования фрагментов в изоляции.
Передача данных
Как мы можем протестировать, что наш фрагмент корректно отправляет данные через родительский FragmentManager? Для этого нам необходимо внутри теста отправить результат и проверить, что наш FragmentResultListener получил корректные данные:
Получение данных
Для проверки корректности получения данных мы можем симулировать отправку данных, используя родительский FragmentManager. Если в отправляющем фрагменте корректно установлен FragmentResultListener мы должны получить корректные данные проверяя сам листенер или последствие их получения.
Вывод
В данный момент FragmentResultListener находится в альфе, а это значит что возможно еще будут изменения со стороны Google. Но уже сейчас видно, что это достаточно крутой инструмент, для передачи данных между фрагментами, не создавая дополнительных интерфейсов и классов. Единственным нюансом остается, пожалуй то, что не совсем понятно, как и где лучше хранить ключи где, но это не кажется таким уж большим минусом.
Для того чтоб получить возможность использовать FragmentResultListener нам нужно подключить в зависимостях версию фрагментов 1.3.0-alpha04 или новее:
Источник
Полный список
— рассмотрим взаимодействие между Activity и ее фрагментами
После размещения фрагмента, хотелось бы начать с ним взаимодействовать. Т.е. размещать View-компоненты и работать с ними, обращаться к фрагментам из Activity и наоборот. Попробуем это реализовать.
Для чистоты эксперимента будем работать с двумя фрагментами: статическим и динамическим.
Project name: P1061_FragmentActivity
Build Target: Android 4.1
Application name: FragmentActivity
Package name: ru.startandroid.develop.p1061fragmentactivity
Create Activity: MainActivity
В strings.xml добавим строки:
Создаем layout и классы для двух фрагментов.
У фрагмента нет привычного для нас метода findViewById для поиска компонентов с экрана. Поэтому вызываем этот метод для View, которое будет содержимым фрагмента. В методе onCreateView мы создаем View и сразу же находим в нем кнопку и ставим ей обработчик. Затем отдаем View системе.
Все аналогично Fragment1.
Настраиваем основное Activity.
Кнопка, компонент fragment, в который помещен Fragment1, и контейнер FrameLayout, в который потом поместим Fragment2.
Обратите внимание на атрибут tools:layout. В нем указан layout-файл, и мы можем на этапе разработки видеть, как будет выглядеть статический фрагмент, когда приложение будет запущено.
Для этого надо нажать правой кнопкой на компоненте fragment, и через пункт Fragment Layout указать нужный layout.
Здесь мы просто добавляем Fragment2 в контейнер.
Все сохраняем, запускаем приложение.
Жмем кнопку Log в первом фрагменте и смотрим лог:
Button click in Fragment1
Жмем Log во втором фрагменте:
Button click in Fragment2
Все ок. Компоненты в фрагментах нашлись и обработчики среагировали на нажатия.
Атрибут onClick, который мы привыкли использовать для кнопки, здесь не прокатит. Указанный в этом атрибуте метод, будет вызван в Activity, а не в фрагменте.
Доступ к фрагменту из Activity
Разберемся, как получить доступ к фрагменту из Activity. Для этого у FragmentManager есть метод findFragmentById, который на вход принимает id компонента fragment (если фрагмент статический) или id контейнера (если динамический).
У нас в main.xml есть кнопка btnFind, вызывающая метод onClick при нажатии. Дорисуем в MainActivity.java метод onClick:
Используем метод findFragmentById. В первом случае на вход передаем id компонента fragment, т.к. Fragment1 у нас размещен именно так. При поиске Fragment2 указываем id контейнера, в который этот фрагмент был помещен. В результате метод findFragmentById возвращает нам объект Fragment.
Далее мы получаем доступ к его View с помощью метода getView, находим в нем TextView и меняем текст.
Все сохраняем, запускаем. Жмем кнопку Find
Тексты в фрагментах обновились. Тем самым из Activity мы достучались до фрагментов и их компонентов.
На всякий случай проговорю одну вещь из разряда «Спасибо кэп!». Если посмотреть на код MainActivity, то можно заметить, что работая с frag2 в методе onCreate и с frag2 в методе onClick мы работаем с текущим фрагментом Fragment2. Это так и есть. Оба frag2 в итоге будут ссылаться на один объект. Так что, если вы динамически добавили фрагмент, то у вас уже есть ссылка на него, и искать его через findFragmentById вам уже не надо.
Доступ к Activity из фрагмента
Теперь попробуем из фрагмента поработать с Activity. Для этого фрагмент имеет метод getActivity.
Давайте перепишем обработчик кнопки в первом фрагменте. Будем менять текст кнопки btnFind.
Получаем Activity методом getActivity, ищем в нем кнопку и меняем текст.
Сохраняем, запускаем. Жмем кнопку в первом фрагменте:
Работает. Из фрагмента мы поменяли компонент Activity.
Обработка в Activity события из фрагмента
Рассмотрим механизм, который описан в хелпе: фрагмент генерирует некое событие и ставит Activity обработчиком.
Например, в Activity есть два фрагмента. Первый – список заголовков статей. Второй – отображает содержимое статьи, выбранной в первом. Мы нажимаем на заголовок статьи в первом фрагменте и получаем содержимое во втором. В этом случае, цель первого фрагмента – передать в Activity информацию о том, что выбран заголовок. А Activity дальше уже сама решает, что делать с этой информацией. Если, например, приложение запущено на планшете в горизонтальной ориентации, то можно отобразить содержимое статьи во втором фрагменте. Если же приложение запущено на смартфоне, то экран маловат для двух фрагментов и надо запускать отдельное Activity со вторым фрагментом, чтобы отобразить статью.
Фишка тут в том, что первому фрагменту неинтересны все эти терзания Activity. Фрагмент – обособленный модуль. Его дело — проинформировать, что выбрана статья такая-то. Ему не надо искать второй фрагмент и работать с ним – это дело Activity.
Тут немного отвлекусь на небольшое лирическое отступление. Модульность, вообще, — очень важная и полезная штука. И ее надо использовать для универсальности, удобности и легкости в понимании работы своих приложений. Но уникальных рецептов, как правильно все организовать, конечно, нет. Каждый делает по-своему. Именно по этим причинам я в своих уроках даю чисто технические вещи про отдельные компоненты и не рассказываю, как организовывать и писать целое приложение. Иначе, форум бы уже ломился от сообщений, что я все делаю не так и надо по-другому, и каждый бы излагал свое видение. И была бы куча споров, где одна сторона говорит, что крокодил зеленый, а другая сторона говорит, что он нифига не зеленый, а длинный ))
Вернемся к уроку. Фрагмент должен сообщить в Activity, что выбрана статья. Для этого он будет вызывать некий метод в Activity. И как нам сообщает хелп, лучший способ тут – это использовать интерфейс, который мы опишем в фрагменте и который затем будет реализован в Activity. Схема известная и распространенная. Давайте реализуем. В нашем приложении никаких статей нет, поэтому будем просто передавать произвольную строку из второго фрагмента в Activity. А Activity уже будет отображать эту строку в первом фрагменте.
Описываем интерфейс onSomeEventListener. В нем метод someEvent, который на вход получает строку. Этот интерфейс будет реализовывать Activity.
В методе onAttach мы на вход получаем Activity, к которому присоединен фрагмент. Мы пытаемся привести это Activity к типу интерфейса onSomeEventListener, чтобы можно было вызывать метод someEvent и передать туда строку. Теперь someEventListener ссылается на Activity.
Далее, в onCreateView, в обработчике кнопки мы вызываем метод someEvent и передаем туда текст. Этот метод будет отработан в Activity.
Теперь меняем Activity.
Дописываем интерфейс onSomeEventListener к описанию класса.
onCreate без изменений.
Реализуем метод someEvent. Просто ищем первый фрагмент и вставляем туда текст.
Все сохраняем и запускаем. Жмем кнопку во втором фрагменте:
Второй фрагмент передал через интерфейс строку в Activity, а оно нашло первый фрагмент и отобразило там эту строку.
На следующем уроке:
— размещаем элементы в ActionBar
Присоединяйтесь к нам в Telegram:
— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование
— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня
— новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме
Источник
Android Fragments Common Queries & Common Mistakes
Fragment class in Android is used to build dynamic User Interfaces. Fragment should be used within the Activity. A greatest advantage of fragments is that it simplifies the task of creating UI for multiple screen sizes. A activity can contain any number of fragments.
Now this meaning of fragment sounds good and easy, right? But there is lot more involved, this article covers main needs and common mistakes while using Fragments.
I am assuming you are having basic knowledge of Fragment and Fragment lifecycle callbacks also I am assuming you know how implement communication between two fragments this article goes beyond that
So here are a few obstacles related to fragments some of you must have faced already, some of you might face later.
- FragmentManager: getSupportFragmentManager and getChildFragmentManager. Which one to use when and avoid memory leaks while using them.
- Callback from DialogFragment, ChildFragment, BottomSheetFragment to parent fragment.
- Fragments when using ViewPager and when to use FragmentStateAdapter vs FragmentPagerAdapter.
- When to use FragmentTransaction add vs replace ?
- Fragment receivers, broadcasts and memory leaks.
- Fragment BottomBarNavigation and drawer. How to handle these?
- commit() and commitAllowingStateLoss()
- Fragment option menus.
- Fragment getActivity(), getView() and NullPointers Exceptions.
- onActivityResult with nested fragments.
- Fragment and Bundle.
- Back Navigation.
Whoa . see its a big list, reply in comment if anyone wish to add something more to the list.
getSupportFragmentManager and getChildFragmentManager
FragmentManager is class provided by the framework which is used to create transactions for adding, removing or replacing fragments.
- getSupportFragmentManager is associated with Activity consider it as a FragmentManager for your Activity .
So whenever you are using ViewPager, BottomSheetFragment and DialogFragment in an Activity you will use getSupportFragmentManager
- getChildFragmentManager is associated with fragment.
Whenever you are ViewPager inside Fragment you will use getChildFragmentManager
Here is official link for this for better understanding.
Now coming to common mistakes people do when they are using ViewPager inside a Fragment they pass getSupportFragmentManager which is fragment manager for Activity and it causes issues as such memory leaks, ViewPager not updated properly sometimes etc.
Most important issue caused by using getSupportFragmentManager in Fragment is memory leak, let me tell you how? Your Fragment has stack of fragments which is used by ViewPager or any other thing and all these fragments stack is in activity since you used getSupportFragmentManager , now if close your Parent fragment it will be closed but it will not be destroyed because all child Fragments are in activity and they are still in memory which does not allow to destroy Parent Fragment hence causing leak. It will not just leak parent fragment but also leak all child fragments since none of them can be cleared from heap memory. So never try to use getSupportFragmentManager in a Fragment
Callback from DialogFragment, ChildFragment, BottomSheetFragment to parent fragment
This is very common issue people face when they use BottomSheetFragment or DialogFragment or ChildFragment.
Add a child fragment
Another example bottomSheetFragment
Now suppose you want callback from these child fragments to parent fragments . Most of people create connection between two fragments using activity, few people pass interface listeners as a parameter to fragment which really a bad practice and one should avoid this. Best way calling getParentFragment() from your child fragment to create a call back this is very simple way consider example below.
then setting callback to parent fragment add following code in child fragment.
thats it you can give a callback to your parent fragment now easily.
Using same way you can create a callback from child fragment inside ViewPager to parent fragment who is holding ViewPager.
Fragments when using ViewPager and adapters FragmentStateAdapter and FragmentPagerAdapter which one one to use when
FragmentPagerAdapter stores the whole fragment in memory, and could increase a memory overhead if a large amount of fragments are used in ViewPager . FragmentStatePagerAdapter only stores the savedInstanceState of fragments, and destroys all the fragments when they lose focus.
So when your is going to have many Fragments use FragmentStateAdapter if ViewPager is going to have less than three fragments use FragmentPagerAdapter.
Commonly faced issues
Update ViewPager not working:
People always come across the issue remember ViewPager fragments are managed by FragmentManager either from Fragment or Activity and this FragmentManager holds instance of all ViewPager Fragments.
So when people say ViewPager is not refreshed it’s nothing but old instances of fragments are still being hold by FragmentManager. So you need to find out why FragmentManger is holding instance of Fragments is there any leak or not ideally to refresh ViewPager following code works if it is not you are doing something wrong
Access current Fragment from ViewPager:
This is also very common issue we come across. For this people either create array list of fragments inside adapter or try to access fragment using some tags according to me both methods are not reliable. FragmentStateAdapter and FragmentPagerAdapter both provides method setPrimaryItem this can be used to set current fragment as below.
I am leaving a Github link below to this simple ViewPager project so that everyone can understand better.
amodkanthe/ViewPagerTest
Contribute to amodkanthe/ViewPagerTest development by creating an account on GitHub.
FragmentTransaction add vs replace
In our Activity we have a container and inside this container we display our Fragments
add will simply add fragment to container suppose you add FragmentA and FragmentB to container. Container will have FragmentA and FragmentB both and suppose if container is FrameLayout fragments are added one above the other. replace will simply replace a fragment on top of container so if I call create FragmentC now and call replace FragmentB which was on top will removed from container unless you are not calling addToBackStack and now FragmentC will be on top.
So which one to use when. replace removes the existing fragment and adds a new fragment . This means when you press back button the fragment that got replaced will be created with its onCreateView being invoked. Whereas add retains the existing fragments and adds a new fragment that means existing fragment will be active and they wont be in ‘paused’ state hence when a back button is pressed onCreateView is not called for the existing fragment(the fragment which was there before new fragment was added). In terms of fragment’s life cycle events onPause, onResume, onCreateView and other life cycle events will be invoked in case of replace but they wont be invoked in case of add .
Use replace fragment if don’t need to revisit current fragment and current fragment is not require anymore. Also if your app has memory constraints use replace instead of add.
Fragment receivers, broadcasts and memory leaks
Mistakes people commonly do when using receivers inside a fragment forgot to unregister receiver in onPause or OnDestroy. If you are registering fragment to listen to receiver inside onCreate or OnResume you will have to unregister it inside onPause or onDestroy otherwise it will cause memory leak
Also if have multiple fragments listening to same broadcast receiver make sure you register in onResume and unregister in onPause. Because if you use onCreate and onDestroy for register and unregister other fragments will not receive the broadcast as this fragment is not destroyed
Fragment BottomBarNavigation and drawer how to handle these
When we are using BottomBarNavigation and NavigationDrawer people face issues like fragments recreated, same fragment is added multiple times etc.
So in such case you can use fragment transaction show and hide instead of add or replace.
There is also one beautiful library which take care of navigations and avoid recreation of fragments called FragNav below is link to it
Источник