Fueled Reactive apps with Asynchronous Flow — Part 1 — Use case & Migration Strategy

Raul Hernandez Lopez
ProAndroidDev
Published in
9 min readMay 28, 2020

--

“Fueled Reactive apps with Asynchronous Flow” cover

(This article was featured at Android #418 & Kotlin #200 Weekly)

During this first part, I will introduce both the Use case which drove me to create the presentation called “Fueled Reactive apps with Asynchronous Flow” and what the Migration strategy I decided to take forehand. This is the story behind the scenes of the presentation.

First, when I thought about this topic back in December 2019 my initial idea was to learn more about Kotlin Coroutines and Kotlin Flows in particular. Understanding any drawbacks or benefits as well as solving myself the question:

why would we like to migrate a well assembled existent reactive mobile application made from the ground with RxJava 2?

The hype was really high after the talk given by Roman Elizarov about Kotlin Flows during the last KotlinConf’19. Coroutines were stable at the time and actually some operators of Kotlin Asynchronous Flows too.

Use Case

For further context, around two years ago I built that kind of sample app myself from scratch using RxJava and Java. Therefore I started wondering precisely that, can that be migrated easily? Is it worth it? For my particular use case, I owned already that kind of old fashion — kind of legacy — Android sample app which was able to simply search for any tweet or hashtag using the Twitter public API for the Search service. Therefore, I expected this to be a good and simple enough use case to experiment with.

sample app running a search after typing the hashtag #androidmakers

This initially sounds like something easy enough to migrate, right? Just a couple of screens with two use cases, one for the search for any single typing by the user, and one for the detail of every single tweet.

This is definitely a good task for a Software Engineer, designing a plan to start with, indeed what we call…

Migration Strategy

The setup of the previous state of the app was sort of this:

  • Dagger 2 for Dependency Injection.
  • Retrofit for Network handling.
  • Picasso for Image handling.
  • RxJava 2 for any Asynchronous processing.

And now the real challenge, migrating this to new technologies…

This call to action looks like a reasonable refactor, precisely because Legacy means Refactor, in two years time many things can dramatically change a lot. Usually, previously used frameworks evolve quite a bit in no time. For instance Room by Google is a well known DB tool nowadays and it wasn’t a few years ago. Of course, Kotlin is the de facto language for Android now, anyone is or would like to use it in production.

Did I actually mention the fact that the sample app once I start looking back at it was completely written in Java?

But this doesn’t end here, this can be far more interesting by…

Analysing the previous Architecture

Back at the time, Clean Architecture was very popular, and this mainly preached the usage of SOLID principles and having the separation of concerns or responsibilities for each entity. Clean Architecture involves anything from the Presentation (the closest layer to the final user) to the Data layer (the closest to the Internet).

I always like to start from the deepest layer, and from this previous architecture, I decided to start from the data layer because elements can be kept in isolation in this way.

Data layer

Here is where any data request and response should come from, where the data is saved in memory or database for its post usage.

What we call Repository is the main manager driving the different Data Sources: both Network and Database ones (it could have any others like memory cache, disk, etc). Data sources work in isolation and each one doesn’t need to know about each other. But the repo will handle the results coming from any and getting back what is needed. It’s interesting to remind all artefacts are coded in Java, like I mentioned earlier.

Data Layer scheme

Business layer

Let’s say we need extra business logic or state handling, here is where interactors/use cases are responsible to rule this duty. If there is no extra logic, then they are just driving the data coming from the repo to the presentation layer. They don’t need to care about where it’s coming from (network or database), interactors just need an input to handle or being transformed into something useful.

Business Layer connects with Data Layer from the Use Case

Presentation layer or Model View Presenter layer (MVP)

I consider that the Presentation layer could be called itself Model View Presenter (MVP) and Clean Architecture is more from the Use Case to the Repository. However, this approach still considers the clear separation of concerns and this works just fine with the Presenter like the new collaborator to the Use Case. Where the Presenter is responsible to get anything manipulated by the Use Case and later on updating, showing or hiding those things on the View. In my case, I used Callbacks at the time (to learn more about removing Callbacks from the codebase, please read the newest article: “Synchronous communication with the UI using StateFlow”) to return results back from Presenters towards Activities or Fragments.

Presenter connects with the View to show updates to the User

And now this doesn’t stop here, I need to check something really important, I need to…

Check backwards compatibility requirements

RxJava is really the main tool I need to migrate easily. Do you wonder why?

Old & New need to co-exist together by the time being

Thinking in a kind of black-box mechanism where an entity expects input, and it processes it towards an output. We cannot really migrate a layer if we cannot make sure that we could use the output coming from the previous layer like the input of our current layer, and so on and so forth. It must be mandatory to have a transformer in between layers that make it backwards compatible.

Analysing pinpoints & connections

Starting to analyse from the deepest layer: the Data layer. All responses give back for a one single shot operation a Single (for Network data sources) or a stream: an Observable (Repository). Concluding this brief overview, RxJava 2 is the main foundation to be respected across layers, but now let’s see the steps to migrate I determined I needed.

RxJava and Java are in every single entity

A good baby step is to migrate the deepest data sources into Kotlin, but keeping RxJava throughout the codebase.

Data Sources using Kotlin + RxJava

In a nutshell, as we can see at the diagram above, both data sources would contain Kotlin and use partially suspend functions to finally return the output as the Repo expects in RxJava 2. This would actually work well and nothing would be broken.

We need to have the code base written in Kotlin already before moving away from RxJava 2 and start using Kotlin Coroutines.

Once this is a matter of fact, and Kotlin is fully used on the Repository, we can move on to the next step.

Repository built upon Kotlin, internally using Coroutines and returning Observable

The same equivalent step mentioned earlier and used on Data Sources is mandatory for the Repo, all functionality needs to be made in Kotlin, right after that suspend functions can be introduced as well. But Observable needs to be the output of the next black box (the Use Case) in the chain needs.

Use Case handles results from the Repository with Coroutines

Talking about the Use Case, once this layer is migrated to Kotlin and uses Kotlin Flows, we can get rid of the RxJava code from our Clean Architecture layers. The Repository can now return a Flow<T>, and this can now be interpreted properly by the Use Case.

Is all done for good?

I still have a mechanism in place to get the user’s query.

By using a ViewListener with the View’s query text listener and including a PublishSubject or BehaviourSubject whose query the ViewDelegate is able to introduce as soon as this is ready for the Presenter to use.

View callback injects view into ViewDelegate and this starts the Presenter by using the ViewListener

Like you can detect fairly quickly, this is again using RxJava and Java. We need to fully migrate this at the same time if we want to use it with Kotlin Flows.

Channels are available for us if we need a synchronous response

In this case, Channels help us to migrate it from RxJava instead of simple suspend functions or Flows, the main reason is we need a hot observable in this case.

By the way, I want to close the previous analysis by mentioning a hint about where is reasonable for me to include Kotlin Flows and their streams. In my case, I decided to use it in a few layers like Repository and Interactor or ViewDelegate, no need to use it when there is a clear one-shot operation (Network requests). Moreover, it could be included in the Database Data Source for purposes like a poll refresh of the data stored at the database, then we could make sure to use that stream and pass values back to the presentation layer once this UI refresh happens.

For the migration strategy, this is all I can tell from my side. Now let me very quickly tell a couple of benefits I found on following the previous analysed approach.

Advantages

We can be sure by re-using existent components and respecting the black box approach that there are no risks by taking advantage of our layers collaboration. The bad news is, we need serious decoupling between layers in order to make this happen. Yes, then it would be truly feasible.

This is all for the first part of this series, we have reviewed the following two sections of the presentation in this article:

  • Use case & Migration strategy

I hope you liked it so far. Whether you liked this article, clap and share it, please!

A big and special thanks to Cristina P. for the proof-reading of this article.

Cheers!

Raul Hernandez Lopez

GitHub | Twitter | Gists

(Update) The newest article talking about “Synchronous communication with the UI using StateFlow" (aka how to get rid of the Callbacks):

To follow up, please, continue with the next sections:

  • “Asynchronous communication: Streams & Basics”. Here I will try to explain the main differences between RxJava and Kotlin Coroutines concepts:
  • “Data layer Implementation”. How-to.
  • “Use Case layer Implementation”. How-to.
  • “View Delegate Implementation”. How-to.
  • “Lessons learned & Next steps”. The end chapter with some reflections and personal opinions as well as closing comparative notes.

Spoiler alert:

All this is based on my findings and my personal sample app under my own experience, yes, all opinions are my own.

This talk was given in its v1 (without StateFlow, read below for catching up) during #VirtualAndroidMakers (previously known as Android Makers and based in Paris, France) 2020.

Feedback of the Talk “Fueled Reactive apps with Asynchronous Flow”

If you want to have my presentation slides forehand, this is the link for v1:

However, I encourage you to skip the previous version and going directly to v2 (this is explained with further details into Synchronous communication with the UI using StateFlow") of those slides from the newest presentation using StateFlow.:

Finally, the #VirtualAndroidMakers video about the presentation:

#VirtualAndroidMakers presentation done by Raul Hernandez Lopez

--

--

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