Обзор LiveData Часть Android Jetpack .
LiveData
— это наблюдаемый класс держателя данных. В отличие от обычного наблюдаемого объекта, LiveData учитывает жизненный цикл, то есть учитывает жизненный цикл других компонентов приложения, таких как действия, фрагменты или службы. Эта осведомленность гарантирует, что LiveData обновляет только наблюдателей компонентов приложения, которые находятся в активном состоянии жизненного цикла.
LiveData считает, что наблюдатель, представленный классом Observer
, находится в активном состоянии, если его жизненный цикл находится в состоянии STARTED
или RESUMED
. LiveData уведомляет об обновлениях только активных наблюдателей. Неактивные наблюдатели, зарегистрированные для просмотра объектов LiveData
, не уведомляются об изменениях.
Вы можете зарегистрировать наблюдателя в паре с объектом, реализующим интерфейс LifecycleOwner
. Эта связь позволяет удалить наблюдателя, когда состояние соответствующего объекта Lifecycle
изменится на DESTROYED
. Это особенно полезно для действий и фрагментов, поскольку они могут безопасно наблюдать за объектами LiveData
и не беспокоиться об утечках — подписки на действия и фрагменты мгновенно отписываются, когда их жизненные циклы уничтожаются.
Дополнительные сведения о том, как использовать LiveData, см. в разделе Работа с объектами LiveData .
Преимущества использования LiveData
Использование LiveData дает следующие преимущества:
- Гарантирует, что ваш пользовательский интерфейс соответствует состоянию ваших данных
- LiveData следует шаблону наблюдателя. LiveData уведомляет объекты
Observer
при изменении базовых данных. Вы можете объединить свой код для обновления пользовательского интерфейса в этих объектахObserver
. Таким образом, вам не нужно обновлять пользовательский интерфейс каждый раз, когда изменяются данные приложения, поскольку наблюдатель делает это за вас. - Никаких утечек памяти
- Наблюдатели привязаны к объектам
Lifecycle
и убирают за собой, когда связанный с ними жизненный цикл уничтожается. - Никаких сбоев из-за остановленных действий
- Если жизненный цикл наблюдателя неактивен, например, в случае действия в заднем стеке, он не получает никаких событий LiveData.
- Больше никакой ручной обработки жизненного цикла
- Компоненты пользовательского интерфейса просто наблюдают за соответствующими данными, не прекращая и не возобновляя наблюдение. LiveData автоматически управляет всем этим, поскольку во время наблюдения отслеживает соответствующие изменения статуса жизненного цикла.
- Всегда актуальные данные
- Если жизненный цикл становится неактивным, он получает самые последние данные, когда снова становится активным. Например, действие, находившееся в фоновом режиме, получает последние данные сразу после возвращения на передний план.
- Правильные изменения конфигурации
- Если действие или фрагмент воссоздается из-за изменения конфигурации, например ротации устройства, он немедленно получает последние доступные данные.
- Совместное использование ресурсов
- Вы можете расширить объект
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
, и доступ к нему осуществляется через метод получения, как показано в следующем примере:
Котлин
class NameViewModel : ViewModel() { // Create a LiveData with a String val currentName: MutableLiveData<String> by lazy { MutableLiveData<String>() } // Rest of the ViewModel... }
Ява
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()
действия или фрагмента. - Чтобы гарантировать, что у активности или фрагмента есть данные, которые они могут отобразить, как только станут активными. Как только компонент приложения находится в состоянии
STARTED
, он получает самое последнее значение от объектовLiveData
, которые он наблюдает. Это происходит только в том случае, если установлен наблюдаемый объектLiveData
.
Как правило, LiveData доставляет обновления только при изменении данных и только активным наблюдателям. Исключением из этого поведения является то, что наблюдатели также получают обновление при переходе из неактивного состояния в активное. Более того, если наблюдатель переходит из неактивного состояния в активное во второй раз, он получает обновление только в том случае, если значение изменилось с момента, когда он в последний раз становился активным.
В следующем примере кода показано, как начать наблюдение за объектом LiveData
:
Котлин
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) } }
Ява
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
, как показано в следующем примере, который запускает всех наблюдателей, когда пользователь нажимает кнопку:
Котлин
button.setOnClickListener { val anotherName = "John Doe" model.currentName.setValue(anotherName) }
Ява
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 поддерживает наблюдаемые запросы, которые возвращают объекты 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
может заблокировать основной поток:
Котлин
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 } } }
Ява
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
:
Котлин
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) } }
Ява
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
следующим образом:
Котлин
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. }) } }
Ява
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
как синглтон следующим образом:
Котлин
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 } } }
Ява
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); } }
А использовать его во фрагменте можно следующим образом:
Котлин
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. }) }
Ява
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
, и передает результат вниз по течению.
Котлин
val userLiveData: LiveData<User> = UserLiveData() val userName: LiveData<String> = userLiveData.map { user -> "${user.name} ${user.lastName}" }
Ява
LiveData<User> userLiveData = ...; LiveData<String> userName = Transformations.map(userLiveData, user -> { user.name + " " + user.lastName });
-
Transformations.switchMap()
- Подобно
map()
, применяет функцию к значению, хранящемуся в объектеLiveData
, а также разворачивает и отправляет результат вниз по потоку. Функция, передаваемая вswitchMap()
должна возвращать объектLiveData
, как показано в следующем примере:
Котлин
private fun getUser(id: String): LiveData<User> { ... } val userId: LiveData<String> = ... val user = userId.switchMap { id -> getUser(id) }
Ява
private LiveData<User> getUser(String id) { ...; } LiveData<String> userId = ...; LiveData<User> user = Transformations.switchMap(userId, id -> getUser(id) );
Вы можете использовать методы преобразования для переноса информации на протяжении всего жизненного цикла наблюдателя. Преобразования не вычисляются, если наблюдатель не наблюдает за возвращаемым объектом LiveData
. Поскольку преобразования вычисляются лениво, поведение, связанное с жизненным циклом, передается неявно, не требуя дополнительных явных вызовов или зависимостей.
Если вы считаете, что вам нужен объект Lifecycle
внутри объекта ViewModel
, преобразование, вероятно, будет лучшим решением. Например, предположим, что у вас есть компонент пользовательского интерфейса, который принимает адрес и возвращает почтовый индекс для этого адреса. Вы можете реализовать простую ViewModel
для этого компонента, как показано в следующем примере кода:
Котлин
class MyViewModel(private val repository: PostalCodeRepository) : ViewModel() { private fun getPostalCode(address: String): LiveData<String> { // DON'T DO THIS return repository.getPostCode(address) } }
Ява
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()
вместо использования результата предыдущего вызова.
Вместо этого вы можете реализовать поиск почтового индекса как преобразование ввода адреса, как показано в следующем примере:
Котлин
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 } }
Ява
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 с видом (Java) (Kotlin)
- Изучите расширенные сопрограммы с помощью Kotlin Flow и LiveData
Блоги
- ViewModels и LiveData: шаблоны + антипаттерны
- LiveData за пределами ViewModel — реактивные шаблоны с использованием преобразований и MediatorLiveData
- LiveData с SnackBar, Navigation и другими событиями (случай SingleLiveEvent)
Видео
{% дословно %}Рекомендуется для вас
- Примечание: текст ссылки отображается, когда JavaScript отключен.
- Используйте сопрограммы Kotlin с компонентами, учитывающими жизненный цикл.
- Управление жизненными циклами с помощью компонентов, учитывающих жизненный цикл
- Проверьте реализацию пейджинга