Android — Form input and validation using MVVM with DataBinding

A simple login form using MVVM with 2-way data-binding (all using Jetpack)

There is a lot of information on the internet on how to do validations and form in Android using MVVM. I’ve however found a lot of these example don’t quite cover the feature set I would like, so I decided to create the example(s) I was seeking.

TL;DR

The issue with incomplete examples is that they result in a lot of developers defaulting to what is familiar and this results in them making decisions that violate the spirit of MVVM. When using the MVVM pattern, if I find myself holding a reference to Activity or Fragment in my ViewModel, I re-evaluate my approach. A reference to Activity or Fragment to get Context and callbacks for actions are two examples where developers often struggle. Personally the Model and ViewModel should be pure objects and have no dependencies on Fragment/Activity or Context of any nature. Firstly, doing this violates MVVM where the ViewModel should have no reference to View, instead the View about be updated through most of an event/observer pattern. Secondly, we want our code to be as testable as possible using unit testing and pure objects are just easier to test and if you hold a reference to Context in any way, this will mean having to mock the Context object for any functionality you want to use.

Disclaimer: I’m not saying that there is *no* scenario where you’ll be able to solve all your problems without a reference to a context or a callback, I’m just saying that you need to be very sure that this is the only way.

So what are we building? — Requirements:

We are going to build a simple login screen with the following features:

  • The page will have a Email and Password field as well as a Login button.
  • Fields should validate if they lose focus only *after* they have been modified. This means that if a field is empty, I click on it and click away, I do not want to validate the field.
  • If the fields input is too short or the format is incorrect, I want to show a field level error.
  • If both the Email and Password fields have valid input in them, I want to enable the Login button. The Login button is disabled by default.

Show me the code

With MVVM there are 2 main approaches you can take.

  • Method1 — A model Object is used to represent the form and it’s fields. The state of the form is maintained by the ViewModel and it contains methods for validating input.
  • Method2 — The input fields, the error messages and, the form each have their own model. The Form model contains all the logic for form validation and the ViewModel only exposes functionality to the View.

Which method is better? Honestly, when I started down this process, I was leaning towards method1. After all, code for method1 is more concise. However, as I worked on method2 I grew fonder of it. The main reason for this is that I found that it was easier to write unit tests for method2 and provide greater code coverage for the code using method2. The operative word here is that I felt it was easier which can be very subjective. Since each of the fields on the form, error messages and the form itself had it’s own model. I could just test the model to test the forms behaviour without having to test the ViewModel. The ViewModel was purely exposing functionality to the View which I felt was better. Now, that is not to say that you don’t need to test the ViewModel, just that I can test the ViewModel and it’s behaviour separate from that of the Form and it’s functionality. All that being said, method2 has a lot more code than method1 and so will take a bit longer to do. That being said, using Kotlin could reduce the extra code and reduce the time difference to a point where it may not matter (thanks to Kotlin Data Classes).

The repo is linked below. Take a look at branches method1 and method2 :

Some of the code explained

I feel like there are parts of the code where people may have some questions. I’ve done my best to address some of the concerns here.

Why do some of the model objects extend BaseObervable while others do not?

We are using 2-way data-binding on this project. This is effectively donated with the annotation @={} in the layout. What this means is that when the fields value is changed, the underlying model object is also changed. When we don’t use 2-way data-binding, the View only displays and does not update the model. In our layout, this is pretty much every macro that does not start with @={} . As an optimisation, when a field is not changed, the UI does not render it again. The 2-way data-binding means that when the Email is changed, the UI for the email field is re-rendered because the value is updated, however, since our model goes ahead and updates the error states as well, the model and the View do not know that other fields have also changed, hence the changes are not visible. On order to fix this issue, we tell our api what has changed with something like notifyPropertyChanged(BR.valid) where BR is a generated file that contains all the bindings. This method is only available if an object extends BaseObservable. BR only contains variables defined in the layout XML or annotated with @Bindable.

How do I deal with Strings in my model when context is not available?

There may be a temptation to deal with this by creating an Application singleton. However, I would resist this urge. The best way to deal with this is to just use the resource ID’s. The data-binding will accept the resource id’s and just work, in places where you have to explicitly handle it yourself, this isn’t all that hard, just take a look at when we do for setError() :

