Loading...
Home
  • Tech Blogs
  • Videos
  • Conferences
    • Droidcon News
    • Upcoming Conferences
    • Become a Partner
    • Past Events
    • Keep Me Informed
    • Diversity Scholarships
  • Community
    • droidcon Team
    • How to Hold a Droidcon
  • Android Careers
Sign In

Global CSS

 

Android SingleLiveEvent Redux with Kotlin Flow

 

 
Michael Ferguson
Android software developer with Lumeca Health
Published: February 03, 2021
Tweet
Share
 
Photo by Phil Goodwin on Unsplash

 

Several years have passed since Jose Alcérreca published his article on “The SingleLiveEvent Case”. This article was a great launching point for many developers as it got them thinking about different communication patterns between ViewModels and the associated View, be it a fragment or an activity.

 

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

A convenient way for a view (activity or fragment) to communicate with a ViewModel is to use LiveData observables. The…

medium.com

 

 

There have been many responses to the SingleLiveEvent case about ways to improve upon the pattern. One of my favorites was written by Hadi Lashkari Ghouchani.

 

LiveData with single events

You may searched for SingleLiveEvent in the Internet to find a good solution for a LiveData that send events once…

proandroiddev.com

 

However, both those cases still used LiveData as a backing data store. I feel there is still room to improve, especially with the use of Kotlin coroutines and flows. In this article I will describe how I handle one-shot events and how to observe those events safely within the Android lifecycle.

 

Background

To stay consistent with other articles about SingleLiveEvent, or variations of that pattern, I’m going to define an event as a notice to take an action once and once only. The original SingleLiveEvent article used displaying a SnackBar as an example but you can include other one-shot action such as fragment navigation, starting an activity, displaying a notification and so on as examples of “events”.

In the MVVM pattern, the communication between the ViewModel and its associated view (fragment or activity) is often done by following the observer pattern. This decouples the view model from the view allowing the view to go through various lifecycle states independently of data being emitted to the observers.

In my ViewModels, I usually expose two flows to be observed. The first is the view state. This flow defines what the state of the UI is. It can be observed repeatedly and is usually backed by a Kotlin StateFlow LiveDataor some other type of data store that exposes a single value. I’m going to ignore this flow as it is not the focus of this article. However, if are you interested there are many articles that describe how to implement UI state with StateFlow or LiveData.

The second observable flow, and the focus of this article, is much more interesting. The purpose of this flow is notify the view to perform an action once and once only. For example, to navigate to another fragment. Let’s explore what options we have for this flow.

 

Requirements

Presumably event are important, even critical. So lets define some requirements for this flow and its observer:

  1. New events cannot overwrite unobserved events.
  2. If there is no observer, events must buffer until an observer starts to consume them.
  3. The view may have important lifecycle states during which it can only observe events safely. Thus the observer may not always be active or consuming the flow at a given moment in time.

 

A Safe Emitter of Events

So satisfy the first requirement, it’s clear that a stream is necessary. LiveData or any Kotlin flow that conflates values, such as StateFlow or a ConflatedBroadcastChannel, is not appropriate. A set of rapidly emitted events may overwrite each other with only the last event being emitted to the observer.

What about the use of SharedFlow? Can that help? Unfortunately, no. SharedFlow is hot. This means that during periods where the is no observer, say during a configuration change, events emitted on to the flow are simply dropped. Regrettably, this also makes SharedFlow inappropriate to emit events on.

So what options do we have to meet the second and third requirements? Fortunately, some options have already been described for us. Roman Elizarov of JetBrains wrote a great article on the different use cases for various types of flows.

 

Shared flows, broadcast channels

See how shared flows made broadcast channels obsolete, but regular channels cover and will continue to support an…

elizarov.medium.com

 

Of particular interest in that article is the section “A use-case for channels” where he describes exactly what we need - a single-shot event bus that is a buffered stream of events.

From the article (emphasis mine):

 

… channels also have their application use-cases. Channels are used to handle events that must be processed exactly once. This happens in a design with a type of event that usually has a single subscriber, but intermittently (at startup or during some kind of reconfiguration) there are no subscribers at all, and there is a requirement that all posted events must be retained until a subscriber appears.

 

Now that we’ve found a safe way to emit events, let us define the basic structure of an example ViewModel with some sample events.

 

