Mediator live data android

MediatorLiveData: a simple use case

Mar 7, 2020 · 3 min read

In Android, LiveData is often used in combination with ViewModel for supporting a MVVM architectural pattern. The basic idea is to expose LiveData to the View, so that it can update the UI.

A very common practice, is to have in the ViewModel , a dedicated LiveData that holds and exposes the state (in terms of progress or activity) of the ViewModel : in the simplest of cases this is just a Boolean that tells if the ViewModel is busy doing some work, so that the View can show a loading indicator. In the ViewModel we could have:

Then the View will have something like this:

Now, whenever we are doing some work (for which we want the UI to be notified) we can change the value of the LiveData inside the ViewModel to true, and when we finish the work we set the value back to false.

But what if we need to do more than one work at the same time?

Let’s assume we have only 2 works (w1 and w2). t0 to t3 are different sequential instants in time. Here we have two representations for the sequential and concurrent scenarios:

Sequential works:
t0: start w1 and set _isLoading to true
t1: start w1 and set _isLoading to false
t2: end w2 and set _isLoading to true
t3: end w2 and set _isLoading to false

Concurrent works:
t0: start w1 and set _isLoading to true
t1: start w2 and set _isLoading to true
t2: end w1 and set _isLoading to false
t3: end w2 and set _isLoading to false

As you can see, if the works are sequential then the value of the LiveData is always correct: meaning that it is always representing correctly if the ViewModel is doing some work or not.
For the concurrent scenario, instead, there are some combinations that will result in wrong state of the loader. In particular at the time t2, we are setting the value as false, but actually the w2 is still running.

There are several ways for addressing this problem: we could, for example, instruct the work to check if it is the last one to be running, and only in that case we set the LiveData value back to false. Or we could even count the number of active works.
There is also a very simple solution, that uses MediatorLiveData . Basically every work is gonna have its own LiveData for indicating the state, and then with the MediatorLiveData we combine them in a single LiveData .

The MediatorLiveData simply combines all the LiveData given as input, and every time one of the LiveData ’s value changes, the resulting LiveData gets a new value.
In our LiveDataLogicOR we are basically creating a LiveData holding a Boolean, which is the result of logic OR applied to all LiveData given as input.

And now we only need to connect all the works states, and from the View we just observe isLoading as before.

Now when we observe this LiveData , we will always get the actual state, even in case of concurrency.

Источник

LiveData beyond the ViewModel — Reactive patterns using Transformations and MediatorLiveData

Reactive architecture has been a hot topic in Android for years now. It’s been a constant theme at Android conferences, normally illustrated with RxJava examples (see the Rx section at the bottom). Reactive programming is a paradigm concerned with how data flows and the propagation of change, which can simplify building apps and displaying data that comes from asynchronous operations.

One tool to implement some of the reactive concepts is LiveData. It’s a simple observable that is aware of the lifecycle of the observers. Exposing LiveData from your data sources or a repository is a simple way to make your architecture more reactive but there are some potential pitfalls.

This blog post will help you avoid traps and use some patterns to help you build a more reactive architecture using LiveData.

LiveData’s purpose

In Android, activities, fragments and views can be destroyed at almost any time, so any reference to one of these components can cause a leak or a NullPointerException .

Читайте также:  Wps подключение android что это

LiveData was designed to implement the observer pattern, allowing communication between the View controller (activities, fragments, etc.) and the source of the UI data (usually a ViewModel). With LiveData, this communication is safer: the data will only be received by the View if it’s active, thanks to its lifecycle awareness.

The advantage, in short, is that you don’t need to manually cancel subscriptions between View and ViewModel.

LiveData beyond the ViewModel

The observable paradigm works really well between the View controller and the ViewModel, so you can use it to observe other components of your app and take advantage of lifecycle awareness. For example:

  • Observe changes in SharedPreferences
  • Observe a document or collection in Firestore
  • Observe the current user with an Authentication SDK like FirebaseAuth
  • Observe a query in Room (which supports LiveData out of the box)

The advantage of this paradigm is that because everything is wired together, the UI is updated automatically when the data changes.

The disadvantage is that LiveData does not come with a toolkit for combining streams of data or managing threads, like Rx does.

Using LiveData in every layer of a typical app would look something like this:

In order to pass data between components we need a way to map and combine. MediatorLiveData is used for this in combination with the helpers in the Transformations class:

Note that when your View is destroyed, you don’t need to tear down these subscriptions because the lifecycle of the View is propagated downstream to the subsequent subscriptions.

Patterns

One-to-one static transformation — map

In our example above, the ViewModel is only forwarding the data from the repository into the view, converting it to the UI model. Whenever the repository has new data, the ViewModel will simply have to map it:

This transformation is very simple. However, if the user is subject to change, you need switchMap:

One-to-one dynamic transformation — switchMap

Consider this example: you are observing a user manager that exposes a user and you need to wait for their ID before you can start observing the repository.

You can’t wire this on initialization of the ViewModel because the user ID won’t be immediately available.

