How can we use LiveData to implement Navigation in our Android apps? Let’s explore some options.

There are some issues we’d need to address and this article aims to help us with that.

What is the problem?The problem revolves around the LiveData component.

Whether we use the MVVM or MVI pattern, or some variant of them, our UI is reactive and it observes emitted values from one or more LiveData properties of a ViewModel.

Any change in these properties will cause the UI, or some part of it, to be updated.

Right after the UI re-subscribes to a LiveData property, after a rotation or another configuration change, the last value of the property is re-emitted.

This makes sure that the data on the screen is shown again after the UI-component has been recreated.

All good, so far!However, if the LiveData property represents a navigation event, we do not want this event to be emitted again after a configuration change.

We do not want to navigate to the same screen again after the user has rotated the phone and then hits the Back button.

Events are one-shot values, they should not be remembered.

Despite this problem, we don’t want to not use LiveData: It offers the functionality to automatically cancel the observation of emitted data when a UI-component goes away and to only emit data when a UI-component is active, in the foreground.

The question now is; How can we work around this problem and handle navigation events successfully?Let’s first examine the LiveData and the MutableLiveData a bit closer, let’s see how they work before we come up with solutions.

LiveData and MutableLiveDataThe LiveData class defines the following public interface for UI-components (alas, in actuality, it is not an interface, it’s an abstract class [1])interface LiveData<T> { // Subscribes the observer to this LiveData instance.

fun <T> observe(owner: LifecycleOwner, observer: Observer<in T>)}This allows a UI-component, such as an Activity or a Fragment, to observe changes in the LiveData.

The UI-component is represented by owner and the observer will be notified as long as the UI-component is active, in the foreground, i.

e.

as long as the owner is at least in the STARTED state [2].

We ignore the getValue() and other observeXXX methods here.

The MVVM or MVI patterns are reactive and the UI will never ask for a value , the UI will observe it instead.

For our purposes, observing is always done by providing a LifecycleOwner; we won’t be managing subscriptions ourselves.

The MutableLiveData defines the following public interface for ViewModels (again, in actuality, it is not an interface, it’s a class)interface MutableLiveData<T> : LiveData<T> { var value: T}This allows the ViewModel to change the value of the underlying LiveData by assigning a new value to value.

We ignore the postValue method here and assume code in a ViewModel is always executed on the main UI thread.

Time for a picture!LiveData State TransitionsState Transition Diagram for a LiveData objectThe states are numbered with the roman numerals i through vi and these are the state-transition events:value = Vwhen MutableLiveData.

value is assigned a new valueobservewhen LiveData.

observe is called with a LifecycleOwnerdestroy-UIwhen UI-component moves into the DESTROYED state(the subscriptions of its observers will be canceled)start-UIwhen UI-component moves into the STARTED statestop-UIwhen UI-component moves back into the CREATED stateThese are the in/out-actions in the diagram:in/onChanged(V)Notifies observers of a new emitted value VFrom the state transition diagram we can see that an observer will get notified whenthe LiveData changes its current value, and its owner is at least in the STARTED state (state transitions ii → vi or vi → vi)LiveData.

observe is called, the LiveData has a current value and the observer’s owner is at least in the STARTED state (state transition iv → v → vi)the LiveData has a current value, and the state of the observer’s owner changes to STARTED (state transition v → vi)and we can see that the (Mutable)LiveData will always remember the last value it was assigned, whether there are observers or not and whether the UI-component is active or not.

In effect, it behaves somewhat like an Rx’ BehaviorSubject/BehaviorProcessor or like a ConflatedBroadcastChannel because it remembers the last value — and only the last value — that was sent to it.

Problem ScenarioBelow is an outline of a scenario where the behavior of LiveData may cause problems for managing navigation or other types of one-shot events:Activity is launched.

Activity is created and inflated.

New ViewModel is created with its LiveData properties.

(initial state i)Activity observes a navigation property of its ViewModel.

(state transition i → iii)Activity becomes active.

(state transition iii → ii)User initiates an action that causes the navigation property to emit a value.

