By Abhishek Jangra
GoTransit is our product which makes multimodal trips effortless for our customers. A quick read about GoTransit, here.
This blog will take you through an architecture component we designed while building GoTransit on Android. This component enables us to decouple the view from the presenter, with added benefits of better readability, scaling and testing.
Beauty in design AND code
To ensure our code is as beautiful as our design, one of the pillars we set up for our architecture was to automate redundant technical setup whenever a developer was creating a new screen in the app, which was intended to save time. So, a good architecture is a long term (and constantly evolving) investment which makes product developers’ life better so they can focus on making customers’ life better.
Automating solutions to boring problems becomes a very interesting problem to solve.
While working on the architecture of the bigger Transport product, we decided to treat the changes on view as events. All the clicks, animations are modelled as events and we expose a stream (internally an RxJava PublishSubject) of these events which can be listened to by the presenter to know about the changes happening in the view.
As you can see in the image above, the events are stored as a sealed class. Based on the listeners set, if the user makes an interaction, instead of worrying about how to supply this to the presenter, view will pass the corresponding event in a stream. This way the view becomes completely independent.
Another cool thing: Due to the sealed class of events, one could look at all the possible user interaction at a glance, instead of looking at the code and trying to find click listeners, scroll listeners etc., which is very painful.
But there were some problems…
In the video above, you can see the issue which has been bugging us since the dawn of humanity. The user could click multiple buttons and then two completely different things start happening on the screen. 🤦♂
As you can see in the screenshot above, every time a view is created, a stream has to be setup before defining the events. After that, each time there is an event, you need to do
stream.onNext(event). It’s just not something which is very readable and easy to work with, especially when there’s no context on the codebase 😓
Let’s tackle Problem 1 first as that directly stops us from scaling the UX.
One solution used in a bunch of places is debouncing, where clicklisteners only trigger once in a set time frame.
If a user goes hulk mode on one button, it’ll still trigger the click listener only once.
But a user can still quickly press two different buttons placed side by side and trigger chaos. We needed something better, and this led us from debouncing to throttling. Treating click events as a stream of user interactions where only one is allowed in a time frame.
And we solved it. 🖖
We created a child sealed class for click events and moved some of the events there. Set up a new stream, everything coming is this stream is throlled by a defined time window (~500ms) and then pushed into the main view events stream. Now ONLY 1 click event will be addressed at a time. Other events like scrolling which need instant response can be sent directly into the view events stream.
But this wasn’t a moment to celebrate. 🙅♂
Remember Problem 2? While solving Problem 1, we added fuel to the fire and made the situation even worse.
From the presenter side, it is still cool as it just needs to subscribe to one stream of events. But on the view:
- Every time a developer creates a screen, they need to setup both the view events and click events stream with throttling
- Also, one needs to remember to send the event to click events stream if it’s a click event
And now, we embarked on the journey to solve Problem 2
We knew the basic idea. We needed some kind of an interface which will give any View the abilities to setup view events automatically.
So we created a new component
ViewEventsDistributor and it works based on kotlin delegates.
I’ll cut to the chase and show you the code directly 👇
But this is the real treat, the new way for a view to setup view events
Yep, that’s about it! 🙌
The View will get the
viewEvents() method which the presenter can use to subscribe to the events.
And how do you send an event? using
This is so much better and readable, someone new could easily understand it and jump right into the source to know more about it.
The developer working on a view does not need to worry about the setup, and could instead focus on making the customer’s life better.
And this is how we solved the riddle of view to presenter communication in GoTransit. Testing the presenter also becomes super easy. Just provide a mock view and fire event from the view to test how the presenter behaves.
If you’re planning to use some of this in your codebase, here’s a virtual high five 🖐
Click here to read more blogs from our vault.
Also, we’re hiring! Click below to view open job positions.