Android activity result callback

AndroidX Activity Result APIs — The new way!

Traditionally, we use startActivityForResult() and onActivityResult() APIs to start another activity and receive a result back that is available on the Activity classes on all API levels.

With JetPack, it’s strongly recommended to get the business done with all-new Activity Result APIs introduced in AndroidX Activity 1.2.0-alpha02 and Fragment 1.3.0-alpha02 roll out.

You now have a nice abstraction which allows you to handle onActivityResult() method in a very clean and reusable structure, You can register a callback for an Activity Result, The Activity Result APIs provide components for registering for a result, launching the result, and handling the result once it is dispatched by the system.

Activity Result APIs —

  • registerForActivityResult() — For registering the result callback
  • ActivityResultContract — A contract specifying that an activity can be called with an input of type I and produce an output of type O Makes calling an activity for result type-safe.
  • ActivityResultCallback — A type-safe callback to be called when an activity result is available. It’s is a single method interface with an onActivityResult() method that takes an object of the output type defined in the ActivityResultContract
  • ActivityResultLauncher — A launcher for a previously-prepared call to start the process of executing an ActivityResultContract.

registerForActivityResult() takes an ActivityResultContract and an ActivityResultCallback and returns an ActivityResultLauncher which you’ll use to launch the other activity.

You can either use different contracts or want separate callbacks for multiple activity result call, you can call registerForActivityResult() multiple times to register multiple ActivityResultLauncher instances.

registerForActivityResult() is safe to call before your fragment or activity is created, allowing it to be used directly when declaring member variables for the returned ActivityResultLauncher instances. But you cannot launch the ActivityResultLauncher until the fragment or activity’s CREATED.

Launching an activity for the result —

Calling launch() starts the process of producing the result. When the user is done with the subsequent activity and returns, the onActivityResult() from the ActivityResultCallback is then executed

You can use the prebuilt ActivityResultContract from ActivityResultContracts. Alongside this, you can create custom contracts as well. You can read here to learn more about creating a custom contract.

Generic StartActivityForResult contract —

StartActivityForResult can be used for the generic purposes. This is an ActivityResultContract that doesn’t do any type conversion, taking raw Intent as an input and ActivityResult as an output. Can be registered with ActivityResultCaller.registerForActivityResult(ActivityResultContract, ActivityResultCallback) to avoid having to manage request codes when calling an activity API for which a type-safe contract is not available.

Ready to Use Contracts —

You can find more here

Dependencies —

Package Summary —

We need to be conservative when developing apps that run in the Android ecosystem which is targeted to a vast and wide variety of OEM manufacturers, When starting an activity for a result, it is possible (and, in cases of memory-intensive operations such as camera usage, almost certain) that your process and your activity will be destroyed due to low memory. For this reason, the Activity Result APIs decouple the result callback from the place in your code where you launch the other activity. As the result callback needs to be available when your process and activity are recreated, the callback must be unconditionally registered every time your activity is created.

Droid Log

The ultimate log sheet for Android. Filled with super random but interesting stuffs on Android.

Читайте также:  Y taxi app android

Источник

ActivityLifecycleCallbacks — слепое пятно в публичном API

С детства я люблю читать инструкции. Я вырос, но меня до сих пор удивляет то, как взрослые люди безалаберно относятся к инструкциям: многие из них считают, что все знают, и при этом пользуются одной-двумя функциями, в то время как их намного больше! Кто из вас пользовался функцией поддержания температуры в микроволновке? А она есть почти в каждой.

Однажды я решил почитать документацию к различным классам Android framework. Пробежался по основным классам: View, Activity, Fragment, Application, — и меня очень заинтересовал метод Application.registerActivityLifecycleCallbacks() и интерфейс ActivityLifecycleCallbacks. Из примеров его использования в интернете не нашлось ничего лучше, чем логирование жизненного цикла Activity. Тогда я начал сам экспериментировать с ним, и теперь мы в Яндекс.Деньгах активно используем его при решении целого спектра задач, связанных с воздействием на объекты Activity снаружи.

Что такое ActivityLifecycleCallbacks?

Посмотрите на этот интерфейс, вот как он выглядел, когда появился в API 14:

Возможно, этому интерфейсу уделяют так мало внимания, потому что он появился только в Android 4.0 ICS. А зря, ведь он позволяет нативно делать очень интересную вещь: воздействовать на все объекты Activity снаружи. Но об этом позже, а сначала внимательнее посмотрим на методы.

Каждый метод отображает аналогичный метод жизненного цикла Activity и вызывается в тот момент, когда метод срабатывает на какой-либо Activity в приложении. То есть если приложение запускается с MainActivity, то первым мы получим вызов ActivityLifecycleCallback.onActivityCreated(MainActivity, null).

Отлично, но как это работает? Тут никакой магии: Activity сами сообщают о том, в каком они состоянии. Вот кусочек кода из Activity.onCreate():

Это выглядит так, как если бы мы сами сделали BaseActivity. Только коллеги из Android сделали это за нас, еще и обязали всех этим пользоваться. И это очень даже хорошо!