@BindingAdapter("error")
public static void setError(EditText editText, Object strOrResId) {
if (strOrResId instanceof Integer) {
editText.setError(
editText.getContext()
.getString((Integer) strOrResId));
} else {
editText.setError((String) strOrResId);
}
}

That code works whether you call setError() with a String or a String resource ID.

The focus change listener code explained.

There is nothing complex here, but some people might understandable not understand why I chose to do this the way I have. The issue is that in order to bind the focus change listener, I need to get a reference to the View. In a ViewModel you should not have a reference to the View. For Android, this means that having access to Context in any form, whether as an Activity or Fragment is not a good idea. Either a reference to either, how do we get the View? There are some ways around it, but they at the very least require you to know the ID of your View. This is problematic, because it means your ViewModel can’t be used with any other View. In the current setup the ViewModel only exposes the functionality of the OnFocusChangeListener and it’s up to the View to bind to it. Again, this is the purpose of the ViewModel, to expose functionality to the View. Basically, we don’t care what the View is, as long as it uses the ViewModel, the app keeps working.

Why does the View go through the ViewModel to get to the Model?

It’s true that you could simply declare an other variable in the View and use a model object directly, pass that model object to the ViewModel to access functionality as needed. This does generally violate the idea that a model is your logic and a ViewModel is your layer for exposing functionality. This further means that the ViewModel can be passed in objects that it’s not meant to handle or that ViewModel is not aware of the functionality it is providing to the View. This is a problem from a encapsulation and separation of concerns point of View. As long as the model being used by the View is being exposed by the ViewModel, the ViewModel and Model can be changed with predictable results. When the user can specify their own model objects in the View and pass them to the ViewModel to achieve some action, there is no guarantee that the outcome is predictable.

Why is buttonClick an instance of MutableLiveData and not Observable?

It’s true that it is possible to make this code work with buttonClick being an ObervableInt or an ObservableBoolean or even an ObservableField. There are a few reasons I chose the LiveData approach. Firstly, I was going to bind to the field in the Activity so it is nice to have it be Lifecycle Aware. So, I don’t have to worry about passing data back to my activity if an activities had been opened to “Pay with PayPal” for example. When I set data in LiveData, it will be delivered to the observer (my activity) when it resumes.

Secondly, look at the code for Observable vs. MutableLiveData:

viewModel.getLoginFields().observe(this, new Observer<LoginFields>() {
@Override
public void onChanged(LoginFields loginModel) {

}
});
viewModel.getLoginFields().addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback() {
@Override
public void onPropertyChanged(Observable sender, int propertyId) {

}
});

As you can see, there is no way to use the addOnPropertyChangedCallback without explicitly fetching the data from the ViewModel. Where as in the case of LiveData, the changed data is delivered right to me. Also, the “Sender” has to be an Observable which in this example is not a problem, however, your model may not be an instance of Observable.

When do I use LiveData vs Observable?

As a rule of thumb, if your data is being delivered to a lifecycle aware component, consider using LiveData. When you want your View to be aware of changes to a field, use Observable (take a look at the method1:LoginFields.java as an example of this. If the fields are not Observable the error messages will not disable automatically).

Update 11-Sept-2018: @BindingAdapters annotated static methods only need to be defined once

It’s come to my attention that methods such as setError() and bindFocusChange() are being defined again and again by people in each ViewModel they create and want to use this functionality.

This is not necessary.

I put these methods in the ViewModel to keep all the code in one place. When a static method is annotated with @BindingAdapter that functionality is available to all Views through data-binding. If you define the same again and again, the duplicate methods will be ignored (if your binding app:onFocus or app:error is the same). Ideally these methods should probably be moved out to their own class such as a CustomViewBindings so that this is not confusing for readers.

Finally

If you’re still struggling between method1 or method2, think of it as a stylistic choice. I can easily come up with a third version that takes things I like from both methods to make a 3rd way of doing things. However, that is not to say that the 3rd method would be perfect or even better than the first two. The wonderful thing about the MVVM patten is that if you decide to use method1 and realise it’s not for you, you can change to method2 with relative ease. One thing is for sure in order to build great Android apps, read more of my articles.


Yay! you made it to the end! We should hang out! feel free to follow me on Medium, LinkedIn, Google+ or Twitter.