- Урок 3. LiveData. Дополнительные возможности
- Transformations
- Свой LiveData
- MediatorLiveData
- RxJava
- Прочие методы LiveData
- Урок 2. LiveData
- Подключение библиотеки
- Теория
- Получение данных из LiveData
- Нюансы поведения
- Отправка данных в LiveData
- Android LiveData на Kotlin с использованием Retrofit и coroutines
- Android, жизненый цикл Jetpack компонентов
- Начало
- Регистрация в spoonacular API
- Жизненные циклы в Android
- Реагируем на изменения жизненного цикла
- Используем компоненты жизненного цикла (Lifecycle-Aware)
- Создание наблюдателя жизненного цикла (lifecycle observer)
- События и состояния жизненного цикла
- Events
- Реакция на события жизненного цикла
- Состояния (States)
- Используем состояния (State) жизненного цикла
- Подписка на события жизненного цикла
- Кто владеет жизненным циклом?
- Использование ProcessLifecycleOwner
- Создаем собственного владельца жизненного цикла
- Добавляем события
- Реакция на события
- Тестирование компонентов жизненного цикла
- Настройка тестов
- Добавляем тесты
- LiveData: компонент жизненного цикла
- Создание и присваивание переменных LiveData
- Наблюдения за изменениями LiveData
- Что посмотреть?
Урок 3. LiveData. Дополнительные возможности
В прошлом уроке мы разобрались, как отправлять и получать данные в LiveData. В этом уроке рассмотрим несколько дополнительных возможностей. Как преобразовать тип данных с помощью map. Как создать свой LiveData. Как объединить несколько LiveData в один с помощью MediatorLiveData.
Полный список уроков курса:
Transformations
Рассмотрим пример, в котором LiveData будем превращать в LiveData :
В метод map передаем имеющийся LiveData и функцию преобразования. В этой функции мы будем получать String данные из LiveData , и от нас требуется преобразовать их в Integer. В данном случае просто парсим строку в число.
На выходе метода map получим LiveData . Можно сказать, что он подписан на LiveData и все получаемые String значения будет конвертировать в Integer и рассылать уже своим подписчикам.
Рассмотрим более сложный случай. У нас есть LiveData , нам необходимо из него получить LiveData . Конвертация id в User выглядит так:
По id мы получаем LiveData и на него надо будет подписываться, чтобы получить объект User.
В этом случае мы не можем использовать метод map, т.к. мы получим примерно такой результат:
switchMap уберет вложенность LiveData и мы получим LiveData .
Свой LiveData
В некоторых ситуациях удобно создать свою обертку LiveData.
Класс LocationLiveData расширяет LiveData .
Внутри него есть некий locationListener — слушатель, который можно передать в LocationService и получать обновления текущего местоположения. При получении нового Location от LocationService, locationListener будет вызывать метод setValue и тем самым обновлять данные этого LiveData.
LocationService — это просто какой-то сервис, который предоставляет нам текущую локацию. Его реализация в данном примере не важна. Главное — это то, что мы подписываемся (addListener) на сервис, когда нам нужны данные, и отписываемся (removeListener), когда данные больше не нужны.
Обратите внимание, что мы переопределили методы onActive и onInactive. onActive будет вызван, когда у LiveData появится хотя бы один подписчик. А onInactive — когда не останется ни одного подписчика. Соответственно эти методы удобно использовать для подключения/отключения нашего слушателя к LocationService.
Получилась удобная обертка, которая при появлении подписчиков сама будет подписываться к LocationService, получать Location и передавать его своим подписчикам. А когда подписчиков не останется, то LocationLiveData отпишется от LocationService.
Осталось сделать из LocationLiveData синглтон и можно использовать его в разных Activity и фрагментах.
MediatorLiveData
MediatorLiveData дает возможность собирать данные из нескольких LiveData в один. Это удобно если у вас есть несколько источников из которых вы хотите получать данные. Вы объединяете их в один и подписываетесь только на него.
Рассмотрим, как это делается, на простом примере.
У нас есть два LiveData : liveData1 и liveData2. Мы хотим объединить их в один. Для этого нам понадобится MediatorLiveData.
Добавляем LiveData к MediatorLiveData
Первый — это LiveData из которого MediatorLiveData собирается получать данные.
Второй параметр — это колбэк, который будет использован для подписки на LiveData из первого параметра. Обратите внимание, что в колбэке нам надо самим передавать в MediatorLiveData данные, получаемые из LiveData. Это делается методом setValue.
Таким образом mediatorLiveData будет получать данные из двух LiveData и постить их своим получателям.
Подпишемся на mediatorLiveData
Сюда теперь должны приходить данные из liveData1 и liveData2. Будем их просто логировать.
Отправим данные в liveData1 и liveData2:
Все данные, что мы передавали в liveData1 и liveData2 пришли в общий mediatorLiveData.
Немного усложним пример. Допустим, нам надо отписаться от liveData2, когда из него придет значение «finish».
Код подписки mediatorLiveData на liveData1 и liveData2 будет выглядеть так:
В случае с liveData1 ничего не меняется.
А вот при получении данных от liveData2 мы смотрим, что за значение пришло. Если это значение «finish», то методом )» target=»_blank» rel=»noopener noreferrer»>removeSource отписываем mediatorLiveData от liveData2 и не передаем это значение дальше.
Отправим несколько значений
liveData2 отправляет здесь значения «a», «finish», «b» и «c». Через mediatorLiveData должно пройти только «a». А значение из liveData1 должны пройти все.
Запускаем, смотрим лог:
Все верно. При получении «finish» от liveData2, mediatorLiveData отписался от него и последующие его данные мы уже не получали.
RxJava
Мы можем конвертировать LiveData в Rx и наоборот. Для этого есть инструмент LiveDataReactiveStreams.
Чтобы его использовать добавьте в dependencies:
Чтобы получить LiveData из Flowable или Observable, используем метод )» target=»_blank» rel=»noopener noreferrer»>fromPublisher:
LiveData будет подписан на Flowable, пока у него (у LiveData) есть подписчики.
LiveData не сможет обработать или получить onError от Flowable. Если в Flowable возникнет ошибка, то будет крэш.
Неважно в каком потоке работает Flowable, результат в LiveData всегда придет в UI потоке.
Чтобы получить Flowable или Observable из LiveData нужно выполнить два преобразования. Сначала используем метод )» target=»_blank» rel=»noopener noreferrer»>toPublisher, чтобы получить Publisher. Затем полученный Publisher передаем в метод Flowable.fromPublisher:
Прочие методы LiveData
hasActiveObservers() — проверка наличия активных подписчиков
hasObservers() — проверка наличия любых подписчиков
)» target=»_blank» rel=»noopener noreferrer»>observeForever (Observer observer) — позволяет подписаться без учета Lifecycle. Т.е. этот подписчик будет всегда считаться активным.
removeObservers (LifecycleOwner owner) — позволяет отписать всех подписчиков, которые завязаны на Lifecycle от указанного LifecycleOwner.
Присоединяйтесь к нам в Telegram:
— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование
— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня
— новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме
Источник
Урок 2. LiveData
В этом уроке рассмотрим основные возможности LiveData. Как подписаться на его данные. Как помещать в него данные. Как он взаимодействует со своими подписчиками.
Полный список уроков курса:
Подключение библиотеки
В build.gradle файл проекта добавьте репозитарий google()
В build.gradle файле модуля добавьте dependencies:
Если у вас студия ниже 3.0 и старые версии Gradle и Android Plugin, то подключение будет выглядеть так:
Иногда может возникнуть конфликт с support library. Будет выдавать такую ошибку: Error:Program type already present: android.arch.lifecycle.LiveData
В таком случае попробуйте указать последние версии двух этих библиотек.
Теория
LiveData — хранилище данных, работающее по принципу паттерна Observer (наблюдатель). Это хранилище умеет делать две вещи:
1) В него можно поместить какой-либо объект
2) На него можно подписаться и получать объекты, которые в него помещают.
Т.е. с одной стороны кто-то помещает объект в хранилище, а с другой стороны кто-то подписывается и получает этот объект.
В качестве аналогии можно привести, например, каналы в Telegram. Автор пишет пост и отправляет его в канал, а все подписчики получают этот пост.
Если вы знакомы с RxJava, то LiveData напомнит вам BehaviourSubject. Методом onNext вы передаете ему данные, а он передает эти данные своим подписчикам. Плюс, все новые подписчики сразу получают последнее значение.
Казалось бы, ничего особо в таком хранилище нет, но есть один очень важный нюанс. LiveData умеет определять активен подписчик или нет, и отправлять данные будет только активным подписчикам. Предполагается, что подписчиками LiveData будут Activity и фрагменты. А их состояние активности будет определяться с помощью их Lifecycle объекта, который мы рассмотрели в прошлом уроке.
Получение данных из LiveData
Давайте рассмотрим пример.
Пусть у нас есть некий синглтон класс DataController из которого можно получить LiveData .
DataController периодически что-то там внутри себя делает и обновляет данные в LiveData. Как он это делает, мы посмотрим чуть позже. Сначала посмотрим, как Activity может подписаться на LiveData и получать данные, которые помещает в него DataController.
Код в Activity будет выглядеть так:
Получаем LiveData из DataController, и методом )» target=»_blank» rel=»noopener noreferrer»>observe подписываемся. В метод observe нам необходимо передать два параметра:
Первый — это LifecycleOwner. Напомню, что LifecycleOwner — это интерфейс с методом getLifecycle. Activity и фрагменты в Support Library, начиная с версии 26.1.0 реализуют этот интерфейс, поэтому мы передаем this.
LiveData получит из Activity его Lifecycle и по нему будет определять состояние Activity. Активным считается состояние STARTED или RESUMED. Т.е. если Activity видно на экране, то LiveData считает его активным и будет отправлять данные в его колбэк.
Если предыдущие два абзаца состоят из кучи незнакомых для вас слов, то посмотрите Урок 1. Lifecycle. Там мы подробно разобрали объект Lifecycle и его состояния.
Второй параметр — это непосредственно подписчик, т.е. колбэк, в который LiveData будет отправлять данные. В нем только один метод onChanged. В нашем примере туда будет приходить String.
Теперь, когда DataController поместит какой-либо String объект в LiveData, мы сразу получим этот объект в Activity, если Activity находится в состоянии STARTED или RESUMED.
Нюансы поведения
Распишу сразу несколько важных моментов в поведении LifeData.
Если Activity было не активно во время обновления данных в LiveData, то при возврате в активное состояние, его observer получит последнее актуальное значение данных.
В момент подписки, observer получит последнее актуальное значение из LiveData.
Если Activity будет закрыто, т.е. перейдет в статус DESTROYED, то LiveData автоматически отпишет от себя его observer.
Если Activity в состоянии DESTROYED попробует подписаться, то подписка не будет выполнена.
Если Activity уже подписывало свой observer, и попробует сделать это еще раз, то просто ничего не произойдет.
Вы всегда можете получить последнее значение LiveData с помощью его метода getValue.
Как видите, подписывать Activity на LiveData — это удобно. Поворот экрана и полное закрытие Activity — все это корректно и удобно обрабатывается автоматически без каких-либо усилий с нашей стороны.
Отправка данных в LiveData
Мы разобрались, как получать данные из LiveData, и каким образом при этом учитывается состояние Activity. Теперь давайте посмотрим с другой стороны — как передавать данные в LiveData.
В классе DataController переменная LiveData будет выглядеть так:
Наружу мы передаем LiveData, который позволит внешним объектам только получать данные. Но внутри DataController мы используем объект MutableLiveData, который позволяет помещать в него данные.
Чтобы поместить значение в MutableLiveData, используется метод setValue:
Этот метод обновит значение LiveData, и все его активные подписчики получат это обновление.
Метод setValue должен быть вызван из UI потока. Для обновления данных из других потоков используйте метод postValue. Он перенаправит вызов в UI поток. Соответственно, подписчики всегда будут получать значения в основном потоке.
Чуть более подробный пример с LiveData мы рассмотрим в Уроке 4, когда будем изучать ViewModel.
Присоединяйтесь к нам в Telegram:
— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование
— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня
— новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме
Источник
Android LiveData на Kotlin с использованием Retrofit и coroutines
Так же рассмотрен пример применения современного Coroutines в связке с репозитарием на Retrofit
Retrofit coroutines extension
kotlin-coroutines-retrofit
Расширение для Retrofit на Kotlin. Это всего два файла. Я просто добавил их в проект. Вы можете подключить их через Dependency в Gradle. На Github есть примеры использования.
Также подключаем Adapter addCallAdapterFactory(CoroutineCallAdapterFactory()).
ServerAPI и Repository находятся в одном файле
Реализацией REST API на Kotlin. Она не имеет каких либо специфичных изменений
Далее рассмотрим Repository. Это основной сервис получения LiveData. Инициализируем LiveData состоянием загрузки: Resource.loading(null). Далее ожидаем окончание запроса awaitResult() Этот вызов должен быть в блоке Coroutin async(UI)
По окончанию запроса мы можем хэндлить результат. Если все хорошо результат будет сохранен в mutableLiveData.value = Resource.success(result.value) Важный момент — это должена быть ссылка на новый экземпляр, иначе observer LiveData не отработает. see: new Resource<>(SUCCESS, data, null);
Для обработки ошибок и передачи состояния в Fragment используется Wrapper — Resource .
Он хранить три состояния:
StoresViewModel запрашивает данные у репозитория и сохраняет во внутренней переменной stores
Для передачи параметров в ViewModel расширим стандартную ViewModelProviders
Например для передачи в LoginViewModel надо два параметра (Login,Password). Для передачи токена в StoresViewModel используется один (Token)
Использование наблюдателя Observer за изменением данных:
Для хранения Token и использования его во всем приложении я применил библиотеку/расширение от Fabio Collini. Применение хорошо описано в его статье. Ссылка есть на странице в Github или ниже в этой статье.
Источник
Android, жизненый цикл Jetpack компонентов
Руководство по работе с жизненным циклом Android компонентов, рассмотрим базовые понятия, что такое LifecycleObserver, события и состояния жизненного цикла, кастомные LifecycleOwner.
используется: Kotlin 1.4, Android 10.0, Android Studio 4.2.
Android Jetpack — коллекция библиотек для разработчиков, которая улучшает код, уменьшает повторяющийся код и работает единообразно в различных версиях Android. Архитектурные компоненты Android Jetpack дают инструменты для создания компонентов в приложении с учетом жизненного цикла Activity или Fragment.
В этой статье, вы создадите компонент, учитывающий жизненный цикл (lifecycle-aware компонент), в приложении AwarenessFood. Компонент будет обрабатывать изменения сетевого подключения. Вы также создадите lifecycle owner для обновления состояние сети в Activity.
Само приложение показывает случайный рецепт пользователю и имеет две опции: получить новый случайный рецепт, показать детали связанные с едой. При отключении сети, на главном экране приложения появляется SnackBar с сообщением и кнопкой повтора.
В статье вы изучите:
Жизненные циклы в Android компонентах
Компоненты учитывающие жизненный цикл (Lifecycle-aware components)
Наблюдателей жизненного цикла (Lifecycle observers)
События и состояния
Владельцев жизненного цикла (Lifecycle owners)
Как тестировать Lifecycle-aware компоненты
Статья предполагает, что вы знакомы с основами разработки под Android. Если это не так, посмотрите руководство для начинающих в Android.
Начало
Загрузите материал с оригинальной статьи (кнопка Download вверху страницы здесь). Откройте Android Studio версии 4.2.1 или выше и импортируйте проект.
Ниже общая информация, что делает каждый пакет в проекте:
analytics классы для трекинга событий в приложении
data классы модели
di поддержка зависимостей
monitor содержит единственный класс, который смотрит за состоянием подключения к сети
network классы для доступа к внешним API
repositories классы для доступа к постоянному хранилищу
viewmodels бизнес логика
view экраны
Регистрация в spoonacular API
AwarenessFood приложение получает рецепты через spoonacular API. Вам нужно зарегистрироваться там, чтобы приложение работало правильно.
Заходим на сайт spoonacular.com и создаем новый аккаунт. После подтверждения аккаунта, входим в личный кабинет и ищем там API ключ. Копируем его, открываем файл RecipesModule.kt внутри пакета di и заменяем ключ в следующей строке:
Компилируем и запускаем. Должен появиться экран со случайным рецептом, похожий на такой:
Жмем на кнопку Reload, чтобы загрузить другой случайный рецепт. Если отключить интернет на устройстве и попробовать получить новый рецепт, то появится SnackBar с ошибкой и кнопкой повторить, как на изображении ниже:
Чтобы перейти на экран деталей, жмем в меню More пункт Food Trivia. Вы добавите этот функционал позже. Сейчас это просто экран с кнопкой:
На этом настройка проекта закончена. Сейчас вы готовы познакомиться с компонентами, учитывающие жизненный цикл (lifecycle-aware).
Жизненные циклы в Android
Важная базовая вещь для Android разработчика — это понимание, как работает жизненный цикл Activity и Fragment. Жизненный цикл представляет собой серию вызовов в определенном порядке при изменении состояния Activity или Fragment.
Жизненный цикл – необходимая концепция, потому что нужно понимать, что делать в определенных состояниях Activity и Fragment. Например, настройка layout (макета) происходит в Activity.onCreate() . Во Fragment за настройку layout отвечает метод Fragment.onCreateView() . Другой пример — включение чтения геолокации в методе onStart() .
Для освобождения ресурсов, чтение геолокации должно быть отключено в onStop() , здесь же освобождаются и другие ресуры и компоненты. Важный момент: не все lifecycle вызовы обязательно выполняются каждый раз. К примеру, операционная система может вызывать, а может и нет метод onDestory() .
Жизненный цикл Activity:
Для тех, кто хочет узнать побольше о жизненном цикле Activity, взгляните на статью Introduction to Android Activities With Kotlin.
Жизненный цикл Fragment:
Реагируем на изменения жизненного цикла
В большинстве приложений есть несколько компонентов, которым надо реагировать на события жизненного цикла Activity или Fragment. Вам нужно инициализировать или регистрировать их в методе onStart() и освобождать ресурсы в методе onStop() .
При таком подходе, ваш код может стать запутанным и подверженным ошибкам. Код внутри методов onStart() и onStop() будет нарастать бесконечно. При этом, легко забыть отписаться от компонента или вызвать метод компонента в неподходящем событии жизненного цикла, что может привести к багам, утечкам памяти и падениям приложения.
Некоторые из этих проблем есть прямо сейчас в приложении. Откройте файл NetworkMonitor.kt в проекте. Этот класс слушает состояние сети и уведомляет Activity об этом.
Примечание: Подробная информация о мониторинге сетевого соединения находится здесь — Read Network State.
Экземпляр NetworkMonitor должен быть доступен с момента инициализации Activity. Откройте MainActivity.kt и метод onCreate() , где монитор сети инициализируется вызовом networkMonitor.init() .
Затем NetworkMonitor регистрирует колбек в методе onStart() через вызов networkMonitor.registerNetworkCallback() . Наконец, экземпляр Activity отписывается от колбека в методе onStop , вызывая networkMonitor.unregisterNetworkCallback() .
Инициализация компонента, подписка на события через колбек и отписка от событий добавляет большое количество шаблонного кода в Activity. Кроме того, необходимо использовать только onStart и onStop для вызова методов NetworkMonitor .
В MainActivity используется только один компонент, который зависит от жизненного цикла Activity. В более крупных и сложных проектах таких компонентов гораздо больше и это может привести к полному беспорядку.
Используем компоненты жизненного цикла (Lifecycle-Aware)
NetworkMonitor выполняет различные действия, зависящие от состояния жизненного цикла Activity. Другими словами, NetworkMonitor должен быть компонентом учитывающим жизненный цикл (lifecycle-aware компонент) и реагировать на изменения в жизненном цикле своего родителя – в нашем случае это MainActivity .
Android Jetpack предоставляет классы и интерфейсы для создания lifecycle-aware компонентов. Используя их, вы можете улучшить работу NetworkMonitor . Он будет работать автоматически с учетом текущего состояния жизненного цикла родительской Activity.
Владелец жизненного цикла (lifecycle owner) – это компонент имеющий жизненный цикл, такой как Activity или Fragment. Владелец жизненного цикла должен знать все компоненты, которые будут слушать события жизненного цикла. Паттерн «Наблюдатель» считается лучшим подходом для решения такой задачи.
Создание наблюдателя жизненного цикла (lifecycle observer)
Наблюдатель жизненного цикла компонент, который способен слушать и реагировать на состояния жизненного цикла своего родителя. У Jetpack есть специальный интерфейс для этого – LifecycleObserver .
Ну что, настало время улучшить наш NetworkMonitor . Откройте NetworkMonitor.kt и добавьте к классу поддержку интерфейса LifecycleObserver :
Вот и все, теперь это lifecycle наблюдатель. Вы только что сделали первый шаг по превращению NetworkMonitor в lifecycle-aware компонент.
События и состояния жизненного цикла
Класс Lifecycle знает состояние (state) жизненного цикла родителя и передает его любому прослушивающему LifecycleObserver .
Lifecycle использует два перечисления для обмена данными о жизненном цикле: Event и State.
Events
Event представляет события жизненного цикла, которые отправляет операционная система:
Каждое значение это эквивалент колбека жизненного цикла. ON_ANY отличается тем, что отправляется при каждом событии (можно сделать один метод для обработки всех событий).
Реакция на события жизненного цикла
Сейчас NetworkMonitor – это LifecycleObserver, но он пока не реагирует на жизненный цикл. Нужно добавить аннотацию @OnLifecycleEvent к методу, чтобы он начал реагировать на конкретное событие.
Добавьте @OnLifecycleEvent к нужным методам, как указано ниже:
В этом случае, метод init() реагирует на ON_CREATE событие, registerNetworkCallback() на ON_START и на событие ON_STOP вызывается unregisterNetworkCallback() .
Итак, NetworkMonitor теперь получает изменения жизненного цикла, сейчас нужно немного подчистить код. Следующий код больше не нужен в MainActivity.kt, потому что эти действия NetworkMonitor выполняет сам:
Полностью удаляйте onStart() и onStop() . Кроме того надо удалить networkMonitor.init() из метода onCreate() .
Сделав эти изменения в коде, вы переместили из Activity ответственность за инициализацию, регистрацию и освобождение ресурсов в сам компонент.
Состояния (States)
State хранит текущее состояние владельца жизненного цикла. Возможные значения:
Состояния жизненного цикла сигнализируют, что конкретное событие случилось.
В качестве примера, представим, что мы запускаем длительную инициализацию компонента в событии ON_START и Activity (или Fragment) уничтожаются до того, как инициализация закончится. В этом случае компонент не должен выполнять никаких действий по событию ON_STOP , так как он не был инициализирован.
Есть прямая связь между событиями и состояниями жизненного цикла. На диаграмме ниже, показана это взаимосвязь:
Эти состояния возникают при:
INITIALIZED: Когда Activity или Fragment уже созданы, но onCreate() еще не вызван. Это первоначальное состояние жизненного цикла.
CREATED: После ON_CREATE и после ON_STOP .
STARTED: После ON_START and После ON_PAUSE .
RESUMED: только после ON_RESUME .
DESTROYED: после ON_DESTROY , но прямо перед вызовом onDestroy() . Как только состояние становится DESTROYED , Activity или Fragment больше не будут посылать события.
Используем состояния (State) жизненного цикла
Иногда компоненты должны выполнять код, если их родитель находится, по крайней мере, в определенном состоянии. Нужно быть уверенным, что NetworkMonitor выполняет registerNetworkCallback() в нужный момент жизненного цикла.
Добавьте Lifecycle аргумент в конструктор NetworkMonitor :
С ним у NetworkMonitor будет доступ к состоянию жизненного цикла родителя.
После этого добавьте в код registerNetworkCallback() условие:
С этим условием NetworkMonitor будет запускать мониторинг состояния сети только, если состояние жизненного цикла не менее, чем STARTED . Это достаточно удобно, потому что жизненный цикл может измениться до завершения кода в компоненте. Зная состояние родителя можно избежать крешей, утечек памяти и гонок состояния в компоненте.
Итак, откройте MainActivity.kt и сделайте изменения для NetworkMonitor :
У NetworkMonitor сейчас есть доступ к родительскому жизненному циклу и прослушивание сети запускается только когда Activity находится в нужном состоянии.
Подписка на события жизненного цикла
Чтобы NetworkMonitor действительно начал реагировать на изменения, его родитель должен зарегистрировать NetworkMonitor как слушателя событий. В MainActivity.kt добавьте следующую линию в методе onCreate() после инициализации компонента:
Теперь владелец жизненного цикла будет уведомлять NetworkMonitor об изменениях. Таким же образом можно добавить другие компоненты на прослушивание событий жизненного цикла.
Это отличное улучшение. С помощью одной строчки кода компонент будет получать события жизненного цикла от родителя. Больше не надо писать шаблонный код в Activity или Fragment. Кроме того, компонент содержит весь код инициализации и конфигурации, что делает его самодостаточном и тестируемым.
Соберите и запустите приложение снова. После загрузки рецептов включите «Самолетный режим». Вы увидите ошибку сети в SnackBar, так же как и раньше:
Однако внутреннняя реализация была значительно улучшена.
Кто владеет жизненным циклом?
Все выглядит хорошо, но. кто владеет жизненным циклом? Почему Activity сама владеет жизненным циклом в этом примере? Существуют ли другие владельцы?
Владелец жизненного цикла (lifecycle owner) – компонент реализующий интерфейс LifecycleOwner . В нем есть единственный метод, который необходимо реализовать: Lifecycle.getLifecycle() . Выходит, что любой класс поддерживающий этот интерфейс может быть владельцем жизненного цикла.
Android имеет встроенные компоненты с поддержкой жизненного цикла. Для Activity это ComponentActivity (является базовым классом для AppCompatActivity и реализует интерфейс LifecycleOwner ).
Однако, есть и другие классы с поддержкой LifecycleOwner . Например, Fragment это LifecycleOwner . Это значит, что можно переместить код в любой Fragment , если это надо и это будет работать точно так же, как и в MainActivity .
Жизненный цикл Fragment может быть значительно длиннее, чем цикл визуальных компонентов (UI), которых он содержит. Если наблюдатель взаимодействует с UI во Fragment, это может привести к проблемам, поскольку наблюдатель может изменить UI до инициализации или после уничтожения.
Это причина почему есть viewLifecycleOwner в Fragment . Вы можете использовать viewLifecycleOwner после вызова onCreateView() и перед onDestroyView() . Как только жизненный цикл будет уничтожен, он больше не будет отправлять события.
Распространенный случай использования viewLifecycleOwner это работа с LiveData в Fragment. Откройте FoodTriviaFragment.kt и добавьте в методе onViewCreated() , до viewModel.getRandomFoodTrivia() , следующий код:
Также надо будет добавить импорт класса import androidx.lifecycle.Observer .
Используя этот код, FoodTriviaFragment будет реагировать на события от foodTriviaState (является LiveData ). Так как Fragment является владельцем жизненного цикла ( viewLifecycleOwner ), наблюдатель будет получать обновления данных только, когда Fragment находится в активном состоянии.
Время сделать сборку и запустить приложение. Нажмите More в меню и выберите Food Trivia. Теперь можно получить несколько забавных и интересных Food Trivia в вашем приложении.
Использование ProcessLifecycleOwner
В некоторых случаях компоненту надо реагировать на изменения жизненного цикла самого приложения. К примеру, отследить когда приложение уходит в фоновый режим и возвращается на передний план. Для таких ситуаций в Jetpack есть ProcessLifecycleOwner, который естественно реализует интерфейс LifecycleOwner .
Этот класс представляет жизненный цикл всего процесса приложения. Событие ON_CREATE посылается только один раз, когда приложение стартует. При этом событие ON_DESTROY не будет посылаться вообще.
ProcessLifecycleOwner отправляет события ON_START и ON_RESUME , когда первая Activity проходит через эти состояния. Наконец, ProcessLifecycleOwner отправляет события ON_PAUSE и ON_STOP после того, последняя видимая Activity приложения проходит через соответствующие состояния.
Важно знать, что эти два последних события произойдут после определенной задержки. ProcessLifecycleOwner должен быть уверен в причине этих изменений. Он отправляет события ON_PAUSE и ON_STOP только если приложение перешло в фоновый режим, а не из-за изменения конфигурации.
При использовании такого владельца жизненного цикла, компонент должен также поддерживать интерфейс LifecycleObserver . Откройте AppGlobalEvents.kt в пакете analytics. Видно, что это LifecycleObserver .
Класс отслеживает, когда приложение выходит на передний план или переходит в фоновый режим. Это происходит когда владелец жизненного цикла посылает события ON_START и ON_STOP .
Регистрация такого LifecycleObserver происходит немного по-другому. Откройте RecipesApplication.kt и добавьте следующий код в метод onCreate() :
Здесь мы получаем экземпляр ProcessLifecycleOwner и добавляем appGlobalEvents как слушателя событий.
Соберите и запустите приложение. После старта приложения, сверните его в фон. Если открыть LogCat и отфильтровать вывод по тегу APP_LOGGER, то вы должны увидеть сообщения:
Итак, вы увидели как Fragment, Activity и Application компоненты реализуют интерфейс LifecycleOwner . Но это еще не все. Сейчас вы узнаете, как создавать собственные классы, которые делают то же самое.
Создаем собственного владельца жизненного цикла
Как вы помните, любой класс может поддерживать интерфейс LifecycleOwner . Это значит, что можно создать кастомного владельца жизненного цикла.
Давайте создадим такого владельца, жизненный цикл которого начинается, когда смартфон теряет сетевое соединение и заканчивается при восстановлении соединения.
Откройте UnavailableConnectionLifecycleOwner.kt в пакете monitor и сделайте изменения, чтобы класс поддерживал интерфейс LifecycleOwner :
После этого, добавьте LifecycleRegistry в UnavailableConnectionLifecycleOwner:
Класс LifecycleRegistry это реализация Lifecycle , который может работать с несколькими наблюдателями и уведомлять их о любых изменениях в жизненном цикле.
Добавляем события
Для уведомления о событиях жизненного цикла можно использовать метод handleLifecycleEvent() . Давайте добавим пару методов в наш класс:
В случае, когда смартфон потеряет сетевое соединение, lifecycleRegistry пошлет событие ON_START всем наблюдателям. Когда соединение снова появится, lifecycleRegistry отправит ON_STOP .
Наконец, добавьте следующий код:
addObserver() регистрирует слушателя жизненного цикла UnavailableConnectionLifecycleOwner .
Реакция на события
Теперь откройте MainActivity.kt и добавьте такую строчку кода:
После этого networkObserver будет реагировать на события unavailableConnectionLifecycleOwner . NetworkObserver покажет SnackBar, когда устройство потеряет сеть и скроет SnackBar при восстановлении сети.
И наконец, замените handleNetworkState() :
Этот код запускает события unavailableConnectionLifecycleOwner .
Время собрать и запустить ваше приложение. Все работает так же как и раньше, кроме мониторинга сети, где мы используем сейчас кастомный lifecycleOwner для обработки сетевого состояния.
В следующей секции, вы узнаете как тестировать компоненты жизненного цикла.
Тестирование компонентов жизненного цикла
Использование компонента жизненного цикла в нашем NetworkMonitor дает еще одно преимущество — мы можем тестировать код со всеми связанными событиями жизненного цикла.
Эти тесты будут проверять, что вызывается нужный метод в NetworkMonitor в соответствии с состоянием владельца жизненного цикла. Чтобы создать тест, нужно пройти те же шаги, что и при создании кастомного владельца жизненного цикла.
Настройка тестов
Откройте NetworkMonitorTest.kt. Для запуска теста необходимо сделать заглушки (mock) для владельца жизненного цикла и NetworkMonitor . Добавьте две заглушки в тестовый класс:
Здесь используется функционал библиотеки MockK<:target="_blank">, она позволяет имитировать реализацию класса. Аргумент relaxed говорит, что заглушки могут работать без указания их поведения.
После этого создайте переменную:
Этот код добавляет объект LifecycleRegistry в тест, для управления наблюдателями и рассылки им событий жизненного цикла.
И наконец, добавьте следующую строчку кода в метод setup() :
Здесь инициализируется реестр LifecycleRegistry , в него передается заглушка владельца жизненного цикла и добавляется наблюдатель NetworkMonitor , чтобы слушать события.
Добавляем тесты
Чтобы убедиться, что нужный метод вызывается в NetworkMonitor , реестр жизненного цикла должен установить правильное состояние и уведомить своих слушателей. Для этого будет использоваться метод handleLifecycleEvent() .
Первый тест будет проверять, что при событии ON_CREATE вызывается метод init() .
Давайте напишем тест:
Сначала устанавливаете состояние ON_CREATE
После этого, проверяете, что был вызван метод init() в NetworkMonitor .
Не забудьте импортировать зависимости:
Запускайте тест, он проходит успешно.
Теперь давайте проверим событие ON_START , оно должно вызывать метод registerNetworkCallback() :
Этот код проверяет ON_START событие.
Тест должен пройти успешно.
В конце концов, создадим тест для проверки события ON_STOP и вызова метода unregisterNetworkCallback() :
Этот код проверяет ON_STOP событие.
Запустите тест и . он падает с ошибкой Verification failed: call 1 of 1: NetworkMonitor(#2).unregisterNetworkCallback()) was not called. Это значит, что unregisterNetworkCallback() не выполнился при событии ON_STOP ? Тестовый код посылает событие ON_STOP , но перед ним обязательно должно быть событие ON_START .
Добавляем событие ON_START в тест:
Теперь тест проходит успешно.
Таким образом, мы проверили все события жизненного цикла и они вызывают нужные методы в NetworkMonitor .
LiveData: компонент жизненного цикла
К этому моменту вы узнали, как создавать собственный компонент жизненного цикла. Но существуют ли такие же готовые компоненты в Android? Конечно, возможно самый известный из них это LiveData.
Принцип LiveData достаточно прост. Это хранилище данных за которыми можно наблюдать, то есть оно может содержать данные и уведомлять слушателей об изменениях этих данных. Однако LiveData это еще и компонент жизненного цикла, то есть он уведомляет своих наблюдателей только тогда, когда жизненный цикл находится в активном состоянии.
Наблюдатель в активном состоянии если его жизненный цикл в состоянии STARTED или RESUMED . Если жизненный цикл в другом состоянии, LiveData не будет уведомлять слушателей об изменениях.
Создание и присваивание переменных LiveData
Откройте MainViewModel.kt из пакета viewmodels. Добавьте там следующие переменные:
_loadingState это LiveData с двумя возможными значениями: Loading и NotLoading . Эти значения будут говорить UI показывать или скрывать загрузку ( ProgressBar ).
Каждый раз, когда LiveData переменная получает новое значение, она уведомляет своих слушателей об изменении. Для установки нового значения используйте поле value.
Давайте изменим метод getRandomRecipe() :
Теперь обновление value будет отправлено всем слушателям _loadingState .
Наблюдения за изменениями LiveData
Откройте MainActivity.kt и добавьте код в метод onCreate() :
Здесь, MainActivity начнет наблюдать за обновлениями от viewModel.loadingState . Как вы видите, первый аргумент observe() это this , то есть текущий экземпляр MainActivity . Взгляните на сигнатуру метода observe() , там видно, что первый аргумент имеет тип LifecycleOwner . Это значит, что наблюдатели LiveData будут реагировать на изменения в зависимости от состояния жизненного цикла Activity. Для того чтобы открыть сигнатуру метода, нажмите Control— или Command-click.
В методе observe() есть такой код:
Видно, если LifecycleOwner в состоянии DESTROYED и новое значение было установлено для переменной, то наблюдатель не получит обновление.
Однако, если LifecycleOwner становится активным опять, то наблюдатели получат последнее значение автоматически.
Соберите и запустите проект. Приложение будет показывать прогресс бар во время загрузки приложения, после окончания прогресс бар скрывается.
Поздравляю! Вы отрефакторили проект с использованием компонентов жизненного цикла.
Что посмотреть?
Официальная документация по архитектурным компонентам: Architecture Components: Official Documentation.
Дополнительные материалы по тестированию Jetpack компонентов: Testing Android Architecture Components.
Источник