avatarJose Alcérreca

Summary

The article discusses best practices for handling one-time events like Snackbar messages or navigation signals in Android using LiveData, recommending treating events as part of the application state and using an Event wrapper for clarity and reliability.

Abstract

The article, updated in 2021, provides guidance on using LiveData for handling one-time events in Android applications. It emphasizes that while LiveData is suitable for continuously displayed data, it is not ideal for one-time events such as Snackbar messages, navigation events, or dialog triggers. The author outlines common mistakes, such as using standard LiveData for events and resetting event values in the observer, and suggests better approaches. The recommended solution is to use an Event wrapper class that explicitly manages whether an event has been handled, thus reducing errors and making the code more maintainable. The article also provides examples and links to code snippets, including the use of SingleLiveEvent and a custom EventObserver for handling multiple observers and reducing repetitive code.

Opinions

  • The author deprecates the use of LiveData directly for events, considering it "plain ugly" and hard to understand.
  • The author advocates for a design approach that integrates events as part of the application state.
  • The use of SingleLiveEvent is acknowledged as a solution for a particular scenario but is criticized for its limitation of only sending an update once and being restricted to one observer.
  • The recommended approach with an Event wrapper is praised for its explicit handling of events, which reduces mistakes and is more maintainable.
  • The article promotes the use of a custom EventObserver to streamline code when dealing with multiple events.

LiveData with SnackBar, Navigation and other events (the SingleLiveEvent case)

2021 Update: This guidance is deprecated in favor of the official guidelines.

A convenient way for a view (activity or fragment) to communicate with a ViewModel is to use LiveData observables. The view subscribes to changes in LiveData and reacts to them. This works well for data that is displayed in a screen continuously.

However, some data should be consumed only once, like a Snackbar message, a navigation event or a dialog trigger.

Instead of trying to solve this with libraries or extensions to the Architecture Components, it should be faced as a design problem. We recommend you treat your events as part of your state. In this article we show some common mistakes and recommended approaches.

❌ Bad: 1. Using LiveData for events

This approach holds a Snackbar message or a navigation signal directly inside a LiveData object. Although in principle it seems like a regular LiveData object can be used for this, it presents some problems.

In a list/detail app, here is the list’s ViewModel:

In the View (activity or fragment):

The problem with this approach is that the value in _navigateToDetails stays true for a long time and it’s not possible to go back to the first screen. Step by step:

  1. The user clicks the button so the Details Activity starts
  2. The user presses back, coming back to the list activity
  3. The observers become active again, after being inactive while activity was in the back stack
  4. The value is still true so the Details activity is incorrectly started again

A solution would be to fire the navigation from the ViewModel and immediately set the flag to false:

However, one important thing to remember is that LiveData holds values but doesn’t guarantee to emit every value that it receives. For example: a value can be set when no observers are active, so a new one will just replace it. Also, setting values from different threads could lead to race conditions that would only generate one call to the observers.

But the main problem with this approach is that it’s hard to understand and plain ugly. How do we make sure the value is reset after the navigation event has happened?

❌ Better: 2. Using LiveData for events, resetting event values in observer

With this approach you add a way to indicate from the View that you already handled the event and that it should be reset.

Usage

With a small change to our observers we might have a solution for this:

Adding the new method in the ViewModel as follows:

Issues

The problem with this approach is that there’s some boilerplate (one new method in the ViewModel per event) and it’s error prone; it’s easy to forget the call to the ViewModel from the observer.

✔️ OK: Use SingleLiveEvent

The SingleLiveEvent class was created for a sample as a solution that worked for that particular scenario. It is a LiveData that will only send an update once.

Usage

Issues

The problem with SingleLiveEvent is that it’s restricted to one observer. If you inadvertently add more than one, only one will be called and there’s no guarantee of which one.

✔️ Recommended: Use an Event wrapper

In this approach you manage explicitly whether the event has been handled or not, reducing mistakes.

Usage

The advantage of this approach is that the user needs to specify the intention by using getContentIfNotHandled() or peekContent(). This method models the events as part of the state: they’re now simply a message that has been consumed or not.

With an Event wrapper, you can add multiple observers to a single-use event

In summary: design events as part of your state. Use your own Event wrapper in LiveData observables and customize it to fit your needs.

Bonus! Use this EventObserver to remove some repetitive code if you end up having lots of events.

Android App Development
Livedata
Android Architecture
Architecture Components
Recommended from ReadMedium