https://gist.github.com/fergusonm/091851d1b2227fa69e578e2c4e782280#file-mainviewmodel

 

The above example view model emits two events immediately on construction. The observer may not be there to consume them right away so they are simply buffered and emitted when the observer starts to collect from the flow. Also included in the above example is the view model reacting to a button click.

The actual definition of the event emitter is surprisingly simple and straightforward. Now that the way events will be emitted is defined let's move on to how we can safely observe these events in the context of Android and the constraints imposed by the different lifecycle states.

 

A Safe Observer of Events

The different lifecycles the Android framework imposes on developers can be difficult to work with. Many actions may only be safely performed within certain lifecycle states. For example, fragment navigation should only be done after onStart but before onStop.

So how do we safely observe the flow of events only within given lifecycle states? If we observe the view model event flow from, say a fragment, within the coroutine scopes provided by the fragment does this do what we need?

 

 

Unfortunately the answer is no. The documentation for viewLifecycleOwner.lifecycleScope indicates that this scope is cancelled when the lifecycle is destroyed. This means that it’s possible to receive events after the lifecycle has reached the stopped state but is not yet destroyed. This may be problematic if actions, such as fragment navigation, are performed as part of processing of the event.

 

Pitfalls of usinglaunchWhenX

Perhaps we can use launchWhenStarted to control the different lifecycle states an event is received in? For example:

 

 

Unfortunately this has some major issues as well, particularly with respect to configuration changes. Halil Ozercan wrote a wonderful deep dive into the Android Lifecycle Coroutines where he describes the underlying mechanisms behind the launchWhenX set of functions.

 

Deep Dive Into Lifecycle Coroutines

Lifecycle Coroutine Extensions were introduced some time ago to ease the usage of coroutines in Android world. You can…

medium.com

 

He notes in his article:

 

launchWhenX functions are not cancelled when lifecycle leaves the desired state. They simple get paused. Cancellation happens only if lifecycle reaches DESTROYED state.

 

I responded to his article with a demonstration that it is possible to lose events on configuration change when observing a flow from within any of the launchWhenX functions. It’s a long enough response that I won’t repeat it here so I encourage you to go read it.

 

Pitfalls of observing flows in launchWhenResumed

Halil Ozercan has a great deep drive article on the various lifecycle coroutines that are available. I feel it is worth…

themikeferguson.medium.com

 

 

Thus, regrettably, we cannot take advantage of the launchWhenX extension functions either to help control what lifecycle state a flow is observed in. So what can we do?

 

Taking a Step Back

If we take a moment to look at what we’re trying to do we can more easily figure out a solution to observing only in specific lifecycle states. Breaking down the problem we note that that all we really want to do is start observing in one state, and stop observing in another.

If we were using another tool, say RxJava, we might subscribe to the event stream in on onStart lifecycle callback, and dispose during the onStopcallback. (A similar pattern could be required for generic callbacks as well.)

 

 

Why can’t we do that with Flow and coroutines? Well, we can. The scope will still cancel when the lifecycle is destroyed but we can tighten up when the observer is active to just the lifecycle states between start and stop.

 

 

This meets the third requirement and solves the problem of only observing the event stream during safe lifecycle state but it introduces a lot of boilerplate.

 

Cleaning Things Up

What if we delegate the responsibility of managing the job to something else to help remove that boilerplate?

Patrick Steiger’s article on substituting LiveData for StateFlow or SharedFlow had an amazing little nugget in it. (It’s also a great read.)

 

Substituting Android's LiveData: StateFlow or SharedFlow?

Kotlin Coroutines recently introduced two Flow types, SharedFlow and StateFlow, and Android's community started…

proandroiddev.com

 

He created a set of extension functions that automatically subscribes a flow collector when a lifecycle owner reached start, and cancels the collector when the lifecycle reached the stop phase!

Below is my slightly modified version of it:

 

 

with a small variation for the Fragment’s view lifecycle:

 

 

Using these extension functions is super simple and straightforward.

 

 

Now we have an event observer that observes only after the start lifecycle has been reached and it cancels when the stop lifecycle has been reached.

It also has the added benefit of re-starting flow collection when the lifecycle goes through a less common, but not impossible, transition from stop to start.

This makes it safe to perform actions like fragment navigation or other lifecycle sensitive processing without worrying about what the lifecycle state is. The flow is only collected during safe lifecycle states!

 

