Getting ready for Declarative UIs — Part 1 — Unidirectional Data Flow

Raul Hernandez Lopez
ProAndroidDev
Published in
7 min readFeb 4, 2021

--

Photo by Henrik Verle on Unsplash

(This article was featured at Compose #3 Digest, Android #452, Kotlin #236 Weekly & jetc.dev #51)

Most of us Android engineers probably remember that Google IO on 2019 where Romain & Chet presented Jetpack Compose, a modern Android toolkit to build native UI. They mentioned some awesome core features of it, among them that it is Reactive & Kotlin.

However, it is especially remarkable to say that to achieve Declarative UIs, we would need to connect the different dots with a Reactive approach. My call would be to do that by using Unidirectional Data Flow (UDF) in Android and Kotlin for it. We’ll look more into why UDF + declarative + reactive work so well.

For this first article of the series, I will focus first on describing this use case and its previous architecture.

Now, let’s have a look at what kind of sample app I want to “Get ready for Declarative UIs”.

Use case and its different UI actions

The sample app is a Tweets Search app using the public Tweets Search API.

The main actor or user will start typing a query of interest for the Search, which means any time the user types a character, the mechanism starts acting.

After that a series of different things happen like:

  • Loading spinner, this is the loading indicator the user will see before any new data comes up.
  • Empty results text, this could happen in two situations: no data came from the network. Or when retrieving saved information from the DB before refreshing with fresh network information.
  • List of results, list of different tweets rendered from a query.
  • Error message text, error message rendered to the user, produced by any exception or network failure.
User is typing “#tweetswithflow”

Now that we got a better idea of the Use Case that the sample app is doing, let’s analyse the previous architecture that we are trying to adapt.

Overview of a previous architecture

The previous architecture uses for the Presentation layer the Model-View-Presenter pattern (MVP) and Clean Architecture: Business Layer with a Use Case and a Data Layer Repository with its Data Sources. For further details about this existing architecture, I’d recommend reading this.

On the input side (User inputs):

For the (results) outputs, this is the set of Kotlin friends used:

The next diagram represents the Data Layer with its connections:

Data layer with the Repository and its Data Sources
  • Repository manages data sources.
  • Data Sources send data by means of suspend functions.

Then the Repository will send a Flow to the UseCase. From that point, once the Flow element is collected, StateFlow takes place into the big picture. Because it will be used to communicate synchronously.

The reasons why StateFlow (and what’s a StateFlowHandler) is a good option to Synchronise with the UI are indicated in detail here:

In a nutshell, why it is a perfect fit for UDF? it allows determinism by returning read-only values. By determinism, I mean the fact that given a particular input, will always produce the same output.

Now that we understand what are we dealing with and the proposed Kotlin friends that would support our end, we can dig deeper on how to start adopting UDF into the existing architecture.

Adopting the UDF

The definition of UDF in very simple terms is one-way data flow.

There is only one way to start an action and therefore one way to get its output.

This means that our UI intends an Action. This Action returns some results which are transformed into a State. Then the State is rendered into the UI. Here’s what that looks like visualised:

UDF triangle

UI performs an Action

Let’s imagine the next situation:

  • First, our existing Imperative UI intends a certain Action that gets triggered via a View Delegate (a delegate of the view, which uses the Delegation pattern).
  • The View Delegate always triggers the Presenter’s search functionality.
  • Then the Presenter executes a unique UseCase for that specific Action.

Action is transformed into a unique State

Note: I’m omitting details on purpose to focus in what’s really important here, our UDF, where the data is coming from is not relevant, let’s assume the UseCase has retrieved all the data it needs from the Data Layer.

  • Now, the UseCase starts modelling and dispatching results in reasonable States. Where a State is defined as unique too, one State would render one UI.
  • Later, an entity called StateFlowHandler will communicate the State to another Imperative UI.

By using this structure, we could essentially replace that final Imperative UI who will become a Declarative UI. This will open the door to introduce Jetpack Compose.

This approach would work well, since our “Data Flow” would move towards one only direction, therefore using “Unidirectional Data Flow” all the way around.

Modelling the meaningful UI States

If you remember, previously we mentioned a few UI actions. Those clearly could be interpreted as States. States that we could use as part of our UDF. For instance:

  • A Loading spinner would become a Loading UI state.
  • Empty results text would become an Empty UI state.
  • List of results would become a List UI state.
  • Error message text would become an Error UI state.

Maybe we could go one step further and adding an Idle UI state too. If you wonder why that would be the initial state for when the view is rendered, the one StateFlow would emit due to it always emits the latest value, even the initial one. With this, we would avoid having nullable states, and we would cover all possible states.

Do you wonder how the modelling (I can recommend this detailed article about “Modeling UI State on Android”, thanks Stojan!) of those states would look like after we model them?

A sealed class would define the hierarchy of strongly typed states composed by data class and object. Notice that all of the data classes are read-only since we are using val. Since StateFlow uses distinctUntilChanged under the hood, introducing mutability into our data model can lead to issues and might leave us wondering why our flow didn’t emit (thanks Sam for the good hints!). Remember to optimise your code on Recomposition scenarios, Thinking in Compose.

TweetsUIState.kt sealed class for UI states

To conclude this first article, I will enumerate a couple of clear advantages of using UDF.

Advantages

Flexibility

Making our systems State-based, we would have our systems ready for both, Imperative and Declarative UIs. This flexibility would make our life as developers much easier.

Layers composition

We could add new smaller elements in existing bigger ones. Defining clear separation of concerns, both between layers and collaborating with each other, enhancing encapsulation among other good principles. That approach would be really useful to replace existing elements of the system and integrate them with the new ones.

That’s a wrap. I hope you enjoyed this first article.
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 Sean McQuillan (follow them!) & Jossi Wolf (follow them) for the good review of this article and to make it more readable to anyone!

The next article is about the Implementation of this at the project and its codebase:

The last article of this series about the “Why” is:

--

--

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