Synchronous communication with the UI using StateFlow

Raul Hernandez Lopez
ProAndroidDev
Published in
10 min readOct 16, 2020

--

Synchronous communication Design cover

(This article was featured at Android #436 & Kotlin #220 Weekly)

Introduction to Synchronous communication

Synchronous communication in simple terms is when we start an action whose output may produce an immediate effect or reaction that must be handled as soon as possible because we are waiting for this process to finish.

This is the opposite of Asynchronous communication, when the response is not coming instantly, with situations or examples like:

  • we are requesting information to the network or database, we carry on with other tasks and the result of the async operation will come back at some point in the future.
  • or like in Android, the views do many asynchronous operations on the UI thread, for instance when we fire off an action but we don’t get a confirmation that it happened, at some point in time we may get the result within a callback.

Those concepts are applicable to all kinds of applications, either backend or frontend / mobile kind of apps would perform either Synchronous and Asynchronous communications for their processes.

For further details about Asynchronous communication please read Part 2:

Notice in this article we will focus on the Synchronous communication.

This article follows up the Use case used at “Fueled Reactive apps with Asynchronous Flow” where a Search mechanism allows us to look for words or hashtags (#tweetswithflow is an example) to get tweets back as a result of it, see the next GIF for full context:

The user is typing #tweetswithflow for starting the query and receive some tweets back in the search

For further details about the Migration strategy previously followed or this use case, please read the previous introduction article about it in Part 1:

At this point, we completed a migration coming from RxJava into Kotlin Coroutines and Flow from the Data layer to the Presentation layer (Model-View-Presenter), including the View‘s Delegate.

Callbacks help us or not really?

In a typical workflow of the app, the View Delegates get asynchronous results from the business layer using callbacks.

A callback is by default a synchronous communication approach that happens as a response of an asynchronous action triggered in the past. Nested callbacks can create something called Callback Hell, see picture below.

Well, all started with Ryu getting ready:

Ryu (Street Fighter) has made the Callback Hell even with a bigger indentation.

In a nutshell, the first callback calls one another that calls another callback and so on. This is an anti-pattern as it hurts readability because by the time you reach the innermost callback of the chain, you might not know where you are and what you are doing. Callbacks don’t have a return value as well as the results we want to pass back are declared as input parameters which could be passed to other functions too.

Callbacks not being deterministic break an interesting principle:

Referential Transparency (RT), which is the ability to make larger functions out of smaller ones through composition and produce always the same output for a given input. RT is used commonly in functional programming (FP), for further context read this article to learn more about this or other FP concepts.

This gives us a very good reason to avoid using callbacks as much as possible to communicate with the view.

Why were we using Callbacks so far?

Callbacks in the View layer were typically used in Clean Architecture patterns.

We used callbacks in the View layer as a way to synchronously communicate between Presenters and Views. A few code snippets will be explored later with more implementation details.

At the next diagram we see the connection between Views, those receive the results coming back from the Presenter who gives back those results produced by the Use Cases, those at the same time are received from the Data layer or Repository.

Can we then remove any callbacks to communicate?

Using StateFlow to get rid of callbacks

We are going to start this process by changing them and using StateFlow instead.

StateFlow migration starts.

SearchTweetUseCase using callbacks

The SearchTweetUseCase is where we implemented the following UseCase contract that uses callbacks:

UseCase contract used for Use Cases with Callback as a parameter for the execute method.
SearchTweetUseCase implements UseCase with a callback in it.

StateFlow

Let’s start by analysing the abilities of a StateFlow. It is defined as a very efficient way to pass state across different entities. Amongst its abilities:

  1. It synchronously retrieves the latest element emitted into the Flow
  2. It emits the latest emitted element to a new collector
  3. It’s a kind of Flow therefore collectors get new emissions as well.
  4. It’s specially well-suited for communication between Views and other Android Arch components, due to its nature it is read-only avoiding side effects.

How StateFlow can solve callbacks problems?

  • StateFlow forces us to get read-only values. Which guarantees a consistent type safe state for our UI. Those states won’t be modified in any intermediate entities or callbacks. Making it deterministic by default.
  • StateFlow sends updates only when the state has changed, otherwise, it doesn’t react. This is the opposite to callbacks that pass any single value that is received in the input parameters.
  • StateFlow code is easy to read and to use (this will be covered in-depth in next sub-sections).

Migration to StateFlow for the UseCase

Talking about how to start this migration. We will only need to expose the StateFlow (from Kotlin Coroutines v. 1.4.0 StateFlow is stable) values to its shareholders by means of getStateFlow(): StateFlow<TweetsUIState?>, we will analyse TweetsUIState later.

Now we need to create the instance for the interface StateFlow with MutableStateFlow.

SearchTweetUseCase

When the user is typing the query and the system starts searching for it, we probably don’t want to trigger a new emission into the StateFlow, for that reason, to avoid creating a new idle state, we would make sure the first value is null (StateFlow always returns a value). This made the integration with the current system faster, however, a better practice could be to add a not nullable new state for the UI state instead.

It is really important to cover all different states we could potentially face in real scenarios, otherwise we could find weird edge cases.

Amongst the states:

  • The system should show a list of tweets with ListResultsUIState
  • The system should show an error toast or dialog in combination with a message with ErrorUIState
  • The system should show there aren’t any results with EmptyUIState
  • The system should show the popup / dialog when loading initially with LoadingUIState
TweetsUIState with all defined UI states

StateFlow integration setup

The first step to integrate our StateFlow into the SearchTweetUse will be removing all callback methods like we did for callback?.onShowLoader(). There we replaced it with the property method tweetsStateFlow.value (MutableStateFlow provides a setter for the value) whose value would be assigned with a new state.

SearchTweetUseCase setup with StateFlow

The same approach will be applied for the rest of states from TweetsUIState.

All states that are coming from the business logic would be passed through the presentation layer straight to the views and its delegates. Previously we described that the LoadingUIState is emitted by means of StateFlow. In the snippet below for each streaming iteration we are checking if the collection of tweets is empty to react with the EmptyUIState, or passing any results we want to render on screen within ListResultsUIState. Finally the catch would emit the ErrorUIState with the tweets state flow value. This collection would be done into the preferred scope by launchIn(scope).

SearchTweetUseCase emits StateFlow values using tweetsStateFlow.value

Interestingly, the next diagram remarks a combination of Java and Kotlin classes. For instance Views and Presenters are still in Java.

To collect the value from a StateFlow we need a coroutine scope. Coroutine scopes can only be used into Kotlin files.

Therefore, a Kotlin file or class would be mandatory for it. For that reason we need to create a new collaborator, it is called StateFlowHandler here.

Migration to StateFlow for the Presenter

The SearchTweetPresenter has SearchTweetUseCase injected.

SearchTweetPresenter class and cancel method

To cancel the SearchTweetUseCase scope manually, we could add the cancel method there.

Prior to the refactoring of SearchTweetPresenter, the reference from the View was inside the SearchCallbackImpl for later usage into the SearchTweetUseCase, a bit messy really. Those callback/view references have been entirely removed from the execute method, like we reviewed previously, as well as from this presenter class. Finally we expose the StateFlow to other shareholders.

searchTweets method and StateFlow value is exposed from the SearchTweetUseCase

Migration to StateFlow for the ViewDelegate

A reference of each View is used inside the SearchViewDelegate like the Android element called SearchView. In addition to our preferred option for the view (some examples of this are using: an Activity, a Fragment or a custom View, etc). This is initialised at the initialiseProcessingQuery method where the view can apply for the latest initStateFlow method. Here the input parameter is the StateFlow reference exposed from the SearchTweetPresenter.

SearchViewDelegate exposes the StateFlow values coming from the Presenter

SearchFlowHandler integration

TweetsListUI has injected a StateFlowHandler and a SearchViewDelegate.

StateFlowHandler would use an init method with the StateFlow parameter passed from other layers in addition to to the view reference into it. Processing the StateFlow collected in the end with processStateFlowCollection.

TweetsListUI initialises the StateFlow collected

SearchFlowHandler connection and collection

We would initialise a bit late the StateFlow value, that’s why we need it to be lateinit var.

Now we need to collect and process any states we want the view to manage.

StateFlowHandler filters any possible values to just react to not null values like we intended from the beginning. Those reactive states will be reflected in the View, who will react showing the loader or any results or may be an empty state message with the failed query or an error produced by a side effect, for instance coming from a thrown exception.

StateFlowHandler processes the state collection
TweetsListUIUtils usage of handleStates extension function for the View

Clean up of resources to prevent memory leaks

We could receive emitted values of StateFlow meanwhile the app is in the background. This would happen when the view has stopped in the case of Android apps.

Let’s have a look to this snippet again:

StateFlowHandler processes the state collection

There are definitely options to prevent reacting to StateFlow while the app is in the background:

  • We could cancel the coroutine scope to avoid processing the state collection when reaching onStop() & initialising it again when onStart().
  • Or using lifecycleScope.launchWhenStarted from the Android Architecture Components — this checks whether the view has started to allow StateFlow going through).

To close and clean resources, in order to prevent memory leaks, we could do by adding a cancel method that can be invoked out of the StateFlowHandler. This will enable Structured Concurrency do it for us.

StateFlowHandler closes the scope and triggers Structured Concurrency

Finally, the TweetsListUI will be destroyed at some point of the View’s lifecycle and we can make good use of the clean up cancel methods declared into our delegates, handlers and other friends.

TweetsListUI destroys the view and its collaborators

When we have a hybrid project combining both Java and Kotlin files, that is why we need to have different areas where we can cancel the coroutine scopes for them, since the Java files make a bridge amongst elements. If we just had Kotlin classes, we could potentially use the same Coroutine Scope for a particular annotation everywhere, for example: @ActivityScope would use one coroutine scope based on the Activity lifecycle, @RetainedScope would use another one based on configuration changes lifecycle, etc. Similarly to what lifecycleScope does.

I believe this is all I got to migrate using StateFlow instead of Callbacks, with this we are going to have a “Fueled Reactive” app!

Key takeaways

  • Synchronous communications work better with read-only values because those prevent edge cases and unexpected side effects. This guarantees our code to be deterministic, thus, our unit tests would take advantage of it, in addition to this helps us build reactive solutions for our apps.
  • StateFlow gives a more readable and easy to use interface to communicate synchronously amongst different hybrid projects (Java & Kotlin files).

If you liked this article, clap and share it, please!

Cheers!

Raul Hernandez Lopez

GitHub | Twitter | Gists

I want to give a special thanks to Manuel Vivo (follow him!), Alfredo Cerezo Luna (follow him!) & Enrique López-Mañas (follow him!) for reviewing this article, suggesting new improvements and to make it more readable.

For context, this article starts from the “Fueled Reactive apps with Asynchronous Flow” v2 (including StateFlow) of this presentation (given at Droidcon EMEA):

Fueled Reactive apps with Asynchronous Flow including StateFlow v2

Link to the talk’s replay (recorded video) at Droidcon EMEA 2020, featured at Kotlin #221 Weekly:

--

--

Senior Staff Software Engineer. Continuous learner, sometimes runner, some time speaker & open minded. Opinions my own.