You can implement this with a switchMap .

A switchMap uses a MediatorLiveData internally, so it’s important to be familiar with it because you need to use it when you want to combine multiple sources of LiveData:

One-to-many dependency — MediatorLiveData

MediatorLiveData lets you add one or multiple sources of data to a single LiveData observable.

This example, from the docs, updates the result when any of the sources change. Note that the data is not combined for you. MediatorLiveData simply takes care of notifications.

In order to implement the transformation in our sample app, we need to combine two different LiveDatas into one:

A way to use MediatorLiveData to combine data is to add the sources and set the value in a different method:

The actual combination of data is done in the combineLatestData method.

It checks if the values are ready or correct and emits a result ( loading, error or success)

See the bonus section below to learn how to clean this up with Kotlin’s extension functions.

When not to use LiveData

Even if you want to “go reactive” you need to understand the advantages before adding LiveData everywhere. If a component of your app has no connection to the UI, it probably doesn’t need LiveData.

For example, a user manager in your app listens to changes in your auth provider (such as Firebase Auth) and uploads a unique token to your server.

The token uploader can observe the user manager, but with whose Lifecycle? This operation is not related to the View at all. Moreover, if the View is destroyed, the user token might not ever be uploaded.

Another option is to use observeForever() from the token uploader and somehow hook into the user manager’s lifecycle to remove the subscription when done.

However, you don’t need to make everything observable. Let the user manager call the token uploader directly (or whatever makes sense in your architecture).

If part of your app doesn’t affect the UI, you probably don’t need LiveData.

Antipattern: Sharing instances of LiveData

When a class exposes a LiveData to other classes, think carefully if you want to expose the same LiveData instance or different ones.

Читайте также:  Heartbeat synchronization что это android

If this class is a singleton in your app (there’s only one instance of it), you can always return the same LiveData, right? Not necessarily: there might be multiple consumers of this class.

For example, consider this one:

And a second consumer also uses it:

The first consumer will receive an update with data belonging to user “2”.

Even if you think you are only using this class from one consumer, you might end up with bugs using this pattern. For example, when navigating from one instance of an activity to another, the new instance might receive data from the previous one for a moment. Remember that LiveData dispatches the latest value to a new observer. Also, activity transitions were introduced in Lollipop and they bring with them an interesting edge case: two activities in an active state. This means that there could be two instances of the only consumer of the LiveData and one of them will probably show the wrong data.

The solution to this problem is simply to return a new LiveData for each consumer.

Think carefully before sharing a LiveData instance across consumers.

MediatorLiveData smell: adding sources outside initialization

Using the observer pattern is safer than holding references to Views (what you would normally do in a MVP architecture). However, this doesn’t mean you can forget about leaks!

Consider this data source:

It simply returns a new LiveData with a random value after 500ms. There’s nothing wrong with it.

In the ViewModel, we need to expose a randomNumber property that takes the number from the generator. Using a MediatorLiveData for this is not ideal because it requires you to add the source every time you need a new number:

If every time the user clicks on the button we add a source to a MediatorLiveData, the app works as intended. However, we’re leaking all previous LiveDatas which won’t be sending updates any more, so it’s a waste.

You could store a reference to the source and then remove it before adding a new one. (Spoiler: this is what Transformations.switchMap does! See solution below.)

Instead of using MediatorLiveData, let’s try (and fail) to fix this with Transformation.map :

Transformation smell: Transformations outside initialization

Using the previous example this would not work:

There’s an important problem to understand here: Transformations create a new LiveData when called (both map and switchMap ). In this example randomNumber is exposed to the View but it’s reassigned every time the user clicks on the button. It’s very common to miss that an observer will only receive updates to the LiveData assigned to the var in the moment of the subscription.

This subscription happens in onCreate() so if the viewmodel.randomNumber LiveData instance changes afterwards, the observer will never be called again.

Don’t use Livedata in a var. Wire transformations on initialization.

Solution: wire transformations during initialization

Initialize the exposed LiveData as a transformation:

Use an Event in a LiveData to indicate when to request a new number:

See this post on events if you’re not familiar with this pattern.

Bonus section

Tidying up with Kotlin

The MediatorLiveData example above shows some code repetition so we can leverage Kotlin’s extension functions:

The repository looks much cleaner now:

LiveData and RxJava

Finally, let’s address the elephant in the room. LiveData was designed to allow the View observe the ViewModel. Definitely use it for this! Even if you already use Rx, you can communicate both with LiveDataReactiveStreams*.

If you want to use LiveData beyond the presentation layer, you might find that MediatorLiveData does not have a toolkit to combine and operate on streams of data like RxJava offers. However, Rx comes with a steep learning curve. A combination of LiveData transformations (and Kotlin magic) might be enough for your case but if you (and your team) already invested in learning RxJava, you probably don’t need LiveData.

*If you use auto-dispose, using LiveData for this would be redundant.

Источник

Android Architecture Components. Часть 3. LiveData

