on
An overview of reactive programming with Android
The Android platform is asynchronous by nature. And so are android apps. Information is flowing asynchronously through your app, from multiple sources. The system will trigger your broadcast receivers, forward intents, and recreate your UI on each configuration change. The user will keep interacting with the UI, and network requests may respond (or eventually fail) at any moment.
Tackling such issues in an imperative programming way will make you write code to coordinate and handle all these interruptions while maintaining a consistent state. If you build android apps, chances are you’re suffering from listeners, callbacks, threads and state management. And when I say suffering I really mean it:
What’s Android reactive programming?
Few years ago, Google introduced Architecture Components, especially view model and live data. You are probably already using these components, and if not then you should. Anyway, in a nutshell, a view model helps you survive activities and fragments lifecycles, and live data will make your UI spontaneously react to data change. In fact, live data follows -in a primitive way- a reactive programming paradigm.
Reactive programming is a paradigm in which data change propagates through data streams. Building an app will mainly consist of wiring these data streams using what we call “operators”. The real power of such operators is the ability to alter the data. For instance, you can filter, map, merge, split, flatten or chain your data streams, this is even the way you will code your business rules in a reactive app. Plus, it’s usually very readable.
How-to: code sample
Let’s suppose you are building a Twitter-like app and are about to code the follow button in profile details screen.
Since every user interaction is seen as a “UI event”, the follow button will just fire a ProfileUiEvent.Follow
and let the magic happen.
Let’s go through a step-by-step implementation
In ProfileActivity
we bind clicks on follow button with the view model main entry (each button click will fire a ProfileUiEvent.Follow
)
Thus, all button clicks will go through the view model main entry (eventsStream).
-
We filter that very view model main entry to branch a stream with only Follow events, we map each event to Follow Request (making use of the latest user id), and then we make it pass through the
followUserInteractor
black box. The resultingfollowUserStream
variable is a stream that will fire one (or many) of the three events:InFlight
,Success
orError
.val followUserStream = eventsStream .ofType() .withLatestFrom(uiContentStream) .map { (_, uiContent) -> FollowRequest(uiContent.userId) } .compose(followUserInteractor)
Sneak peek into that black box: for each Follow Request in, you get an immediate InFlight out and then Success or Error a while later.
-
The stream we described before (
followUserStream
) will be useful to build the UI state stream. For simplicity, we are here building the UI state stream from a merge of all streams. Thus, whenever there is a failure somewhere, the state stream will reflect it. You can also combine streams in a more custom way using more sophisticated combination operators.val uiStateStream = Flowable .merge( userDetailsStream.mapUserState(), followUserStream.mapFollowState() )
-
The two final streams (
uiStateStream
&uiStateStream
) are transformed intoLiveDatas
that will be exposed to the activity (uiState & uiContent)val uiState: LiveData val uiContent: LiveData ... uiState = fromPublisher(uiStateStream) uiContent = fromPublisher(uiContentStream)
-
We make
ProfileActivity
observe and reflect the liveDatas.viewModel.uiState.observe(this, ::showState) viewModel.uiContent.observe(this, ::showContent)
Takeaways
- Your view model is an initializer of your data streams from the UI and back again;
- We extend a
ReactiveViewModel<T>
which is a view model with a main entry stream (eventStream: Flowable<T>
) for all incoming UI events; - At the view model constructor, we create a stream (branched from
eventStream
) for each UI event:userDetailsStream
andfollowUserStream
; - We make it go through the corresponding use case (compose operator);
- Each business use case (you can see it as in a use case diagram) has a corresponding interactor;
- An interactor is just a flowable transformer with some code to call and coordinate repositories (business logic);
- The results of composing with interactors are combined together to form the output liveDatas.
- The Activity observes and reflects the liveDatas.
Do I need all of that?
Clearly, there are a lot of notions to get familiar with before starting to build reactive android apps. That’s one of the main reasons developers and companies just give up doing so. It’s actually an effective cost to deal with when you consider building a reactive app. But, is it really worth it?
It’s a long-standing debate inside the Android engineering team here @ Fabernovel. We usually list pros & cons for each new project before making such decision. If the app we’re about to build depends on data that changes very frequently and from different sources, we tend to prefer a reactive architecture, otherwise, we see it more like an unnecessary over-engineering. Here’s a list of main pros & cons of reactive programming compared to imperative programming.
Pros
- No states to maintain means less inconsistency bugs
- Less intermediate states boilerplate means better readability & maintainability
- Some features are just out-of-the-box, like updating the UI with the latest data, thread management, etc.
- No callback chaining hell
Cons
- Harder debugging (you can’t just breakpoint anywhere)
- Some problems are not always very intuitive to solve in a reactive way (a bit restrictive)
- Your team should be familiar with RxJava and reactive programming paradigm.
As an alternative, one can consider building a coroutine-based architecture. Lately, we started using coroutines in production @ Fabernovel, substituting RxJava, and the feedback is quite positive. Stay tuned for an overview of a coroutine-based architecture…
By then, happy coding :-)