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

  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 , и доступ к нему осуществляется через метод получения, как показано в следующем примере:

Котлин

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 , обратитесь к следующим ресурсам.

Образцы

Кодлабы

Блоги

Видео

{% дословно %} {% дословно %} {% дословно %} {% дословно %}