- Получаем результат правильно (Часть 1). Activity Result API
- Чем плох onActivityResult()?
- Используем Activity Result API
- Шаг 1. Создание контракта
- Шаг 2. Регистрация контракта
- Шаг 3. Запуск контракта
- Важно!
- Работа с runtime permissions
- Подводим итоги
- Получаем результат правильно (Часть 1). Activity Result API
- Чем плох onActivityResult()?
- Используем Activity Result API
- Шаг 1. Создание контракта
- Шаг 2. Регистрация контракта
- Шаг 3. Запуск контракта
- Важно!
- Работа с runtime permissions
- Подводим итоги
- 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
Получаем результат правильно (Часть 1). Activity Result API
Каждый Android-разработчик сталкивался с необходимостью передать данные из одной Activity в другую. Эта тривиальная задача зачастую вынуждает нас писать не самый элегантный код.
Наконец, в 2020 году Google представила решение старой проблемы — Activity Result API. Это мощный инструмент для обмена данными между активностями и запроса runtime permissions.
В данной статье мы разберёмся, как использовать новый API и какими преимуществами он обладает.
Чем плох onActivityResult()?
Роберт Мартин в книге “Чистый код” отмечает важность переиспользования кода — принцип DRY или Don’t repeat yourself, а также призывает проектировать компактные функции, которые выполняют лишь единственную операцию.
Проблема onActivityResult() в том, что при его использовании соблюдение подобных рекомендаций становится практически невозможным. Убедимся в этом на примере простого экрана, который запрашивает доступ к камере, делает фото и открывает второй экран — SecondActivity . Пусть в SecondActivity мы передаём строку, а назад получаем целое значение.
Очевидно, что метод onActivityResult() нарушает принцип единственной ответственности, ведь он отвечает и за обработку результата фотографирования и за получение данных от второй Activity. Да и выглядит этот метод уже довольно запутанно, хоть мы и рассмотрели простой пример и опустили часть деталей.
Кроме того, если в приложении появится другой экран со схожей функциональностью, мы не сможем переиспользовать этот код и будем вынуждены его дублировать.
Используем Activity Result API
Новый API доступен начиная с AndroidX Activity 1.2.0-alpha02 и Fragment 1.3.0-alpha02 , поэтому добавим актуальные версии соответствующих зависимостей в build.gradle:
Применение Activity Result состоит из трех шагов:
Шаг 1. Создание контракта
Контракт — это класс, реализующий интерфейс ActivityResultContract . Где I определяет тип входных данных, необходимых для запуска Activity, а O — тип возвращаемого результата.
Для типовых задач можно воспользоваться реализациями “из коробки”: PickContact , TakePicture , RequestPermission и другими. Полный список доступен тут.
При создании контракта мы обязаны реализовать два его метода:
createIntent() — принимает входные данные и создает интент, который будет в дальнейшем запущен вызовом launch()
parseResult() — отвечает за возврат результата, обработку resultCode и парсинг данных
Ещё один метод — getSynchronousResult() — можно переопределить в случае необходимости. Он позволяет сразу же, без запуска Activity, вернуть результат, например, если получены невалидные входные данные. Если подобное поведение не требуется, метод по умолчанию возвращает null .
Ниже представлен пример контракта, который принимает строку и запускает SecondActivity, ожидая от неё целое число:
Шаг 2. Регистрация контракта
Следующий этап — регистрация контракта в активности или фрагменте с помощью вызова registerForActivityResult() . В параметры необходимо передать ActivityResultContract и ActivityResultCallback . Коллбек сработает при получении результата.
Регистрация контракта не запускает новую Activity , а лишь возвращает специальный объект ActivityResultLauncher , который нам понадобится далее.
Шаг 3. Запуск контракта
Для запуска Activity остаётся вызвать launch() на объекте ActivityResultLauncher , который мы получили на предыдущем этапе.
Важно!
Отметим несколько неочевидных моментов, которые необходимо учитывать:
Регистрировать контракты можно в любой момент жизненного цикла активности или фрагмента, но вот запустить его до перехода в состояние CREATED нельзя. Общепринятый подход — регистрация контрактов как полей класса.
Не рекомендуется вызывать registerForActivityResult() внутри операторов if и when . Дело в том, что во время ожидания результата процесс приложения может быть уничтожен системой (например, при открытии камеры, которая требовательна к оперативной памяти). И если при восстановлении процесса мы не зарегистрируем контракт заново, результат будет утерян.
Если запустить неявный интент, а операционная система не сможет найти подходящую Activity, выбрасывается исключение ActivityNotFoundException: “No Activity found to handle Intent”. Чтобы избежать такой ситуации, необходимо перед вызовом launch() или в методе getSynchronousResult() выполнить проверку resolveActivity() c помощью PackageManager .
Работа с runtime permissions
Другим полезным применением Activity Result API является запрос разрешений. Теперь вместо вызовов checkSelfPermission() , requestPermissions() и onRequestPermissionsResult() , стало доступно лаконичное и удобное решение — контракты RequestPermission и RequestMultiplePermissions .
Первый служит для запроса одного разрешения, а второй — сразу нескольких. В колбеке RequestPermission возвращает true , если доступ получен, и false в противном случае. RequestMultiplePermissions вернёт Map , где ключ — это название запрошенного разрешения, а значение — результат запроса.
В реальной жизни запрос разрешений выглядит несколько сложнее. В гайдлайнах Google мы видим следующую диаграмму:
Зачастую разработчики забывают о следующих нюансах при работе с runtime permissions:
Если пользователь ранее уже отклонял наш запрос, рекомендуется дополнительно объяснить, зачем приложению понадобилось данное разрешение (пункт 5a)
При отклонении запроса на разрешение (пункт 8b), стоит не только ограничить функциональность приложения, но и учесть случай, если пользователь поставил галочку “Don’t ask again”
Обнаружить эти граничные ситуации можно при помощи вызова метода shouldShowRequestPermissionRationale() . Если он возвращает true перед запросом разрешения, то стоит рассказать пользователю, как приложение будет использовать разрешение. Если разрешение не выдано и shouldShowRequestPermissionRationale() возвращает false — была выбрана опция “Don’t ask again”, тогда стоит попросить пользователя зайти в настройки и предоставить разрешение вручную.
Реализуем запрос на доступ к камере согласно рассмотренной схеме:
Подводим итоги
Применим знания о новом API на практике и перепишем с их помощью экран из первого примера. В результате мы получим довольно компактный, легко читаемый и масштабируемый код:
Мы увидели недостатки обмена данными через onActivityResult(), узнали о преимуществах Activity Result API и научились использовать его на практике.
Новый API полностью стабилен, в то время как привычные onRequestPermissionsResult() , onActivityResult() и startActivityForResult() стали Deprecated. Самое время вносить изменения в свои проекты!
Демо-приложение с различными примерами использования Activty Result API, в том числе работу с runtime permissions, можно найти в моем Github репозитории.
Источник
Получаем результат правильно (Часть 1). Activity Result API
Каждый Android-разработчик сталкивался с необходимостью передать данные из одной Activity в другую. Эта тривиальная задача зачастую вынуждает нас писать не самый элегантный код.
Наконец, в 2020 году Google представила решение старой проблемы — Activity Result API. Это мощный инструмент для обмена данными между активностями и запроса runtime permissions.
В данной статье мы разберёмся, как использовать новый API и какими преимуществами он обладает.
Чем плох onActivityResult()?
Роберт Мартин в книге “Чистый код” отмечает важность переиспользования кода — принцип DRY или Don’t repeat yourself, а также призывает проектировать компактные функции, которые выполняют лишь единственную операцию.
Проблема onActivityResult() в том, что при его использовании соблюдение подобных рекомендаций становится практически невозможным. Убедимся в этом на примере простого экрана, который запрашивает доступ к камере, делает фото и открывает второй экран — SecondActivity . Пусть в SecondActivity мы передаём строку, а назад получаем целое значение.
Очевидно, что метод onActivityResult() нарушает принцип единственной ответственности, ведь он отвечает и за обработку результата фотографирования и за получение данных от второй Activity. Да и выглядит этот метод уже довольно запутанно, хоть мы и рассмотрели простой пример и опустили часть деталей.
Кроме того, если в приложении появится другой экран со схожей функциональностью, мы не сможем переиспользовать этот код и будем вынуждены его дублировать.
Используем Activity Result API
Новый API доступен начиная с AndroidX Activity 1.2.0-alpha02 и Fragment 1.3.0-alpha02 , поэтому добавим актуальные версии соответствующих зависимостей в build.gradle:
Применение Activity Result состоит из трех шагов:
Шаг 1. Создание контракта
Контракт — это класс, реализующий интерфейс ActivityResultContract . Где I определяет тип входных данных, необходимых для запуска Activity, а O — тип возвращаемого результата.
Для типовых задач можно воспользоваться реализациями “из коробки”: PickContact , TakePicture , RequestPermission и другими. Полный список доступен тут.
При создании контракта мы обязаны реализовать два его метода:
createIntent() — принимает входные данные и создает интент, который будет в дальнейшем запущен вызовом launch()
parseResult() — отвечает за возврат результата, обработку resultCode и парсинг данных
Ещё один метод — getSynchronousResult() — можно переопределить в случае необходимости. Он позволяет сразу же, без запуска Activity, вернуть результат, например, если получены невалидные входные данные. Если подобное поведение не требуется, метод по умолчанию возвращает null .
Ниже представлен пример контракта, который принимает строку и запускает SecondActivity, ожидая от неё целое число:
Шаг 2. Регистрация контракта
Следующий этап — регистрация контракта в активности или фрагменте с помощью вызова registerForActivityResult() . В параметры необходимо передать ActivityResultContract и ActivityResultCallback . Коллбек сработает при получении результата.
Регистрация контракта не запускает новую Activity , а лишь возвращает специальный объект ActivityResultLauncher , который нам понадобится далее.
Шаг 3. Запуск контракта
Для запуска Activity остаётся вызвать launch() на объекте ActivityResultLauncher , который мы получили на предыдущем этапе.
Важно!
Отметим несколько неочевидных моментов, которые необходимо учитывать:
Регистрировать контракты можно в любой момент жизненного цикла активности или фрагмента, но вот запустить его до перехода в состояние CREATED нельзя. Общепринятый подход — регистрация контрактов как полей класса.
Не рекомендуется вызывать registerForActivityResult() внутри операторов if и when . Дело в том, что во время ожидания результата процесс приложения может быть уничтожен системой (например, при открытии камеры, которая требовательна к оперативной памяти). И если при восстановлении процесса мы не зарегистрируем контракт заново, результат будет утерян.
Если запустить неявный интент, а операционная система не сможет найти подходящую Activity, выбрасывается исключение ActivityNotFoundException: “No Activity found to handle Intent”. Чтобы избежать такой ситуации, необходимо перед вызовом launch() или в методе getSynchronousResult() выполнить проверку resolveActivity() c помощью PackageManager .
Работа с runtime permissions
Другим полезным применением Activity Result API является запрос разрешений. Теперь вместо вызовов checkSelfPermission() , requestPermissions() и onRequestPermissionsResult() , стало доступно лаконичное и удобное решение — контракты RequestPermission и RequestMultiplePermissions .
Первый служит для запроса одного разрешения, а второй — сразу нескольких. В колбеке RequestPermission возвращает true , если доступ получен, и false в противном случае. RequestMultiplePermissions вернёт Map , где ключ — это название запрошенного разрешения, а значение — результат запроса.
В реальной жизни запрос разрешений выглядит несколько сложнее. В гайдлайнах Google мы видим следующую диаграмму:
Зачастую разработчики забывают о следующих нюансах при работе с runtime permissions:
Если пользователь ранее уже отклонял наш запрос, рекомендуется дополнительно объяснить, зачем приложению понадобилось данное разрешение (пункт 5a)
При отклонении запроса на разрешение (пункт 8b), стоит не только ограничить функциональность приложения, но и учесть случай, если пользователь поставил галочку “Don’t ask again”
Обнаружить эти граничные ситуации можно при помощи вызова метода shouldShowRequestPermissionRationale() . Если он возвращает true перед запросом разрешения, то стоит рассказать пользователю, как приложение будет использовать разрешение. Если разрешение не выдано и shouldShowRequestPermissionRationale() возвращает false — была выбрана опция “Don’t ask again”, тогда стоит попросить пользователя зайти в настройки и предоставить разрешение вручную.
Реализуем запрос на доступ к камере согласно рассмотренной схеме:
Подводим итоги
Применим знания о новом API на практике и перепишем с их помощью экран из первого примера. В результате мы получим довольно компактный, легко читаемый и масштабируемый код:
Мы увидели недостатки обмена данными через onActivityResult(), узнали о преимуществах Activity Result API и научились использовать его на практике.
Новый API полностью стабилен, в то время как привычные onRequestPermissionsResult() , onActivityResult() и startActivityForResult() стали Deprecated. Самое время вносить изменения в свои проекты!
Демо-приложение с различными примерами использования Activty Result API, в том числе работу с runtime permissions, можно найти в моем Github репозитории.
Источник
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
Источник