(state transition ii → vi)Activity reacts to the observed navigation change by launching another Activity.

The current Activity will be stopped.

(state transition vi → v)User lingers on the new Activity, rotates the phone and hits the back button.

The previous Activity reacts to the rotation and is destroyed.

(state transition v → iv)Activity is recreated and re-inflated because of the rotation.

Its old ViewModel is reused.

Newly recreated Activity observes the navigation property of its old ViewModel.

(state transition iv → v)Activity becomes active and the navigation property emits the same value again.

(state transition v → vi)Go to point (7.

)…The solutions we need to find must either prevent the 2nd emission by the navigation property or they must ignore the emission.

Solution 1SituationOur app uses an MVI or MVVM pattern and the ViewModel has only one LiveData property emitting a value that describes the entire state of the UI-component, including navigation events.

sealed class MyUiState(.

) { .

}data class MyUiLoadingState(val .

) : MyUiState(.

)data class MyUiUserListState(val .

) : MyUiState(.

).

data class MyNavToScreenState(val .

) : MyUiState(.

)Plan of AttackWhen our ViewModel emits the navigation state, it should restore the current UI-state as soon as possible.

The code snippet below may not work, because a MutableLiveData is like a conflated broadcast channel.

It only remembers the last value.

If no observers are effectively listening while the code below is run, only the last value will be ever handled.

Any previous values will be ignored.

This can happen if no observers have subscribed yet or when the UI-component is in the background.

The bold and italic line of code in the snippet below may have no effect, since the last value will be currentState again.

The navigation-event may never happen:class MyViewModel(.

) : BaseViewModel() { val uiState: LiveData<MyUiState> = MutableLiveData() .

.

val currentState = uiState.

value uiState.

value = MyNavToScreenState(.

) uiState.

value = currentState .

.

}Instead, we should make sure that the UI-state that represents a navigation-event, contains the current UI-state as well so that we can restore it later.

interface MyUiEvent { val stateToRestore: MyUiState }.

data class MyNavToScreenState( override val stateToRestore : MyUiState, val .

) : MyUiState(.

), MyUiEventAs soon as the UI-component observes and handles the navigation-event state, it must call back into the ViewModel to request a restoration of the previous UI-state.

class MyViewModel(.

) : BaseViewModel() { val uiState: LiveData<MyUiState> = MutableLiveData() .

.

val currentState = uiState.

value uiState.

value = MyNavToScreenState(currentState, .

) .

.

fun restoreStateFrom(eventState: MyUiEvent) { uiState.

value = eventState.

stateToRestore } .

}class MyActivity : Activity() { .

viewModel.

uiState.

observe(this, Observer { if (it is MyUiEvent) { viewModel.

restoreStateFrom(it) // for MVVM // viewModel.

intentAction(Flowable.

just(it)) for MVI } when (it) { .

is MyNavToScreenState -> { .

launch the other activity .

} .

} }) .

}When the phone is rotated, the uiState property will never emit the MyNavToScreenState again, because the call to viewModel.

restoreStateFrom replaced it with UI-state that existed just before the navigation-event.

This UI-state will be re-emitted instead.

Pros and ConsProsNo need to modify or create our own variations of LiveData classesFits in well with MVI patternConsWhen using an MVVM pattern, and to a lesser extent an MVI pattern, an additional method needs to be created and the UI-component must not forget to call this method at the appropriate timeThe UI-component takes on a responsibility, that should belong to the ViewModel, by requesting to restore the UI-stateSolution 2SituationOur app uses an MVVM pattern and the ViewModel does not expose just one property that represents the entire UI-state.

Instead, it may exposes two or more LiveData properties.

Some are solely used for emitting simple navigation-events that contain no data (e.

g.

the event is just a Unit value) or contain just simple data for an Intent or arguments for a Fragment (e.

g.

the event is a very small data class).

Plan of AttackThis is somewhat similar to the previous solution.

The observer in the UI-component must make sure to clear the navigation-state.

In this case, this is done by setting the LiveData’s value to null.

As a complicating factor, a LiveData object’s value cannot be truly cleared.