В API 29 эти методы работают почти так же, но их Pre- и Post-копии честно вызываются до и после конкретных методов. Вероятно, теперь этим управляет ActivityManager, но это только мои догадки, потому что я не углублялся в исходники достаточно, чтобы это выяснить.

Как заставить ActivityLifecycleCallbacks работать?

Как и все callback, сначала их надо зарегистрировать. Мы регистрируем все ActivityLifecycleCallbacks в Application.onCreate(), таким образом получаем информацию обо всех Activity и возможность ими управлять.

Небольшое отступление: начиная с API 29 ActivityLifecycleCallbacks можно зарегистрировать еще и изнутри Activity. Это будет локальный callback, который работает только для этого Activity.

Вот и все. Но это вы можете найти, просто введя название ActivityLifecycleCallbacks в строку поисковика. Там будет много примеров про логирование жизненного цикла Activity, но разве это интересно? У Activity много публичных методов (около 400), и все это можно использовать для того, чтобы делать много интересных и полезных вещей.

Что с этим можно сделать?

А что вы хотите? Хотите динамически менять тему во всех Activity в приложении? Пожалуйста: метод setTheme() — публичный, а значит, его можно вызывать из ActivityLifecycleCallback:

Повторяйте этот трюк ТОЛЬКО дома
Какие-то Activity из подключенных библиотек могут использовать свои кастомные темы. Поэтому проверьте пакет или любой другой признак, по которому можно определить, что тему этой Activity можно безопасно менять. Например, проверяем пакет так (по-котлиновски =)):

Пример не работает? Возможно, вы забыли зарегистрировать ThemeCallback в Application или Application в AndroidManifest.

Хотите еще интересный пример? Можно показывать диалоги на любой Activity в приложении.

Повторяйте этот трюк ТОЛЬКО дома
Конечно же, не стоит показывать диалог на каждом экране — наши пользователи не будут нас любить за такое. Но иногда может быть полезно показать что-то такое на каких-то конкретных экранах.

А вот еще кейс: что, если нам надо запустить Activity Тут все просто: Activity.startActivity() — и погнали. Но что делать, если нам надо дождаться результата после вызова Activity.startActivityForResult()? У меня есть один рецепт:

Читайте также:  Android sdk platform tools как пользоваться

В примере мы просто закидываем Fragment, который запускает Activity и получает результат, а потом делегирует его обработку нам. Будьте осторожны: тут мы проверяем, что наша Activity является AppCompatActivity, что может привести бесконечному циклу. Используйте другие условия.

Усложним примеры. До этого момента мы использовали только те методы, которые уже есть в Activity. Как насчет того, чтобы добавить свои? Допустим, мы хотим отправлять аналитику об открытии экрана. При этом у наших экранов свои имена. Как решить эту задачу? Очень просто. Создадим интерфейс Screen, который сможет отдавать имя экрана:

Теперь имплементируем его в нужных Activity:

После этого натравим на такие Activity специальные ActivityLifecycleCallback’и:

Видите? Мы просто проверяем интерфейс и, если он реализован, отправляем аналитику.

Повторим для закрепления. Что делать, если надо прокидывать еще и какие-то параметры? Расширим интерфейс:

Но это все еще легко. Все это было только ради того, чтобы подвести вас к по-настоящему интересной теме: нативное внедрение зависимостей. Да, у нас есть Dagger, Koin, Guice, Kodein и прочее. Но на небольших проектах они избыточны. Но у меня есть решение… Угадайте какое?

Допустим, у нас есть некоторый инструмент, вроде такого:

Закроем его интерфейсом, как взрослые программисты:

А теперь немного уличной магии от ActivityLifecycleCallbacks: мы создадим интерфейс для внедрения этой зависимости, реализуем его в нужных Activity, а с помощью ActivityLifecycleCallbacks найдем его и внедрим реализацию CoolToolImpl.

Не забудьте зарегистрировать InjectingLifecycleCallbacks в вашем Application, запускайте — и все работает.

И не забудьте протестировать:

Но на больших проектах такой подход будет плохо масштабироваться, поэтому я не собираюсь отбирать ни у кого DI-фреймворки. Куда лучше объединить усилия и достигнуть синергии. Покажу на примере Dagger2. Если у вас в проекте есть какая-то базовая Activity, которая делает что-то вроде AndroidInjection.inject(this), то пора ее выкинуть. Вместо этого сделаем следующее:

  1. по инструкции внедряем DispatchingAndroidInjector в Application;
  2. создаем ActivityLifecycleCallbacks, который вызывает DispatchingAndroidInjector.maybeInject() на каждой Activity;
  3. регистрируем ActivityLifecycleCallbacks в Application.

И такого же эффекта можно добиться с другими DI-фреймворками. Попробуйте и напишите в комментариях, что получилось.

Источник

Получаем результат правильно (Часть 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 — тип возвращаемого результата.

Читайте также:  Android dashboard что такое

Для типовых задач можно воспользоваться реализациями “из коробки”: 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 репозитории.

Источник

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