Model-View-Intent & Data Binding
Recently, thanks to joining the new project, I started using Model-View-Intent pattern. I always try to simplify development process and reduce boilerplate code, therefore I decided to dive deeper into MVI and discover new horizons. This approach is highly testable and has reactive nature — I consider that as big advantages.
Since I already heavily use data binding in my projects, I’d like to take advantage of MVI and have view state rendered in just two lines of code.
As usual, I will show several concepts on a simple example. In this case it will be login screen.
How does standard login screen looks like? It’s simple form where user inputs email, password, there is button which is clickable or not — we will change button status depending on input. We may additionally want to run some frontend-side validation for edit fields. I believe such case was described million times, however we will take a closer look at Model-View-Intent approach and try to reduce boilerplate with data binding.
In this article I will be using Mosby MVI library:
Show me the code!
Describe it with ViewState
In MVI we describe ViewState by one data class. In this case it looks like this:
As I used to do with MVP, I wrote contract interface for login screen — I like to keep basic relations in one place:
As you can see, I also defined Interactor (some developers would name it UseCase) — I decided to put it in contract for clarity, because it emits partial view changes, so it’s bound directly to the login flow.
Now, the biggest part of login screen implementation process will be creating new ViewStates.
Partial view changes
When you are prototyping screen with MVI, you should ask yourself — which user or backend changes affects this view state? When you start thinking like that, MVI will be your good friend during whole development process.
Additionally, when you create screen with data binding, you may want to keep your view state model as class with primitives.
PartialViewChange is a sealed class which is responsible for changing parts of view state. It takes processed change, such as email edit field content and creates new view state, based on previous state. Smooth syntax is possible due to Kotlin data classes copy()
method with named arguments.
I also decided to put abstract reduce()
in PartialChanges class. From my point of view, it’s this class responsibility to create new state. With this approach you can also avoid huge when
statements in presenter.
Enabling or disabling submit button can be triggered by additional validation on email and password — however in this example simple checking if they are not empty should be enough.
Our presenter binds intents from view — it subscribes to edit text changes and button clicks.
Submit intent is mapped to business logic method, where API calls are performed. At the end of the method, I’m using scan operator on the merged observable.
Scan operator takes the actual and previous item and applies function to them. It gives us possibility to create new state based on previous.
After that I’m reducing changes (with LoginPartialState method) to new view state.
Finally — the view
As we have logic for login screen prepared, we can implement our View:
You can use abstract MviActivity<View, Presenter> from Mosby library — it takes care of presenter flow.
I’m mapping both email and password edit fields to specific partial states. RxBindings by Jake Wharton are very handy because of reactive nature of MVI. You can additionally use some kind of extension function for EditText changes:
Let’s take a look at our layout file. It has two input fields, submit button, loading view and some container for error message. Now we put <data>
into layout tags and try to set properties in xml:
If button should be enabled, we set android:enabled=true on this button. If we should show progress bar, we toggle visibility on progress bar view. And finally, if we should display error — we set text to specific text container.
If you want to use View.VISIBLE property with data binding, you must import it’s type as described in gist.
Back to ViewState
Each property is immutable and has default value. What could be done better here? Sometimes we want our error message to be in Snackbar or custom dialog. We won’t achieve that in a simple way with data binding, we will have to implement it in View class.
My favorite part of this approach is toggling loading state. In MVP I was invoking showProgress()
and hideProgress()
methods very often. Here I just change loading property to true, and binding in xml does the rest.
Another thing that could be improved is observing edit texts state. I’d like to have some kind of custom binding adapter which will propagate changes to the presenter. But that would be more MVVM way, not MVI. Yet, RxBinding library and set of Kotlin extensions seems to do the job and doesn’t look horrible in the code.
Conclusion
I showed you my approach to designing screens. I know, that that’s not the smoothest, cleanest solution possible.
There are cases where MVI is the best tool to get things done and with data binding it will be even better. I don’t use MVI in every screen, and I don’t use data binding if it’s not suitable. Always choose the right tool for the job.
I believe you found something interesting in this article, and you are ready to try something new while developing new feature to your app!
All examples used in this article can be found on Github: