Обзор LiveData Android Jetpack
LiveData – это класс наблюдаемого держателя данных. В отличие от обычного наблюдаемого объекта, LiveData учитывает жизненный цикл, то есть учитывает жизненный цикл других компонентов приложения, таких как действия, фрагменты или службы. Это гарантирует, что LiveData обновляет только наблюдателей компонентов приложения, которые находятся в активном состоянии жизненного цикла.
LiveData считает наблюдателя, который представлен классом Observer, активным, если его жизненный цикл находится в состоянии STARTED или RESUMED. LiveData уведомляет об обновлениях только активных наблюдателей. Неактивные наблюдатели, зарегистрированные для отслеживания объектов LiveData, не получают уведомления об изменениях.
Вы можете зарегистрировать наблюдателя, связанного с объектом, который реализует интерфейс LifecycleOwner. Это позволяет удалить наблюдателя, когда состояние соответствующего объекта Lifecycle меняется на DESTROYED.
Это особенно полезно для объектов activity и фрагментов, поскольку они могут безопасно отслеживать объекты LiveData, не беспокоясь об утечках. Подписка на объекты activity и фрагменты отменяется сразу после завершения их жизненного цикла.
Подробнее о том, как работать с объектами LiveData…
Преимущества использования LiveData
У LiveData есть следующие преимущества:
- Обеспечивает соответствие пользовательского интерфейса состоянию данных
- LiveData follows the observer pattern. LiveData уведомляет объекты
Observerоб изменении базовых данных. Вы можете объединить код, чтобы обновить интерфейс в этих объектахObserver. Таким образом, вам не нужно обновлять пользовательский интерфейс каждый раз, когда данные приложения меняются, потому что наблюдатель делает это за вас. - Нет утечек памяти
- Наблюдатели привязаны к объектам
Lifecycleи удаляются, когда завершается связанный с ними жизненный цикл. - Нет сбоев из-за остановленных действий
- Если жизненный цикл наблюдателя неактивен, например в случае действия в обратном стеке, он не получает никаких событий LiveData.
- Больше не нужно вручную управлять жизненным циклом
- Компоненты пользовательского интерфейса только наблюдают за соответствующими данными и не останавливают и не возобновляют наблюдение. LiveData автоматически управляет всем этим, поскольку знает о соответствующих изменениях статуса жизненного цикла во время наблюдения.
- Всегда актуальные данные
- Если жизненный цикл становится неактивным, он получает последние данные, когда снова становится активным. Например, если действие выполнялось в фоновом режиме, оно получит последние данные сразу после того, как вернется на передний план.
- Правильные изменения конфигурации
- Если объект activity или фрагмент создается заново из-за изменения конфигурации, например поворота устройства, он сразу же получает последние доступные данные.
- Обмен ресурсами
- Вы можете расширить объект
LiveData, используя шаблон Singleton, чтобы обернуть системные службы и сделать их доступными для совместного использования в приложении. ОбъектLiveDataподключается к системной службе один раз, а затем любой наблюдатель, которому нужен ресурс, может просто следить за объектомLiveData. Подробнее о расширении LiveData…
Работа с объектами LiveData
Чтобы работать с объектами LiveData, выполните следующие действия:
- Создайте экземпляр
LiveDataдля хранения данных определенного типа. Обычно это делается в классеViewModel. - Создайте объект
Observer, который определяет методonChanged(), управляющий действиями при изменении данных, хранящихся в объектеLiveData. Обычно объектObserverсоздается в контроллере пользовательского интерфейса, например в действии или фрагменте. Прикрепите объект
Observerк объектуLiveDataс помощью методаobserve(). Методobserve()принимает объектLifecycleOwner. Это подписывает объектObserverна объектLiveData, чтобы он получал уведомления об изменениях. Обычно объектObserverприкрепляется к контроллеру пользовательского интерфейса, например к действию или фрагменту.
Когда вы обновляете значение, хранящееся в объекте LiveData, это активирует все зарегистрированные наблюдатели, если прикрепленный объект LifecycleOwner находится в активном состоянии.
LiveData позволяет наблюдателям контроллера пользовательского интерфейса подписываться на обновления. Когда данные, хранящиеся в объекте LiveData, изменяются, пользовательский интерфейс автоматически обновляется.
Создание объектов LiveData
LiveData – это оболочка, которую можно использовать с любыми данными, включая объекты, реализующие Collections, например List. Объект LiveData обычно хранится в объекте ViewModel и доступен через метод получения, как показано в следующем примере:
Kotlin
class NameViewModel : ViewModel() { // Create a LiveData with a String val currentName: MutableLiveData<String> by lazy { MutableLiveData<String>() } // Rest of the ViewModel... }
Java
public class NameViewModel extends ViewModel { // Create a LiveData with a String private MutableLiveData<String> currentName; public MutableLiveData<String> getCurrentName() { if (currentName == null) { currentName = new MutableLiveData<String>(); } return currentName; } // Rest of the ViewModel... }
Изначально данные в объекте LiveData не заданы.
Подробнее о преимуществах и использовании класса ViewModel рассказывается в руководстве по ViewModel.
Наблюдение за объектами LiveData
В большинстве случаев метод onCreate() компонента приложения – это подходящее место для начала отслеживания объекта LiveData по следующим причинам:
- Чтобы система не выполняла лишние вызовы из метода
onResume()объекта activity или fragment. - Чтобы у объекта activity или фрагмента были данные, которые можно показать сразу же после активации. Как только компонент приложения переходит в состояние
STARTED, он получает последнее значение от объектовLiveData, за которыми наблюдает. Это происходит, только если задан объектLiveData, за которым нужно наблюдать.
Как правило, LiveData доставляет обновления только при изменении данных и только активным наблюдателям. Исключение составляют наблюдатели, которые также получают обновление при переходе из неактивного состояния в активное. Кроме того, если наблюдатель снова перейдет из неактивного состояния в активное, он получит обновление, только если значение изменилось с момента его последней активации.
В следующем образце кода показано, как начать отслеживание объекта LiveData.
Kotlin
class NameActivity : AppCompatActivity() { // Use the 'by viewModels()' Kotlin property delegate // from the activity-ktx artifact private val model: NameViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Other code to setup the activity... // Create the observer which updates the UI. val nameObserver = Observer<String> { newName -> // Update the UI, in this case, a TextView. nameTextView.text = newName } // Observe the LiveData, passing in this activity as the LifecycleOwner and the observer. model.currentName.observe(this, nameObserver) } }
Java
public class NameActivity extends AppCompatActivity { private NameViewModel model; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Other code to setup the activity... // Get the ViewModel. model = new ViewModelProvider(this).get(NameViewModel.class); // Create the observer which updates the UI. final Observer<String> nameObserver = new Observer<String>() { @Override public void onChanged(@Nullable final String newName) { // Update the UI, in this case, a TextView. nameTextView.setText(newName); } }; // Observe the LiveData, passing in this activity as the LifecycleOwner and the observer. model.getCurrentName().observe(this, nameObserver); } }
После вызова метода observe() с параметром nameObserver сразу же вызывается метод onChanged(), который предоставляет последнее значение, сохраненное в mCurrentName.
Если объект LiveData не задал значение в mCurrentName, onChanged() не вызывается.
Обновление объектов LiveData
У LiveData нет общедоступных методов для обновления сохраненных данных. Класс MutableLiveData предоставляет открытые методы setValue(T) и postValue(T), которые необходимо использовать, если вам нужно изменить значение, хранящееся в объекте LiveData. Обычно MutableLiveData используется в ViewModel, а затем ViewModel предоставляет наблюдателям только неизменяемые объекты LiveData.
После настройки отношений наблюдателя вы можете обновить значение объекта LiveData, как показано в следующем примере, который запускает все наблюдатели, когда пользователь нажимает кнопку:
Kotlin
button.setOnClickListener { val anotherName = "John Doe" model.currentName.setValue(anotherName) }
Java
button.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { String anotherName = "John Doe"; model.getCurrentName().setValue(anotherName); } });
Вызов setValue(T) в примере приводит к тому, что наблюдатели вызывают свои методы onChanged() со значением John Doe. В примере показано нажатие кнопки, но setValue() или postValue() можно вызвать для обновления mName по разным причинам, в том числе в ответ на сетевой запрос или завершение загрузки базы данных. Во всех случаях вызов setValue() или postValue() активирует наблюдателей и обновляет пользовательский интерфейс.
Как использовать LiveData с Room
Библиотека персистентности Room поддерживает наблюдаемые запросы, которые возвращают объекты LiveData.
Наблюдаемые запросы записываются как часть объекта доступа к базе данных (DAO).
Room генерирует весь необходимый код для обновления объекта LiveData при обновлении базы данных. Сгенерированный код при необходимости выполняет запрос асинхронно в фоновом потоке. Этот шаблон полезен для синхронизации данных, отображаемых в пользовательском интерфейсе, с данными, хранящимися в базе данных. Подробнее о Room и DAO рассказывается в руководстве по библиотеке персистентности Room.
Использование сопрограмм с LiveData
LiveData поддерживает сопрограммы Kotlin. Подробнее о том, как использовать сопрограммы Kotlin с компонентами архитектуры Android…
LiveData в архитектуре приложения
LiveData учитывает жизненный цикл объектов, таких как действия и фрагменты. Используйте LiveData для связи между этими владельцами жизненного цикла и другими объектами с другим сроком службы, например объектами ViewModel.
Основная задача ViewModel – загружать данные, связанные с интерфейсом, и управлять ими. Поэтому в нем удобно хранить объекты LiveData. Создайте объекты LiveData в ViewModel и используйте их, чтобы передавать состояние на уровень пользовательского интерфейса.
Экземпляры LiveData не должны храниться в действиях и фрагментах, поскольку их роль заключается в отображении данных, а не в хранении состояния. Кроме того, если в действиях и фрагментах не хранятся данные, то писать модульные тесты становится проще.
Может возникнуть соблазн использовать объекты LiveData в классе слоя данных, но LiveData не предназначен для обработки асинхронных потоков данных. Хотя для этого можно использовать преобразования LiveData и MediatorLiveData, у такого подхода есть недостатки: возможность объединения потоков данных очень ограничена, и все объекты LiveData (включая созданные с помощью преобразований) наблюдаются в основном потоке. В примере кода ниже показано, как удержание LiveData в Repository может заблокировать основной поток.
Kotlin
class UserRepository { // DON'T DO THIS! LiveData objects should not live in the repository. fun getUsers(): LiveData<List<User>> { ... } fun getNewPremiumUsers(): LiveData<List<User>> { return getUsers().map { users -> // This is an expensive call being made on the main thread and may // cause noticeable jank in the UI! users .filter { user -> user.isPremium } .filter { user -> val lastSyncedTime = dao.getLastSyncedTime() user.timeCreated > lastSyncedTime } } }
Java
class UserRepository { // DON'T DO THIS! LiveData objects should not live in the repository. LiveData<List<User>> getUsers() { ... } LiveData<List<User>> getNewPremiumUsers() { return Transformations.map(getUsers(), // This is an expensive call being made on the main thread and may cause // noticeable jank in the UI! users -> users.stream() .filter(User::isPremium) .filter(user -> user.getTimeCreated() > dao.getLastSyncedTime()) .collect(Collectors.toList())); } }
Если вам нужно использовать потоки данных на других уровнях приложения, рекомендуем использовать Kotlin Flows, а затем преобразовать их в LiveData в ViewModel с помощью asLiveData().
Подробнее о том, как использовать Kotlin Flow с LiveData, рассказывается в этой практической работе.
Для баз кода, созданных с помощью Java, рекомендуется использовать Executors в сочетании с обратными вызовами или RxJava.
Расширение LiveData
LiveData считает наблюдателя активным, если его жизненный цикл находится в состоянии STARTED или RESUMED. В следующем образце кода показано, как расширить класс LiveData.
Kotlin
class StockLiveData(symbol: String) : LiveData<BigDecimal>() { private val stockManager = StockManager(symbol) private val listener = { price: BigDecimal -> value = price } override fun onActive() { stockManager.requestPriceUpdates(listener) } override fun onInactive() { stockManager.removeUpdates(listener) } }
Java
public class StockLiveData extends LiveData<BigDecimal> { private StockManager stockManager; private SimplePriceListener listener = new SimplePriceListener() { @Override public void onPriceChanged(BigDecimal price) { setValue(price); } }; public StockLiveData(String symbol) { stockManager = new StockManager(symbol); } @Override protected void onActive() { stockManager.requestPriceUpdates(listener); } @Override protected void onInactive() { stockManager.removeUpdates(listener); } }
Реализация прослушивателя цен в этом примере включает следующие важные методы:
- Метод
onActive()вызывается, когда у объектаLiveDataесть активный наблюдатель. Это означает, что вам нужно начать наблюдать за обновлениями цен на акции с помощью этого метода. - Метод
onInactive()вызывается, когда у объектаLiveDataнет активных наблюдателей. Поскольку наблюдателей нет, нет причин оставаться подключенным к сервисуStockManager. - Метод
setValue(T)обновляет значение экземпляраLiveDataи уведомляет об изменении все активные наблюдатели.
Класс StockLiveData можно использовать следующим образом:
Kotlin
public class MyFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val myPriceListener: LiveData<BigDecimal> = ... myPriceListener.observe(viewLifecycleOwner, Observer<BigDecimal> { price: BigDecimal? -> // Update the UI. }) } }
Java
public class MyFragment extends Fragment { @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); LiveData<BigDecimal> myPriceListener = ...; myPriceListener.observe(getViewLifecycleOwner(), price -> { // Update the UI. }); } }
Метод observe() передает LifecycleOwner, связанный с представлением фрагмента, в качестве первого аргумента. Это означает, что наблюдатель привязан к объекту Lifecycle, связанному с владельцем. Таким образом:
- Если объект
Lifecycleнеактивен, наблюдатель не вызывается, даже если значение меняется. - После удаления объекта
Lifecycleнаблюдатель автоматически удаляется.
Тот факт, что объекты LiveData учитывают жизненный цикл, означает, что вы можете использовать их в нескольких действиях, фрагментах и сервисах. Чтобы упростить пример, вы можете реализовать класс LiveData как синглтон, как показано ниже.
Kotlin
class StockLiveData(symbol: String) : LiveData<BigDecimal>() { private val stockManager: StockManager = StockManager(symbol) private val listener = { price: BigDecimal -> value = price } override fun onActive() { stockManager.requestPriceUpdates(listener) } override fun onInactive() { stockManager.removeUpdates(listener) } companion object { private lateinit var sInstance: StockLiveData @MainThread fun get(symbol: String): StockLiveData { sInstance = if (::sInstance.isInitialized) sInstance else StockLiveData(symbol) return sInstance } } }
Java
public class StockLiveData extends LiveData<BigDecimal> { private static StockLiveData sInstance; private StockManager stockManager; private SimplePriceListener listener = new SimplePriceListener() { @Override public void onPriceChanged(BigDecimal price) { setValue(price); } }; @MainThread public static StockLiveData get(String symbol) { if (sInstance == null) { sInstance = new StockLiveData(symbol); } return sInstance; } private StockLiveData(String symbol) { stockManager = new StockManager(symbol); } @Override protected void onActive() { stockManager.requestPriceUpdates(listener); } @Override protected void onInactive() { stockManager.removeUpdates(listener); } }
и использовать его во фрагменте следующим образом:
Kotlin
class MyFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) StockLiveData.get(symbol).observe(viewLifecycleOwner, Observer<BigDecimal> { price: BigDecimal? -> // Update the UI. }) }
Java
public class MyFragment extends Fragment { @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); StockLiveData.get(symbol).observe(getViewLifecycleOwner(), price -> { // Update the UI. }); } }
Несколько фрагментов и действий могут наблюдать за экземпляром MyPriceListener.
LiveData подключается к системному сервису, только если один или несколько из них видимы и активны.
Преобразование LiveData
Возможно, вы захотите изменить значение, хранящееся в объекте LiveData, прежде чем отправлять его наблюдателям, или вам может потребоваться вернуть другой экземпляр LiveData на основе значения другого. Пакет Lifecycle предоставляет класс Transformations, который включает вспомогательные методы, поддерживающие эти сценарии.
Transformations.map()- Применяет функцию к значению, хранящемуся в объекте
LiveData, и передает результат дальше.
Kotlin
val userLiveData: LiveData<User> = UserLiveData() val userName: LiveData<String> = userLiveData.map { user -> "${user.name} ${user.lastName}" }
Java
LiveData<User> userLiveData = ...; LiveData<String> userName = Transformations.map(userLiveData, user -> { user.name + " " + user.lastName });
Transformations.switchMap()- Аналогично
map(), применяет функцию к значению, хранящемуся в объектеLiveData, а затем распаковывает и отправляет результат. Функция, переданная вswitchMap(), должна возвращать объектLiveData, как показано в примере ниже.
Kotlin
private fun getUser(id: String): LiveData<User> { ... } val userId: LiveData<String> = ... val user = userId.switchMap { id -> getUser(id) }
Java
private LiveData<User> getUser(String id) { ...; } LiveData<String> userId = ...; LiveData<User> user = Transformations.switchMap(userId, id -> getUser(id) );
Вы можете использовать методы преобразования, чтобы переносить информацию на протяжении жизненного цикла наблюдателя. Преобразования не вычисляются, если наблюдатель не следит за возвращенным объектом LiveData. Поскольку преобразования вычисляются лениво, поведение, связанное с жизненным циклом, неявно передается вниз без необходимости дополнительных явных вызовов или зависимостей.
Если вы считаете, что вам нужен объект Lifecycle внутри объекта ViewModel, то, скорее всего, вам лучше использовать преобразование. Например, у вас есть компонент пользовательского интерфейса, который принимает адрес и возвращает почтовый индекс для этого адреса. Вы можете реализовать простой ViewModel для этого компонента, как показано в следующем образце кода:
Kotlin
class MyViewModel(private val repository: PostalCodeRepository) : ViewModel() { private fun getPostalCode(address: String): LiveData<String> { // DON'T DO THIS return repository.getPostCode(address) } }
Java
class MyViewModel extends ViewModel { private final PostalCodeRepository repository; public MyViewModel(PostalCodeRepository repository) { this.repository = repository; } private LiveData<String> getPostalCode(String address) { // DON'T DO THIS return repository.getPostCode(address); } }
Затем компонент пользовательского интерфейса должен отменить регистрацию в предыдущем объекте LiveData и зарегистрироваться в новом экземпляре при каждом вызове getPostalCode(). Кроме того, если компонент интерфейса создается заново, он запускает ещё один вызов метода repository.getPostCode() вместо использования результата предыдущего вызова.
Вместо этого вы можете реализовать поиск почтового индекса как преобразование введенного адреса, как показано в следующем примере:
Kotlin
class MyViewModel(private val repository: PostalCodeRepository) : ViewModel() { private val addressInput = MutableLiveData<String>() val postalCode: LiveData<String> = addressInput.switchMap { address -> repository.getPostCode(address) } private fun setInput(address: String) { addressInput.value = address } }
Java
class MyViewModel extends ViewModel { private final PostalCodeRepository repository; private final MutableLiveData<String> addressInput = new MutableLiveData(); public final LiveData<String> postalCode = Transformations.switchMap(addressInput, (address) -> { return repository.getPostCode(address); }); public MyViewModel(PostalCodeRepository repository) { this.repository = repository } private void setInput(String address) { addressInput.setValue(address); } }
В этом случае поле postalCode определяется как преобразование поля addressInput. Пока с полем postalCode связан активный наблюдатель, значение поля пересчитывается и извлекается при каждом изменении addressInput.
Этот механизм позволяет нижним уровням приложения создавать объекты LiveData, которые вычисляются по запросу. Объект ViewModel может легко получать ссылки на объекты LiveData, а затем определять для них правила преобразования.
Как создать преобразование
Существует множество различных преобразований, которые могут быть полезны в вашем приложении, но они не предоставляются по умолчанию. Чтобы реализовать собственное преобразование, можно использовать класс MediatorLiveData, который прослушивает другие объекты LiveData и обрабатывает события, генерируемые ими. MediatorLiveData правильно передает свое состояние исходному объекту LiveData. Подробную информацию об этом шаблоне можно найти в справочной документации по классу Transformations.
Объединение нескольких источников LiveData
MediatorLiveData – это подкласс LiveData, который позволяет объединять несколько источников LiveData. Наблюдатели за объектами MediatorLiveData активируются при изменении любого из исходных объектов LiveData.
Например, если в интерфейсе есть объект LiveData, который можно обновить из локальной базы данных или сети, то в объект MediatorLiveData можно добавить следующие источники:
- Объект
LiveData, связанный с данными, которые хранятся в базе данных. - Объект
LiveData, связанный с данными, полученными из сети.
Чтобы получать обновления из обоих источников, вашему действию нужно только отслеживать объект MediatorLiveData. Подробный пример приведен в разделе Приложение. Отображение статуса сети руководства по архитектуре приложений.
Дополнительные ресурсы
Дополнительную информацию о классе LiveData можно найти в следующих ресурсах:
Образцы
- Sunflower – демонстрационное приложение, в котором показаны рекомендации по работе с компонентами архитектуры.
Практические работы
- Android Room с View (Java) (Kotlin)
- Продвинутые сопрограммы в Kotlin Flow и LiveData
Блоги
- ViewModels и LiveData: шаблоны и антишаблоны
- LiveData beyond the ViewModel — Reactive patterns using Transformations and MediatorLiveData
- LiveData со SnackBar, Navigation и другими событиями (случай SingleLiveEvent)
Видео
Рекомендуем
- Примечание. Текст ссылки показывается, когда JavaScript отключен.
- Как использовать сопрограммы Kotlin с компонентами, учитывающими жизненный цикл
- Handling Lifecycles with Lifecycle-Aware Components
- Как протестировать реализацию разбиения на страницы