Pulling It All Together

Bringing everything together this is the basic pattern I use to define a stream of “single live events” and how I observe it safely.

To summarize: the view model’s event stream is defined using a channel receiving as a flow. This allows the view model to submit events without having to know about the state of the observer. Events are buffered when there is no observer.

The view (ie. the fragment or the activity) is observing that flow only after the lifecycle has reached the started state. Observation is cancelled when the lifecycle reaches the stopped event. This allows safe processing of events without worrying about the difficulties imposed by the Android lifecycle.

Finally, with the help of the FlowObserver the boilerplate is eliminated.

You can see the entire sample here:

 

 

I want to give a huge shout out to all the authors referenced in this article. Their contributions to the community has greatly improved the quality of my work.

 

 

 

Tags: Android, Kotlin, Android Development, Android App Development, Android Developers

 

View original article at: 


 

Originally published: January 26, 2021

Android News
Our Engineering Roadmap
Our Engineering Roadmap

By Mark Ng

We just completed our engineering road map for our Android apps at Australia Post. Each year we get together and try to decide on what we are going to do from an engineering perspective for the next 12 months. Each team gets to decide on what should be done now, what they want to complete by the end of the year and whats on the horizon for next year.

By ProAndroidDev -
Android News
Android Activity Lifecycle considered harmful
Android Activity Lifecycle considered harmful

By Eric Silverberg

The Android activity lifecycle is one of the first architectural concepts learned by a new Android developer. Ask any Android dev and they’ll likely all have seen this handy diagram from Google: 

By ProAndroidDev -
Android News
Our Safe Approach to Android Jetpack Navigation in a Multi-Modular App
Our Safe Approach to Android Jetpack Navigation in a Multi-Modular App

By Alejandro Weichandt

It has been a year since we started working on the Android version of the mobile app at Sync. During that year, we faced more than once that moment when we had to choose which path to follow on an Architectural decision. This story is about Navigation.

By ProAndroidDev -
Android News
Custom KotlinX Serializers
Custom KotlinX Serializers

By Jobin Lawrance

Let’s say we have a third-party class that we are using as a type in one of our data class that we want to be serialized, then we have to write a custom serializable for @Serializable to work.

 

By ProAndroidDev -
droidcon News

Tech Showcases,

Developer Resources &

Partners

/portal/rest/jcr/repository/collaboration/Groups/spaces/droidcon_hq/Documents/public/home-details/EmployerBrandingHeader
EmployerBrandingHeader
https://jobs.droidcon.com/
/portal/rest/jcr/repository/collaboration/Groups/spaces/droidcon_hq/Documents/public/employerbranding/jobs-droidcon/jobs.droidcon.com
jobs.droidcon.com

Latest Android Jobs

http://www.kotlinweekly.net/
/portal/rest/jcr/repository/collaboration/Groups/spaces/droidcon_hq/Documents/public/employerbranding/kotlin-weekly/Kotlin Weekly
Kotlin Weekly

Your weekly dose of Kotlin

https://proandroiddev.com/
/portal/rest/jcr/repository/collaboration/Groups/spaces/droidcon_hq/Documents/public/employerbranding/pad/ProAndroidDev
ProAndroidDev

Android Tech Blogs, Case Studies and Step-by-Step Coding

/detail?content-id=/repository/collaboration/Groups/spaces/droidcon_hq/Documents/public/employerbranding/Zalando/Zalando
/portal/rest/jcr/repository/collaboration/Groups/spaces/droidcon_hq/Documents/public/employerbranding/Zalando/Zalando
Zalando

Meet one of Berlin's top employers

/detail?content-id=/repository/collaboration/Groups/spaces/droidcon_hq/Documents/public/employerbranding/Academy for App Success/Academy for App Success
/portal/rest/jcr/repository/collaboration/Groups/spaces/droidcon_hq/Documents/public/employerbranding/Academy for App Success/Academy for App Success
Academy for App Success

Google Play resources tailored for the global droidcon community

Follow us

Team droidcon

Get in touch with us

Write us an Email

 

 

Quicklinks

> Code of Conduct

> Terms and Conditions

> How to hold a conference

> FAQs

> Imprint

Droidcon is a registered trademark of Mobile Seasons GmbH Copyright © 2020. All rights reserved.

powered by Breakpoint One