Обзор 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, выполните следующие действия:

  1. Создайте экземпляр LiveData для хранения данных определенного типа. Обычно это делается в классе ViewModel.
  2. Создайте объект Observer, который определяет метод onChanged(), управляющий действиями при изменении данных, хранящихся в объекте LiveData. Обычно объект Observer создается в контроллере пользовательского интерфейса, например в действии или фрагменте.
  3. Прикрепите объект 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 – демонстрационное приложение, в котором показаны рекомендации по работе с компонентами архитектуры.

Практические работы

Блоги

Видео