Вы узнаете о lifecycle-aware components, о том, что они собой представляют, как они работают, а также как создать свои собственные компоненты и как их протестировать.
Android Jetpack - это набор библиотек, которые помогают разработчикам улучшать свой код, сокращать шаблонный код и обеспечивать последовательную работу своего приложения в разных версиях Android. Android Jetpack Architecture Components предоставляет инструменты для создания lifecycle-aware components (компонентов, имеющих жизненный цикл андройда), которые реагируют на изменения жизненного цикла в активити или фрагментов.
В этом туториале вы создадите компонент с учетом жизненного цикла в приложении AwarenessFood. Данный компонент позволит улучшить обработку изменений сетевого подключения. Вы также создадите lifecycle owner (владельца жизненного цикла), который будет сообщать активити о состоянии сети.
Приложение показывает пользователю случайный рецепт и имеет два пункта меню: первый, генерирующий новый рандомный рецепт, и второй, показывающий некоторую дополнительную информацию о блюде. Когда устройство находится в режиме оффлайн, на главном экране отображается Снэк-бар с сообщением и возможностью повтора действий.
Из этого туториала вы узнаете о:
- Жизненных циклах в Android
- Lifecycle-aware components
- Lifecycle observers (наблюдатели жизненного цикла)
- Событиях и состояниях
- Lifecycle owners (владельцы жизненного цикла)
- Как протестировать lifecycle-aware components
- LiveData
Начинаем работу
Загрузите материалы. Откройте Android Studio 4.2.1 или новее и импортируйте стартовый проект.
Ниже приводится краткое описание того, что делает каждый пакет:
- analytics: Содержит классы для отслеживания событий приложения.
- data: Содержит классы моделей.
- di: Здесь вы найдете классы для внедрения зависимостей.
- monitor: Содержит один класс для наблюдения за сетевым подключением.
- network: Здесь вы найдете классы для доступа к внешним API.
- repositories: Содержит классы для управления persistence.
- viewmodels: Cодержит классы бизнес-логики.
- views: Содержит настраиваемый View.
Регистрация в API Spoonacular
AwarenessFood использует API-интерфейс Spoonacular для получения рецептов. Вам необходимо зарегистрироваться для этого API, чтобы успешно запускать приложение.
Зайдите на сайт spoonacular и создайте новую учетную запись. После подтверждения учетной записи войдите в систему и перейдите в свой профиль, чтобы найти ваш ключ API. Скопируйте его, откройте RecipesModule.kt внутри пакета di и замените значение в следующей строке:
private const val API_KEY = "YOUR_API_KEY_HERE"
Соберите и запустите проект. Вы увидите экран со случайным рецептом (пример расположен ниже). Ловите бонусные баллы, если вы получите тот же рецепт, что и на картинке. :]
Чтобы получить другой случайный рецепт, нажмите кнопку Reload на панели действий. Если вы попытаетесь получить новый рецепт, и ваше устройство перейдет в автономный режим, вы увидите Снэк-бар с сообщением об ошибке и кнопкой повтора, как показано ниже:
Для того, чтобы перейти к экрану с информацией по блюдам, нажмите на Food Trivia в меню More. Вы реализуете эту функцию позже в этом туториале. Прямо сейчас вы увидите только кнопку для получения информации о блюдах, как показано на изображении ниже:
На этом настройка, необходимая для запуска приложения, завершена. Теперь вы готовы узнать о lifecycle-aware components.
Жизненные циклы (lifecycles) в Android
Важная базовая концепция, которую вам необходимо понять как разработчику Android, - это принцип работы жизненного цикла активити и фрагментов. Жизненный цикл - это серия обратных вызовов, выполняемых в определенном порядке при изменении статуса активити или фрагмента.
Жизненный цикл важен потому, что определенные действия должны выполняться при нахождении активити или фрагмента в определенном состоянии. Например, настройка макета активити должна происходить в его onCreate().
Во фрагменте нужно создать представление и задать его макет через onCreateView(). Другой пример - включение чтения текущего местоположения onStart().
Для выполнения процесса удаления чтение местоположения должно остановиться на onStop(), где вам также нужно отменить регистрацию других компонентов. Важно знать, что не все обратные вызовы выполняются каждый раз. Например, операционная система может исполнять, а может и не исполнять onDestroy().
На следующей диаграмме показан полный жизненный цикл активити:
На этой диаграмме показан жизненный цикл фрагментов:
Реагирование на изменения жизненного цикла
В большинстве приложений есть несколько компонентов, которые должны реагировать на жизненный цикл активити или фрагмента. Вам необходимо инициализировать или зарегистрировать эти компоненты через onStart() и отменить регистрацию или выполнить некоторую очистку в onStop(). В некоторых случаях вам необходимо выполнить другие действия во время другого обратного вызова жизненного цикла.
Следуя этому шаблону, ваш код может стать хаотичным и склонным к ошибкам. Код внутри onStart() и onStop() будет расширяться бесконечно. Между тем, легко забыть отменить регистрацию некоторых из ваших компонентов или вызвать методы компонента в неправильном обратном вызове жизненного цикла, что приведет к ошибкам, утечкам памяти и сбоям.
Некоторые из этих проблем вы можете увидеть в приложении прямо сейчас. Откройте NetworkMonitor.kt в стартовом проекте. Этот класс отвечает за мониторинг состояния сетевого подключения и уведомляет об активности при изменении состояния подключения.
Заметка
Дополнительные сведения о мониторинге сетевого подключения см. в документации Read Network State.
Экземпляр NetworkMonitor должен быть доступен при инициализации активности. Откройте MainActivity.kt, который содержит код для его инициализации onCreate() путем вызова networkMonitor.init().
Затем NetworkMonitor регистрирует сетевые обратные вызовы через onStart() через вызов networkMonitor.registerNetworkCallback(). Наконец, он отменяет регистрацию этих обратных вызовов в onStop(), вызывая networkMonitor.unregisterNetworkCallback().
Инициализация компонента, регистрация обратных вызовов и их отмена снова добавляют в активность много шаблонного кода. Кроме того, вам нужно только добавить onStart() и onStop() для вызова методов NetworkMonitor.
В системе MainActivity есть только один компонент, который должен реагировать на изменения жизненного цикла. Однако ситуация, когда в более крупном и более сложном приложении несколько компонентов должны делать то же самое, может привести к полной неразберихе.
Использование Lifecycle-Aware Components
NetworkMonitor выполняет различные действия, которые зависят от состояния жизненного цикла действия, в котором он находится. Другими словами, NetworkMonitor должен быть lifecycle-aware component и реагировать на изменения в жизненном цикле своего родительского объекта - в данном случае MainActivity.
Jetpack предоставляет классы и интерфейсы, помогающие создавать lifecycle-aware component. Используя их, вы можете улучшить способ выполнения своих действий посредством NetworkMonitor. Эти действия будут выполняться автоматически в соответствии с текущим состоянием жизненного цикла родительского activity.
Lifecycle owner – это компонент, который имеет жизненный цикл, как, например, активность или фрагмент. Lifecycle owner должен знать все компоненты, которые необходимо отслеживать для изменения его жизненного цикла. Использование observer pattern (шаблона наблюдателя) - лучший способ добиться этого.
Создание Lifecycle Observer
Lifecycle Observer - компонент, который имеет возможность наблюдать и реагировать на состояние жизненного цикла его родителей. Jetpack предоставляет интерфейс LifecycleObserver для преобразования класса в наблюдателя жизненного цикла.
Пришло время приступить к обновлению NetworkMonitor. Откройте NetworkMonitor.kt и реализуйте LifecycleObserver следующим образом:
class NetworkMonitor @Inject constructor(private val context: Context) : LifecycleObserver {
// Code to observe changes in the network connection.
}
Вот и все. Теперь NetworkMonitor - это компонент наблюдения за жизненным циклом. Вы выполнили первый шаг по преобразованию его в компонент с учетом жизненного цикла.
События и состояния жизненного цикла
Класс Lifecycle отвечает за знание состояния жизненного цикла родителя и передачи его любому LifecycleObserver, выполняющему мониторинг.
Lifecycle использует два enum для управления и передачи состояния жизненного цикла: Event и State (событие и состояние).
События
Значения Event представляют собой события жизненного цикла, отправляемые операционной системой. Доступные значения для Event:
- ON_CREATE
- ON_START
- ON_RESUME
- ON_PAUSE
- ON_STOP
- ON_DESTROY
- ON_ANY
Каждое значение эквивалентно обратному вызову жизненного цикла с тем же именем. ON_ANY отличается, потому что его запускают все обратные вызовы жизненного цикла.
Реагирование на события жизненного цикла
NetworkMonitor теперь является LifecycleObserver, но пока не реагирует ни на какие изменения жизненного цикла. Для этого вам нужно добавить аннотацию @OnLifecycleEvent к методу, который будет реагировать на конкретное изменение жизненного цикла. Вы используете параметр, чтобы указать, на какое событие жизненного цикла он будет реагировать.
Добавьте @OnLifecycleEvent к различным методам:
@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
fun init() {
// ...
}
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun registerNetworkCallback() {
// ...
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun unregisterNetworkCallback() {
// ...
}
В этом случае NetworkMonitor необходим init(), чтобы он среагировал на событие ON_CREATE. registerNetworkCallback() реагирует на событие ON_START, unregisterNetworkCallback() - на ON_STOP.
Теперь когда NetworkMonitor может реагировать на изменения жизненного цикла, вам нужно выполнить некоторую очистку. Вам больше не нужен следующий код в MainActivity.kt, потому что NetworkMonitor теперь сам выполняет эти действия.
override fun onCreate(savedInstanceState: Bundle?) {
// ...
// 1.Network Monitor initialization.
networkMonitor.init()
// ...
}
// 2. Register network callback.
override fun onStart() {
super.onStart()
networkMonitor.registerNetworkCallback()
}
// 3. Unregister network callback.
override fun onStop() {
super.onStop()
networkMonitor.unregisterNetworkCallback()
}
Удаляем onStart() и onStop() полностью. Также необходимо удалить строку networkMonitor.init() из onCreate().
Внеся эти изменения, вы переместили ответственность за запуск, регистрацию и отмену регистрации компонента с активити на сам компонент.
Состояния
State содержит текущее состояние владельца жизненного цикла. Доступны следующие значения:
- INITIALIZED
- CREATED
- STARTED
- RESUMED
- DESTROYED
Эти состояния особенно полезны, когда действие в lifecycle-aware component должно знать, произошло ли конкретное событие.
Одним из примеров является длительная операция, выполняемая во время события ON_START, и активити или фрагмент уничтожаются до завершения этой операции. В этом случае компонент не должен выполнять никаких действий во время события ON_STOP, поскольку он никогда не инициализировался полностью.
Есть связь между событиями и состояниями жизненного цикла. На следующей диаграмме показана эта взаимосвязь:
Вот когда возникают эти состояния:
- INITIALIZED: когда активности или фрагмент уже созданы, но до выполнения onCreate(). Это начальное состояние жизненного цикла.
- CREATED: После ON_CREATE и после ON_STOP.
- STARTED: После ON_START и после ON_PAUSE.
- RESUMED: Происходит только после ON_RESUME.
- DESTROYED: После ON_DESTROY, но прямо перед вызовом onDestroy(). Когда состояние принимает значение DESTROYED, активность или фрагмент больше не будет отправлять события.
Использование состояний жизненного цикла
Иногда компонентам необходимо выполнить некоторый код, когда его родительский элемент находится, по крайней мере, в определенном состоянии жизненного цикла. Вам нужно убедиться, что NetworkMonitor выполняется registerNetworkCallback()в нужный момент.
Добавьте параметр Lifecycle в конструктор NetworkMonitor, как показано ниже:
private val lifecycle: Lifecycle
При этом NetworkMonitor имеет доступ к своему родительскому состоянию жизненного цикла через переменную lifecycle.
Теперь «упакуйте» весь код registerNetworkCallback() в следующее условие:
fun registerNetworkCallback() {
if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
// ...
}
}
При этом условии NetworkMonitor сетевого подключения будет начат только тогда, когда родительский жизненный цикл находится, по крайней мере, в состоянии STARTED.
Это удобно, потому что жизненный цикл родителя может измениться до завершения выполнения кода в компоненте. Знание состояния родителя может помочь избежать сбоев, утечек памяти и состояний гонки в компоненте.
Наконец, откройте MainActivity.kt и измените создание NetworkMonitor следующим образом:
networkMonitor = NetworkMonitor(this, lifecycle)
NetworkMonitor теперь имеет доступ к жизненному циклу своего родителя и начнет мониторить сетевые изменения только тогда, когда действие находится в правильном состоянии.
Подписка на события жизненного цикла
Чтобы NetworkMonitor действительно начал реагировать на изменения жизненного цикла, его родительский элемент должен знать о его существовании, чтобы он мог отправлять свои события жизненного цикла компоненту.
В MainActivity.kt добавьте следующую строку onCreate() после строки, в которой вы инициализировали networkMonitor:
lifecycle.addObserver(networkMonitor)
Это сообщает владельцу жизненного цикла, что NetworkMonitor будет мониторить события жизненного цикла. Если есть несколько компонентов, которым необходимо отслеживать эти изменения, вы можете без проблем добавить их владельцу жизненного цикла в качестве наблюдателей.
Это большое улучшение. Имея только одну строку, компонент теперь будет получать изменения жизненного цикла от владельца жизненного цикла. Вам больше не нужен шаблонный код в действии. Кроме того, сам компонент содержит весь код инициализации и конфигурации, что делает его автономным и тестируемым.
Снова соберите и запустите приложение. После загрузки рецепта переведите устройство в режим полета. Как и раньше, вы увидите, что появляется панель с ошибкой сетевого подключения.
Однако «за кулисами» вы значительно улучшили реализацию.
Кому принадлежит жизненный цикл?
Все выглядит отлично, но ... кому принадлежит жизненный цикл? Почему в этом примере жизненный цикл принадлежит активити? Есть еще хозяева?
Lifecycle owner является компонентом, который реализует интерфейс LifecycleOwner. Данный интерфейс содержит один метод, нужный владельцу для реализации - Lifecycle.getLifecycle(). По сути, любой класс, реализующий этот интерфейс, может быть владельцем жизненного цикла.
Android предоставляет встроенные компоненты, которые являются владельцами жизненного цикла. Для активностей, например, для ComponentActivity, который является базовым классом AppCompatActivity, будет тот, который реализует LifecycleOwner.
Однако есть и другие классы, которые уже реализуют этот интерфейс. Например, Fragment - это файл LifecycleOwner. Это означает, что вы можете переместить этот код в Fragment, если необходимо, и он будет работать так же, как в MainActivity.
Жизненный цикл фрагментов может быть значительно дольше, чем жизненный цикл view, который они содержат. Если наблюдатель взаимодействует с пользовательским интерфейсом во фрагменте, это может вызвать проблему, поскольку наблюдатель может изменить view до его инициализации или после его уничтожения.
Вот почему вы можете найти viewLifecycleOwner внутри Fragment. Вы можете начать использовать владельца жизненного цикла во время onCreateView() и до onDestroyView(). Как только жизненный цикл представления будет уничтожен, он больше не будет отправлять события.
Обычно используется viewLifecycleOwner для наблюдения LiveData во фрагменте. Откройте FoodTriviaFragment.kt и, в onViewCreated(), прямо перед ним добавьте следующий код viewModel.getRandomFoodTrivia():
viewModel.foodTriviaState.observe(viewLifecycleOwner, Observer {
handleFoodTriviaApiState(it)
})
Вам нужно добавить также import androidx.lifecycle.Observer.
С помощью этого кода FoodTriviaFragment будет реагировать на foodTriviaState, являющимся частью LiveData. Поскольку у наблюдателя есть viewLifecycleOwner своего владельца, он будет получать события только тогда, когда фрагмент находится в активном состоянии.
Пришло время создать и запустить приложение. Нажмите на опцию меню «More» и выберите «Food Trivia». Теперь вы можете принимать участие в забавных и интересных викторинах о еде в своем приложении.
Использование ProcessLifecycleOwner
В некоторых случаях компоненту необходимо реагировать на изменения жизненного цикла приложения. Например, может потребоваться отслеживать, когда приложение переходит в фоновый режим и когда оно выходит из него. Для этих случаев Jetpack предоставляет ProcessLifecycleOwner, который реализует тот же интерфейс LifecycleOwner.
Этот класс сопровождает жизненный цикл всего процесса приложения. Он вызывает ON_CREATE только один раз при первом запуске приложения. При этом он вообще не вызывает ON_DESTROY.
ProcessLifecycleOwner вызывает ON_START и ON_RESUME, когда первое activity в приложении проходит через эти состояния. Наконец, ProcessLifecycleOwner вызывает события ON_PAUSE и ON_STOP после последней видимой активности приложения.
Важно знать, что эти два последних события произойдут с определенной задержкой. Задержка возникает потому, что ProcessLifecycleOwner нужно точно знать причину этих изменений. Эти события необходимо отправлять только в том случае, если они происходят из-за перехода приложения в фоновый режим, а не из-за изменений в конфигурации.
При использовании этого владельца жизненного цикла, компонент LifecycleObserver, как всегда, должен быть реализован. Откройте AppGlobalEvents.kt в пакете analytics. Вы можете заметить, что это файл LifecycleObserver.
Его задача - отслеживать, когда приложение используется или уходит в фоновый режим. Как вы можете заметить в коде, это происходит, когда владелец жизненного цикла отправляет события ON_START и ON_STOP.
Вам нужно зарегистрировать LifecycleObserver по-другому. Откройте RecipesApplication.kt и добавьте следующий код в onCreate():
ProcessLifecycleOwner.get().lifecycle.addObserver(appGlobalEvents)
С помощью этого кода вы получаете экземпляр ProcessLifecycleOwner и добавляете appGlobalEvents в качестве наблюдателя.
Теперь соберите и запустите приложение. После запуска приложения отправьте его в фоновый режим. Откройте Logcat, отфильтруйте по тегу APP_LOGGER, и вы увидите следующие сообщения:
До сих пор вы наблюдали, как фрагменты, активности и компоненты приложения реализуют интерфейс LifecycleOwner. Но это не все. Теперь вы увидите, как создавать собственные классы, которые делают то же самое.
Создание Custom Lifecycle Owner (владельца жизненного цикла)
Помните, что любой класс может реализовать интерфейс LifecycleOwner. Это означает, что вы можете создать собственного владельца жизненного цикла.
Вы создадите Custom Lifecycle Owner, который начинает свой жизненный цикл, когда устройство теряет сетевое подключение, и завершает свой жизненный цикл, когда оно восстанавливает подключение.
Откройте UnavailableConnectionLifecycleOwner.kt в пакете monitor и измените класс, чтобы реализовать LifecycleOwner:
@Singleton
class UnavailableConnectionLifecycleOwner @Inject constructor() : LifecycleOwner {
// ...
}
Затем добавьте LifecycleRegistry в UnavailableConnectionLifecycleOwner следующим образом:
private val lifecycleRegistry: LifecycleRegistry = LifecycleRegistry(this)
override fun getLifecycle() = lifecycleRegistry
LifecycleRegistry - это реализация Lifecycle, которая может обрабатывать несколько наблюдателей и уведомлять их о любых изменениях в жизненном цикле.
Добавление событий
Вы можете использовать handleLifecycleEvent()для уведомлений всякий раз, когда происходят события жизненного цикла. Добавьте в класс следующие методы:
fun onConnectionLost() {
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START)
}
fun onConnectionAvailable() {
lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP)
}
В этом случае всякий раз, когда устройство теряет соединение, lifecycleRegistry отправляет событие ON_START своим наблюдателям. Он отправит ON_STOP, когда соединение станет доступным.
Наконец, добавьте следующий код:
fun addObserver(lifecycleObserver: LifecycleObserver) {
lifecycleRegistry.addObserver(lifecycleObserver)
}
addObserver() зарегистрирует любого наблюдателя, который хочет получать события UnavailableConnectionLifecycleOwner жизненного цикла.
Реагирование на события
Теперь откройте MainActivity.kt и добавьте в него следующий код onCreate():
unavailableConnectionLifecycleOwner.addObserver(networkObserver)
Это заставит networkObserver реагировать на события unavailableConnectionLifecycleOwner. NetworkObserver будет показывать snackbar, когда устройство теряет соединение, и скрывать его, когда соединение восстанавливается.
Наконец, замените handleNetworkState() на:
private fun handleNetworkState(networkState: NetworkState?) {
when (networkState) {
NetworkState.Unavailable -> unavailableConnectionLifecycleOwner.onConnectionLost()
NetworkState.Available -> unavailableConnectionLifecycleOwner.onConnectionAvailable()
}
}
Этот код будет запускать события unavailableConnectionLifecycleOwner жизненного цикла.
Наконец, соберите и запустите приложение. Он работает так же, как и раньше, за исключением того, что приложение теперь использует пользовательский интерфейс lifecycleOwner для обработки сетевых событий.
Как написать тесты для lifecycle-aware components? Вы узнаете об этом в следующем разделе.
Тестирование lifecycle-aware components
Еще одно преимущество наличия всего кода, учитывающего жизненный цикл, в NetworkMonitor заключается в том, что вы можете тестировать код в соответствии с событием жизненного цикла, на которое он должен реагировать.
Эти тесты подтвердят, что правильный метод NetworkMonitor выполняется в соответствии с состоянием владельца жизненного цикла. Создание теста будет аналогично шагам, которые вы выполнили при настройке владельца жизненного цикла.
Настройка тестов
Откройте NetworkMonitorTest.kt. Для начала тест должен имитировать как владельца жизненного цикла, так и монитор сети. Добавьте следующие два макета сразу после открывающих фигурных скобок класса:
private val lifecycleOwner = mockk(relaxed = true)
private val networkMonitor = mockk(relaxed = true)
В приведенном выше коде mockk есть функция, предоставляемая библиотекой MockK, которая помогает создавать фиктивные реализации определенного класса. Атрибут relaxed указывает на то, что вы можете создать фиктивную реализацию без указания на логику работы.
Затем добавьте следующую переменную после только что добавленных переменных:
private lateinit var lifecycle: LifecycleRegistry
Это добавляет LifecycleRegistry к тесту, который будет анализировать наблюдателей и уведомлять их об изменениях в своем жизненном цикле.
Наконец, добавьте следующее setup():
lifecycle = LifecycleRegistry(lifecycleOwner)
lifecycle.addObserver(networkMonitor)
Это действие инициализирует реестр жизненного цикла, передает макет владельца жизненного цикла и добавляет NetworkMonitor, чтобы он мог мониторить изменения жизненного цикла.
Добавление тестов
Чтобы убедиться, что в нем выполняется правильный метод NetworkMonitor, реестр жизненного цикла должен установить правильное состояние жизненного цикла и уведомить своих наблюдателей. Вы будете использовать handleLifecycleEvent() для этого.
Первый тест, который вы напишете, подтверждает, что событие ON_CREATE запускается посредством init().
Реализуйте это, используя приведенный ниже код:
@Test
fun `When dispatching On Create lifecycle event, call init()`() {
// 1. Notify observers and set the lifecycle state.
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
// 2. Verify the execution of the correct method.
verify { networkMonitor.init() }
}
В приведенном выше коде вы:
- Сначала установите и уведомите о состоянии жизненного цикла.
- Затем убедитесь, что init() выполняется в NetworkMonitor.
Наконец, добавьте следующий импорт:
import androidx.lifecycle.Lifecycle
import io.mockk.verify
import org.junit.Test
Выполните тест, и вы увидите, что он выполнился успешно.
Добавьте следующий код, чтобы проверить, что событие START жизненного цикла вызывает registerNetworkCallback():
@Test
fun `When dispatching On Start lifecycle event, call registerNetworkCallback()`() {
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_START)
verify { networkMonitor.registerNetworkCallback() }
}
Это подтверждает, что ON_START активирует registerNetworkCallback().
Выполните тест, и он также пройдет успешено.
Наконец, создайте тест для проверки unregisterNetworkCallback(). В этом случае тест нужно отправить через ON_STOP.
Для этого используйте следующий код:
@Test
fun `When dispatching On Stop lifecycle event, call unregisterNetworkCallback()`() {
// 1. Notify observers and set the lifecycle state.
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_STOP)
// 2. Verify the execution of the correct method.
verify { networkMonitor.unregisterNetworkCallback() }
}
Это подтверждает, что ON_STOP активирует unregisterNetworkCallback().
Запустите тест и ... он завершится с ошибкой: Verification failed: call 1 of 1: NetworkMonitor(#2).unregisterNetworkCallback()) was not called.
Означает ли это, что unregisterNetworkCallback() не выполняется с событием ON_STOP? Тест пытается отправить событие ON_STOP, но в соответствии с «потоком» жизненного цикла, по крайней мере, ON_START должно произойти раньше ON_STOP.
Теперь попробуйте этот подход. Сначала добавьте код для отправки ON_START:
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_START)
lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_STOP)
Запустите тест - все прошло успешно.
Как видите, действительно легко проверить, запускают ли правильные события жизненного цикла правильные методы в NetworkMonitor.
LiveData: компонент с учетом жизненного цикла
До сих пор в этом туториале вы наблюдали, как создавать lifecycle-aware components. Но есть ли в Android уже доступные lifecycle-aware components? Ответ - да, и, возможно, самая известная из них – это LiveData.
Концепция LiveData проста. Это наблюдаемый класс держателя данных, что означает, что он может содержать данные и будет уведомлять своих наблюдателей при изменении этих данных. Однако приятным моментом LiveData является то, что он учитывает жизненный цикл, это означает, что он обновляет своих наблюдателей только тогда, когда жизненный цикл находится в активном состоянии.
Наблюдатель находится в активном состоянии, если его жизненный цикл находится в состоянии STARTED или RESUMED. Если жизненный цикл находится в другом состоянии, LiveData не будет уведомлять своих наблюдателей о каких-либо изменениях.
Создание и назначение переменных LiveData
Откройте MainViewModel.kt из пакета viewmodels. Внутри добавьте следующие переменные:
private val _loadingState = MutableLiveData()
val loadingState: LiveData
get() {
return _loadingState
}
loadingState - это LiveData, он может иметь два значения: Loading и NotLoading. Это значение сообщит представлению, нужно ли отображать или скрывать индикатор выполнения.
Всякий раз, когда переменная LiveData получает новое значение, она уведомляет своих наблюдателей об изменении. Для этого вы используете его свойство value.
Измените getRandomRecipe() следующим образом:
fun getRandomRecipe() {
_loadingState.value = UiLoadingState.Loading
viewModelScope.launch {
recipeRepository.getRandomRecipe().collect { result ->
_loadingState.value = UiLoadingState.NotLoading
_recipeState.value = result
}
}
}
Теперь обновление value будет уведомлять всех наблюдателей об обновлениях _loadingState.
Наблюдение за изменениями LiveData
Откройте MainActivity.kt и добавьте в него следующее onCreate():
viewModel.loadingState.observe(this, Observer { uiLoadingState ->
handleLoadingState(uiLoadingState)
})
С помощью этого кода MainActivity начнет наблюдение за обновлениями viewModel.loadingState. Как видите, первым параметром в observe() является this, текущий экземпляр MainActivity. Посмотрите на сигнатуру observe() и вы увидите, что первый параметр - это LifecycleOwner. Это означает, что наблюдатели LiveData будут реагировать в зависимости от состояния жизненного цикла активности. Клавишей Control или Command щелкните на observe, чтобы проверить сигнатуру метода.
Загляните внутрь observe():
if (owner.getLifecycle().getCurrentState() == DESTROYED) {
// ignore
return;
}
Здесь вы видите, что если LifecycleOwner является DESTROYED и для переменной LiveData установлено новое значение, то наблюдатель ничего не сделает.
Однако, если LifecycleOwner снова станет активным, наблюдатель автоматически получит последнее доступное значение.
Соберите и запустите. Приложение покажет индикатор выполнения при загрузке данных. Он скроет индикатор выполнения после загрузки данных.
Поздравляю! Вы успешно перенесли приложение на использование компонентов с учетом жизненного цикла.
Вы можете скачать конечный проект.