The state-transitions (iii → i), (iv → i) and (iv → ii) do not exist.

Setting a LiveData's value to null may cause it to emit that null value later.

class MyViewModel(.

) : BaseViewModel() { val navToScreen: LiveData<MyNavToScreen> = MutableLiveData() .

.

navToScreen.

value = MyNavToScreen(.

) .

.

.

}fun <T> LiveData<T>.

nullify(): Unit = (this as MutableLiveData<T>).

value = null.

class MyActivity : Activity() { .

viewModel.

navToScreen.

observe(this, Observer { if (it != null) { viewModel.

navToScreen.

nullify() .

launch the other activity .

} }) .

}Pros and ConsProsNo need to modify or create our own variations of LiveData classesConsThe UI-component observers must make sure to clear out the LiveData navigation-properties they observeThe UI-component must be able to observe null values for navigation-properties and it should ignore them accordinglyThe UI-component takes on a responsibility, that should belong to the ViewModel, by nullifying the navigation-propertySolution 3SituationIt is the same situation as the one for solution 2, but it has some additional requirements:Our ViewModel must closely resemble the UI-component that it supports.

The ViewModel is not only an architectural component to separate concerns and minimize the amount code in the UI-component itself, it is mainly used to make our UI-component unit-testable, in order to reduce UI-tests and lessen the burden of manual QA testers.

Users cannot navigate to multiple places at the same time.

Navigation-events should bring the user to one other place in the app, therefore only one observer can subscribe to observe and handle a LiveData navigation-property.

Plan of AttackWe will create our own interfaces and classes and use them instead of LiveData.

The classes will use MutableLiveData instances, because we want to use as much of their implementation as possible.

We won’t use inheritance to accomplish this, to avoid any leaking of abstractions.

We’ll use aggregation, and wrap MutableLiveData instances instead.

Let’s examine one possible implementation of this.

For UI-components, our ViewModels should expose observable properties of the new LiveDataPublisher type:/** * Public interface of a Lifecycle aware publisher for * the benefit of a UI-component */public interface LiveDataPublisher<T : Any> { /** * See [LiveData.

observe] */ fun observe(owner: LifecycleOwner, observer: Observer<in T>)}/** * Useful extension function to be able to subscribe with a simple * lambda instead of an [Observer].

* * See also [LiveData.

observe] */fun <T : Any> LiveDataPublisher<T>.

subscribe(owner: LifecycleOwner, observer: (T?) -> Unit) { observe(owner, Observer { observer(it) })}Our ViewModels instantiate LiveDataField classes to emit UI-data.

A LiveDataField object is nothing more than a simple wrapper around a MutableLiveData object:/** * Represents a [LiveData] property for * observing UI-data changes.

*/class LiveDataField<T : Any> : AbstractLiveDataPublisher<T>() { override val liveData = MutableLiveData<T>() override fun observe( owner: LifecycleOwner, observer: Observer<in T> ) { liveData.

observe(owner, observer) }}Our ViewModels instantiate LiveNavigationField classes to emit navigation-events.

It prevents re-emission of navigation-events after a rotation by replacing the old MutableLiveData object with a new one when the observing UI-component is destroyed.

This prevents the state transition (iv → v) of the State Transition Diagram shown earlier, because the oldMutableLiveData object is no longer there.

When a UI-component’s observer calls observe, the newMutableLiveData object is used and its state transition will start at the initial state (i).

It would have been preferable to just reset the wrappedMutableLiveData object.

However, the state-transition (iv → i) is not possible, we can’t truly reset a MutableLiveData object.

