React to UI events in a non-reactive way
From the early days of Android development, we have used different Architecture Patterns to help us build scalable, maintainable and testable code.
And as we have worked on different projects we potentially have utilized one or another way to communicate UI events to ViewModels or Presenters just so they resolve into actions performed by our domain/business layer.
Chances are that our project runs heavily on RxJava and our way of bridging this communication is by using RxBinding. Probably, what comes first to our mind is how fun is to deal with disposables and complicated reactive chains.
Or maybe, we are migrating to Coroutines and our gut tells us that Flow might be a good replacement for RxBinding, but we are still not sure if we should use just Flow, CallbackFlow, Stateflow, SharedFlow, Channels… hello? Or we simply just call functions directly in our ViewModel or Presenter… since let’s face it, our classes are always small and neat, right? Bazinga! 🤓
What if I tell you that there is a simple, yet powerful alternative of communication between your View and your ViewModel or Presenter?
The current ways
Let’s first take a look at how it might look like a traditional implementation of a reactive MVP, in order to visualize one of the many but common ways to communicate UI events, in this case, to a Presenter.
Since one of the most common ways of working on MVP is through contracts, let’s then start by defining our View and Presenter interfaces. Usually, the View interface denotes the responsibilities of a concrete View and so does the Presenter interface towards a concrete Presenter.
In our example, let’s notice how our View interface, apart from stating responsibilities, also provides two Observable streams seemingly related to some UI events.
Now, in our View, we use the Presenter reference to call actions in our Presenter. However, notice how with the help of RxBinding, we are wrapping some UI events into the Observable streams we had defined previously.
In our Presenter, we subscribe to the Observable streams we get from the View interface. Whenever there is a new UI event, we react to it and execute the respective action.
One of the goals of this implementation is to allow us to apply a throttling mechanism to our UI events just so we don’t spam our business logic with duplicated or wrong actions that could have a negative impact on our system and potentially, the user experience.
Now, you might be wondering, what are the caveats of this approach? Well, let’s do a quick summary based on what we have seen so far.
- The View to Presenter communication seems quite complex as we need to wrap UI events in a stream of events just for the sake of throttling.
- The View interface is now polluted with providers of this stream of events.
- Dependency of RxJava in the concrete View.
- As part of the waste of these reactive operations, we need to take care of the disposables. Probably not a problem if your project is mostly reactive, however, it’s still cumbersome having to write all of this code just to receive a UI event.
A new hope
Is there a better way, then? Well, let’s call the ViewIntentCallback to the stand!
The ViewIntentCallback is one of the results of our efforts at Free Now to migrate from RxJava to Coroutines. We wanted a solution that allows us to communicate UI events from our Views to our Presenters without the need for any external frameworks or a complex stream of events. We wanted something simple, yet powerful that still allows us to throttle UI events without the risks of having to handle the waste from reactive operations.
Now, let’s take a look at how the ViewIntentCallback can be applied to our Reactive MVP example.
We start by adding to our View interface a
sealed interface Intent : ViewIntent, this interface provides us with a structured group of references that represent the intentions or needs our View can send to our Presenter. You can notice how the Presenter interface is now empty, and it makes sense since now we are stating all the responsibilities of our Presenter with the Intent sealed interface.
Now, in our View, we have at our disposition a
sender reference which is bound to the intentions we have defined previously. With this reference, whenever a UI component triggers an event, we are now able to call a
send function passing as a parameter the intention we want our Presenter to receive and the throttling mechanism we want to apply to this operation if any.
Finally, in our Presenter, we connect everything thanks to the
onViewIntent property, this
operator fun should be invoked in our
init block just so we pass to it the expected lambda reference to be used by our
sender reference in our View, in this case, the lambda reference is the receive function.
So, whenever an intent is sent from the View the
receive function will “magically” catch it and then with the help of an idiomatic and exhaustive when conditional we execute the respective action. How cool is that?
How does the ViewIntentCallback work under the hood?
All looks simple, shiny and kind of magical, but let’s take a quick look at the internals to understand a bit more about this alternative way of communication.
The ViewIntentCallback approach is built upon the following core components:
ViewIntentinterface helps us constraint the whole approach to a specific type, that way we make sure we are only sending and receiving the view intentions we define in our features.
ViewIntentBindertype alias is just a convenient name for the lambda that we need to pass to our receiver function, the sender function ultimately uses this lambda to emit our intentions.
Senderinterface provides us with the
sendfunction which will allow us to pass an intention and apply a throttling mechanism if needed.
Receiveinterface provides us with the operator fun that receives the lambda (aka
ViewIntentBinder) whenever our Presenter initializes.
Now, in our orchestrator class, we implement our sender, receiver and throttling abstractions just so we can build up the behaviour we want to achieve.
Notice how we use the
_send backing property to hold the receive lambda reference we get in the operator function from the Presenter. Here is where the magic happens, where everything connects! 🪄
Therefore, whenever we call the
send function and based on the throttling type we ultimately invoke our
_send property with the intention we want our Presenter to receive. Easy, right?
Finally, to almost wrap things up, you surely want to know how throttling works, right? Well, similarly to the sender and receiver interfaces we have a throttling interface bound to our core
ViewIntent, this interface provides us with the supported throttling operations, as well as, an enum class for filtering.
Now, in our throttling orchestrator class, we hold lazy references to the
ThrottleLast instances just so they are initialised when needed. And that’s it! 💪
… Ok, but how
ThrottleLast work? 🧐 Well, for that I invite you to take a look at the sample project in the reference section 😆.
To wrap things up, this time for real, we have learnt how the ViewIntentCallback can help you communicate UI events from your View to your Presenter in a simple, powerful and idiomatic way. But also, how the ViewIntentCallback allows you to apply a throttling mechanism without the risks of handling reactive operations or having external framework dependencies in your View.
And this is not all! The ViewIntentCallback is compatible not only with MVP-based projects but also with MVVM and Jetpack Compose-based projects 🙀. To find out more, take a look at the example project in the reference section.