Объедините несколько источников LiveData в один

Компонент LiveData является частью Android Jetpack и в настоящее время широко используется для реализации шаблона Observer с автоматическим учетом жизненного цикла соответствующего приложения Android.

Он в основном используется для обновления пользовательского интерфейса с новым состоянием соответствующей ViewModel. Используя этот компонент, мы можем переключаться с поведения вытягивания на поведение выталкивания, когда нам нужно сообщить нашему пользовательскому интерфейсу об изменениях состояния.

Иногда вы хотите получать информацию не только об одном элементе, но и о нескольких.

Особенно когда дело доходит до нескольких асинхронных потоков LiveData, которые требуются одновременно для обновления представления пользовательского интерфейса, это может быстро стать сложным.

Поэтому вы можете столкнуться с поведением, при котором вы нарушаете обычный шаблон MVVM и активно получаете состояние других необходимых параметров из пользовательского интерфейса.

Этого мы хотим избежать. На помощь приходит MediatorLiveData.

ПосредникLiveData

Хотя MediatorLiveData является подклассом LiveData, он сам действует как LiveData. Это позволяет нам объединить несколько источников LiveData в одну LiveData, которую мы затем можем наблюдать.

Например, синтаксис слияния двух источников LiveData выглядит следующим образом:

Мы объявляем два отдельных LiveData источника одного и того же типа. Кроме того, мы объявляем и инициализируем переменную mediatorLiveData типа MediatorLiveData.

Затем в блоке init класса exampleViewModel мы добавляем один источник LiveData традиционным способом объявления Observer и последующего переопределения функции onChange(..).

Во второй функции мы используем лямбда-вариацию. Возвращаемое значение будет новым значением значения mediatorLiveData.

Удаление источников

Но что, если мы просто хотим взять один источник в течение определенного времени?

Взгляните на первую реализацию. Здесь мы удаляем _firstLiveData, как только invocationCount внутри Observer превысит 10.

После этого мы больше не будем получать призывы для этого LiveData.

Что, если нам нужно рассмотреть LiveData источники разных типов? Проблема здесь в том, что этот вариант использования невозможен с исходной реализацией MediatorLiveData. Мы можем объединять только LiveData источников одного типа.

А вот и PairMediatorLiveData в игру.

PairMediatorLiveData

PairMediatorLiveData - это не более чем комбинация LiveData источников, которые автоматически объединяются в один LiveData источник, который представлен Котлиным как тип данных Пара.

Примечание. Конечно, вы можете использовать любой другой тип класса в качестве держателя данных.

В приведенном выше фрагменте кода мы объявляем класс PairMediatorLiveData, который наследуется от обсуждаемого MediatorLiveData.

Мы инициализируем класс, добавляя три LiveData источника из конструктора к родительскому MediatorLiveData.

Внутри onChange(..) функций, которые мы используем здесь только как лямбда-функции, мы объединяем все текущие LiveData значения в одно единственное Pair в качестве возвращаемого значения.

Пример объявления ViewModel

Теперь давайте посмотрим на пример того, как использовать PairMediatorLiveData на практике. В следующем фрагменте кода показана примерная реализация ViewModel:

Мы объявляем два LiveData значения. Каждый из них имеет частную изменяемую и общедоступную неизменяемую версию.

После этого мы объявляем переменную pairMediatorLiveData, которая инициализируется экземпляром нашего класса PairMediatorLiveData, который принимает два MutableLiveData источника в качестве аргументов конструктора.

Если мы сейчас наблюдаем за pairMediatorLiveData, мы получим обновления, как только будет установлено новое значение для _liveDataOne или _liveDataTwo.

Но независимо от того, какое из обоих значений вызывается, мы всегда получаем оба текущих значения в объекте Pair.

TripleMediatorLiveData

То же самое касается варианта использования, когда нам нужны три разных типа.

В этом случае мы можем использовать тип данных Котлина Triple.

В приведенном выше фрагменте кода мы объявляем класс TripleMediatorLiveData, который наследуется от обсуждаемого MediatorLiveData.

Мы инициализируем класс, добавляя три LiveData источника из конструктора к родительскому MediatorLiveData.

Внутри onChange(..) функций, которые мы используем здесь только как лямбда-функции, мы объединяем все текущие LiveData значения в один Triple, который мы возвращаем.

Пример объявления ViewModel

Так же, как мы сделали это для PairMediatorLiveData, давайте рассмотрим пример. Рассмотрим примерный фрагмент кода ниже:

Этот пример не сильно отличается от предыдущего PairMediatorLiveData примера.

Вместо двух теперь мы можем использовать три LiveData источника с разными типами данных.

Как и в случае с PairMediatorLiveData, наблюдая за переменной tripleMediatorLiveData, содержащей наш TripleMediatorLiveData, мы будем получать обновления, как только любой из наших трех LiveData источников получит новое значение.

Примечание. Если вы хотите получать обновления только в том случае, если значение действительно изменяется, вам нужно добавить .distinctUntilChanged() в переменную LiveData.

Преобразование SwitchMap

Что, если мы хотим преобразовать все значения тройки в один единственный LiveData источник?

В этом случае мы можем использовать функцию Преобразования switchMap. Вы можете использовать статическую функцию Transformations.switchMap(..) или использовать функцию lifecycle-livedata-ktx и использовать функцию switchMap непосредственно в LiveData или даже в наших обсуждаемых классах MediatorLiveData.

Если вы хотите использовать расширение, убедитесь, что вы интегрировали следующую зависимость в файл build.gradle уровня приложения.

implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1"

Рассмотрим пример TripleMediatorLiveData. Если бы мы изменили состояние LiveData, код мог бы выглядеть следующим образом:

Функция switchMap будет вызвана, как только обновится любой из наблюдаемых источников внутри TripleMediatorLiveData.

Затем мы просто берем и преобразуем значения Triple и передаем значение в новом LiveData, которое в конечном итоге сохраняется в switchMapLiveData.

Из пользовательского интерфейса мы можем наблюдать за этим LiveData и получать обновления преобразованного значения, как только какой-либо из источников получит обновление.

Примечание. Чтобы предотвратить проблемы с производительностью, вам следует избегать тяжелой работы с внутренней функцией switchMap, потому что преобразование выполняется на MainThread.

Заключение

В этой статье мы глубоко погрузились в класс MediatorLiveData. Мы обсудили некоторые варианты реализации и возможности класса.

Кроме того, мы рассмотрели две адаптации этого класса, которые позволяют нам использовать не только один, но и несколько LiveData типов источников.

В качестве бонуса мы также проверили, как мы можем преобразовать MediatorLiveData в один LiveData источник с помощью Transformations.

Надеюсь, у вас есть какие-то выводы, аплодируйте, если вам понравилась моя статья, и следите за новостями!