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

 

Synchronous communication with the UI using StateFlow

 

 
Raul Hernandez Lopez
Software Engineer (Android) @ Twitter. Kotlin lover. Continuous learner, sometimes runner, some time speaker & open minded. Opinions my own.
Published: October 21, 2020
Tweet
Share
 
 

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 UIthread, 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:

 

Fueled Reactive apps with Asynchronous Flow — Part 2 — Asynchronous communication: Streams &…

Asynchronous communication: Streams & Basics

proandroiddev.com

 

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:

 

Image for post

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:

 

Fueled Reactive apps with Asynchronous Flow — Part 1 — Use case & migration strategy

Use Case & Migration Strategy

proandroiddev.com

 

At this point, we completed a migration coming from RxJava into KotlinCoroutines 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:

 

Image for post

 

Image for post

 

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.

 

Image for post

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 StateFlowinstead.

 

Image for post

StateFlow migration starts.

 

SearchTweetUseCase using callbacks

The SearchTweetUseCase is where we implemented the following UseCasecontract 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 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 catchwould 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 Kotlinclasses. For instance Views and Presenters are still in Java.

 

Image for post

 

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 cancelmethod 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 initialiseProcessingQuerymethod 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 StateFlowparameter 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, @RetainedScopewould 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 v2 (including StateFlow)

 

Link to the talk’s replay (video recording) at Droidcon EMEA:

 

Fueled Reactive apps with Asynchronous Flow

Speaker: Raul Hernandez Lopez Source: droidcon EMEA 2020

www.droidcon.com

 

To recap previous articles and better understand this, please read:

 

Fueled Reactive apps with Asynchronous Flow — Part 1 — Use case & migration strategy

Use Case & Migration Strategy

proandroiddev.com

 

Fueled Reactive apps with Asynchronous Flow — Part 2 — Asynchronous communication: Streams &…

Asynchronous communication: Streams & Basics

proandroiddev.com

 

Fueled Reactive apps with Asynchronous Flow — Part 3— Data layer Implementation

Data layer Implementation

Data layer Implementationproandroiddev.com

 

Fueled Reactive apps with Asynchronous Flow — Part 4— Use Case layer Implementation

Use Case layer Implementation

Use Case layer Implementationproandroiddev.com

 

Fueled Reactive apps with Asynchronous Flow — Part 5— View Delegate Implementation

View Delegate Implementation

View Delegate Implementationproandroiddev.com

 

Fueled Reactive apps with Asynchronous Flow — Part 6 — Lessons learned & Next steps

Lessons learned & Next steps

Lessons learned & Next stepsproandroiddev.com

 

Thanks to Manuel Vivo and Enrique López-Mañas. 

 

 

Tags: Kotlin Flow, Kotlin Coroutines, AndroidDev, Callback Hell, Callback

 

View original article at: 


 

Originally published: October 16, 2020

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