Компонент LiveData — предназначен для хранения объекта и разрешает подписаться на его изменения. Ключевой особенностью является то, что компонент осведомлен о жизненном цикле и позволяет не беспокоится о том, на каком этапе сейчас находиться подписчик, в случае уничтожения подписчика, компонент отпишет его от себя. Для того, чтобы LiveData учитывала жизненный цикл используется компонент Lifecycle, но также есть возможность использовать без привязки к жизненному циклу.

Сам компонент состоит из классов: LiveData, MutableLiveData, MediatorLiveData, LiveDataReactiveStreams, Transformations и интерфейса: Observer.

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

Читайте также:  Касперский или доктор веб кто лучше для андроид

Для обновления значения мы должны передать его с помощью метода setValue(T), будьте внимательны поскольку этот метод нужно вызывать с main треда, в противном случае мы получим IllegalStateException, если же нам нужно передать значение из другого потока можно использовать postValue(T), этот метод в свою очередь обновит значение в main треде. Интересной особенностью postValue(T) является еще то, что он в случае множественного вызова, не будет создавать очередь вызовов на main тред, а при исполнении кода в main треде возьмет последнее полученное им значение. Также, в классе присутствует два колбека:

onActive() — будет вызван когда количество подписчиков изменит свое значение с 0 на 1.
onInactive() — будет вызван когда количество подписчиков изменит свое значение с 1 на 0.

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

Давайте рассмотрим, как будет выглядеть наш LiveData класс, который будет хранить wife network name и в случае изменения будет его обновлять, для упрощения он реализован как синглтон.

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

Для того чтобы добавить подписчика есть два метода: observe(LifecycleOwner, Observer ) — для добавления подписчика с учетом жизненного цикла и observeForever(Observer ) — без учета. Уведомления об изменении данных приходят с помощью реализации интерфейса Observer, который имеет один метод onChanged(T).

Выглядит это приблизительно так:

Примечание: Этот фрагмент только для примера, не используйте этот код в реальном проекте. Для работы с LiveData лучше использовать ViewModel(про этот компонент в следующей статье) или позаботиться про отписку обсервера вручную.
В случае использования observe(this,this) при повороте экрана мы будем каждый раз отписываться от нашего компонента и заново подписываться. А в случае использование observeForever(this) мы получим memory leak.

Помимо вышеупомянутых методов в api LiveData также входит getValue(), hasActiveObservers(), hasObservers(), removeObserver(Observer observer), removeObservers(LifecycleOwner owner) в дополнительных комментариях не нуждаются.

Класс MutableLiveData, является расширением LiveData, с отличием в том что это не абстрактный класс и методы setValue(T) и postValue(T) выведены в api, то есть публичные.
По факту класс является хелпером для тех случаев когда мы не хотим помещать логику обновления значения в LiveData, а лишь хотим использовать его как Holder.

Класс MediatorLiveData, как понятно из названия это реализация паттерна медиатор, на всякий случай напомню: поведенческий паттерн, определяет объект, инкапсулирующий способ взаимодействия множества объектов, избавляя их от необходимости явно ссылаться друг на друга. Сам же класс расширяет MutableLiveData и добавляет к его API два метода: addSource(LiveData , Observer ) и removeSource(LiveData ). Принцип работы с классом заключается в том что мы не подписываемся на конкретный источник, а на наш MediatorLiveData, а источники добавляем с помощью addSource(..). MediatorLiveData в свою очередь сам управляет подпиской на источники.

Для примера создадим еще один класс LiveData, который будет хранить название нашей мобильной сети:

И перепишем наше приложение так чтоб оно отображало название wifi сети, а если подключения к wifi нет, тогда название мобильной сети, для этого изменим MainActivity:

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

Класс LiveDataReactiveStreams, название ввело меня в заблуждение поначалу, подумал что это расширение LiveData с помощью RX, по факту же, класс являет собой адаптер с двумя static методами: fromPublisher(Publisher publisher), который возвращает объект LiveData и toPublisher(LifecycleOwner lifecycle, LiveData liveData), который возвращает объект Publisher . Для использования этого класса, его нужно импортировать отдельно:
compile «android.arch.lifecycle:reactivestreams:$version»

Класс Transformations, являет собой хелпер для смены типизации LiveData, имеет два static метода:
map(LiveData , Function ) — применяет в main треде реализацию интерфейса Function и возвращает объект LiveData

, где T — это типизация входящей LiveData, а P желаемая типизация исходящей, по факту же каждый раз когда будет происходить изменения в входящей LiveData она будет нотифицировать нашу исходящую, а та в свою очередь будет нотифицировать подписчиков после того как переконвертирует тип с помощью нашей реализации Function. Весь этот механизм работает за счет того что по факту исходящая LiveData, является MediatiorLiveData.

switchMap(LiveData , Function ) — похож к методу map с отличием в том, что вместо смены типа в функции мы возвращаем сформированный объект LiveData.

Базовый пример можно посмотреть в репозитории: git

Источник

Оцените статью