/** * Represents a [LiveData] property for * observing UI-navigation events.

*/class LiveNavigationField<T : Any> : AbstractLiveDataPublisher<T>(), LifecycleObserver { override var liveData = MutableLiveData<T>() private var lifecycleOwner: LifecycleOwner?.= null override fun observe( owner: LifecycleOwner, observer: Observer<in T> ) { if (lifecycleOwner != null) { throw IllegalStateException("Function 'observe' can only be called once during each lifecycle") } lifecycleOwner = owner.

also { it.

lifecycle.

addObserver(this) } liveData.

observe(owner, observer) } @OnLifecycleEvent(Lifecycle.

Event.

ON_DESTROY) fun onDestroyed() { lifecycleOwner?.

let { lifecycleOwner = null liveData = MutableLiveData() it.

lifecycle.

removeObserver(this) } }}To make the picture complete, here is the AbstractLiveDataPublisher:/** * Base class of [LiveDataField] and [LiveNavigationField].

*/abstract class AbstractLiveDataPublisher<T : Any> : LiveDataPublisher<T> { protected abstract val liveData: MutableLiveData<T> var value: T?.get() = liveData.

value set(value) { liveData.

value = value }}The code snippet below shows you how these interfaces and classed can be used:class MyViewModel(.

) : BaseViewModel() { val counter: LiveDataPublisher<Int> = LiveDataField() val navToScreen: LiveDataPublisher<MyNavToScreen> = LiveNavigationField() .

.

counter.

value = secondsElapsed .

.

.

navToScreen.

value = MyNavToScreen(.

) .

.

.

}.

class MyActivity : Activity() { .

viewModel.

counter.

subscribe(this) { .

show number in text-field .

} viewModel.

navToScreen.

subscribe(this) { .

launch the other activity .

} .

}And to make this picture complete as well, the BaseViewModel has a handy extension-function to allow access to the value property without the need to define a private backing-field:abstract class BaseViewModel : ViewModel() { .

protected var <T : Any> LiveDataPublisher<T>.

value: T?.get() = (this as AbstractLiveDataPublisher<T>).

value set(value) { (this as AbstractLiveDataPublisher<T>).

value = value }}Pros and ConsProsNo need to provide a function to clear LiveData or to restore a UI-stateNo possibility for the UI-components to forget to call this functionThe UI-components handle navigation-events the same way they handle plain UI-dataVery suitable for an MVVM pattern where the ViewModel closely matches its corresponding UI-componentConsNot suitable for an MVI pattern that always emits the entire UI-stateLess suitable for an MVVM pattern that always emits the entire UI-state, unless we allow navigation-events to be observed separatelyLess suitable for an MVVM pattern where the ViewModel does not closely match the UI-component For example, we could decide to no longer use a dialog or a snackbar to show an error, and show it in a plain text-view instead.

This would leave the text-view empty after rotation, unless we change the ViewModel to use a LiveDataField instead of a LiveNavigationField.

The need for additional interfaces and classesSingleLiveEventWriters of the “To Do” Google Sample app created a class called SingleLiveEvent.

You can find it here: http://bit.

ly/SingleLiveEventIt is very similar to what our LiveNavigationField class does.

SingleLiveEvent has a few differences, though:It produces a warning if more than one observer subscribes to it, whereas LiveNavigationField fails fast by throwing an exceptionIt is a sub-class of MutableLiveData, possibly leaking some abstractions which may cause problems if the implementation of MutableLiveData changes.

It exposes the value property through its base-class MutableLiveData and that could allow a UI-component to read the value directly instead of just observing itConclusionIt is possible to come up with solutions that can deal with navigation events when our app is using a MVVM or MVI pattern.

None of them are ideal.

They require extra work or look a little hacky.

We’d need to be careful if we try to come up with a variation of a MutableLiveData implementation that can handle events in general and still allows for multiple observers owned by multiple lifecycle-owners that can each have a different lifecycle.

We don’t want to re-invent the EventBus using LiveData.

In my opinion, my ideal solution would be for Google to add a MutableLiveData sub-class to the Architecture Components, that just focuses on helping handle navigation-events by only allowing for at most one lifecycle-owner at any given time and resetting its value when the lifecycle-owner’s lifecycle ends (i.

e.

when it reaches the DESTROYED state).

Links[1] http://bit.

ly/AndroidLiveData[2] http://bit.

ly/AndroidLifecycleStateList of other blog postsIf you enjoyed reading this article, here is a list of other articles that I wroteLet’s also apply a run with Kotlin on our minds.. More details

Leave a Reply