How to implement validation using ViewModel and Databinding? How to implement validation using ViewModel and Databinding? android android

How to implement validation using ViewModel and Databinding?


There can be many ways to implement this. I am telling you two solutions, both works well, you can use which you find suitable for you.

I use extends BaseObservable because I find that easy than converting all fields to Observers. You can use ObservableFields too.

Solution 1 (Using custom BindingAdapter)

In xml

<variable    name="model"    type="sample.data.Model"/><EditText    passwordValidator="@{model.password}"    android:layout_width="match_parent"    android:layout_height="wrap_content"    android:text="@={model.password}"/>

Model.java

public class Model extends BaseObservable {    private String password;    @Bindable    public String getPassword() {        return password;    }    public void setPassword(String password) {        this.password = password;        notifyPropertyChanged(BR.password);    }}

DataBindingAdapter.java

public class DataBindingAdapter {    @BindingAdapter("passwordValidator")    public static void passwordValidator(EditText editText, String password) {        // ignore infinite loops        int minimumLength = 5;        if (TextUtils.isEmpty(password)) {            editText.setError(null);            return;        }        if (editText.getText().toString().length() < minimumLength) {            editText.setError("Password must be minimum " + minimumLength + " length");        } else editText.setError(null);    }}

Solution 2 (Using custom afterTextChanged)

In xml

<variable    name="model"    type="com.innovanathinklabs.sample.data.Model"/><variable    name="handler"    type="sample.activities.MainActivityHandler"/><EditText    android:id="@+id/etPassword"    android:layout_width="match_parent"    android:layout_height="wrap_content"    android:afterTextChanged="@{(edible)->handler.passwordValidator(edible)}"    android:text="@={model.password}"/>

MainActivityHandler.java

public class MainActivityHandler {    ActivityMainBinding binding;    public void setBinding(ActivityMainBinding binding) {        this.binding = binding;    }    public void passwordValidator(Editable editable) {        if (binding.etPassword == null) return;        int minimumLength = 5;        if (!TextUtils.isEmpty(editable.toString()) && editable.length() < minimumLength) {            binding.etPassword.setError("Password must be minimum " + minimumLength + " length");        } else {            binding.etPassword.setError(null);        }    }}

MainActivity.java

public class MainActivity extends AppCompatActivity {    ActivityMainBinding binding;    @Override    protected void onCreate(@Nullable Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        binding = DataBindingUtil.setContentView(this, R.layout.activity_main);        binding.setModel(new Model());        MainActivityHandler handler = new MainActivityHandler();        handler.setBinding(binding);        binding.setHandler(handler);    }}

Update

You can also replace

android:afterTextChanged="@{(edible)->handler.passwordValidator(edible)}"

with

android:afterTextChanged="@{handler::passwordValidator}"

Because parameter are same of android:afterTextChanged and passwordValidator.


This approach uses TextInputLayouts, a custom binding adapter, and creates an enum for form errors. The result I think reads nicely in the xml, and keeps all validation logic inside the ViewModel.

The ViewModel:

class SignUpViewModel() : ViewModel() {   val name: MutableLiveData<String> = MutableLiveData()   // the rest of your fields as normal   val formErrors = ObservableArrayList<FormErrors>()   fun isFormValid(): Boolean {      formErrors.clear()      if (name.value?.isNullOrEmpty()) {          formErrors.add(FormErrors.MISSING_NAME)      }      // all the other validation you require      return formErrors.isEmpty()   }   fun signUp() {      auth.createUser(email.value!!, password.value!!)   }   enum class FormErrors {      MISSING_NAME,      INVALID_EMAIL,      INVALID_PASSWORD,      PASSWORDS_NOT_MATCHING,   }}

The BindingAdapter:

@BindingAdapter("app:errorText")fun setErrorMessage(view: TextInputLayout, errorMessage: String) {    view.error = errorMessage}

The XML:

<layout>  <data>        <import type="com.example.SignUpViewModel.FormErrors" />        <variable            name="viewModel"            type="com.example.SignUpViewModel" />  </data><!-- The rest of your layout file etc. -->       <com.google.android.material.textfield.TextInputLayout            android:id="@+id/text_input_name"            android:layout_width="match_parent"            android:layout_height="wrap_content"            app:errorText='@{viewModel.formErrors.contains(FormErrors.MISSING_NAME) ? "Required" : ""}'>            <com.google.android.material.textfield.TextInputEditText                android:layout_width="match_parent"                android:layout_height="wrap_content"                android:hint="Name"                android:text="@={viewModel.name}"/>        </com.google.android.material.textfield.TextInputLayout><!-- Any other fields as above format -->

And then, the ViewModel can be called from activity/fragment as below:

class YourActivity: AppCompatActivity() {   val viewModel: SignUpViewModel  // rest of class  fun onFormSubmit() {     if (viewModel.isFormValid()) {        viewModel.signUp()        // the rest of your logic to proceed to next screen etc.     }     // no need for else block if form invalid, as ViewModel, Observables     // and databinding will take care of the UI  }}


I've written a library for validating bindable fields of an Observable object.

Setup your Observable model:

class RegisterUser:BaseObservable(){@Bindablevar name:String?=""    set(value) {        field = value        notifyPropertyChanged(BR.name)    }@Bindablevar email:String?=""    set(value) {        field = value        notifyPropertyChanged(BR.email)    }

}

Instantiate and add rules

class RegisterViewModel : ViewModel() {var user:LiveData<RegisterUser> = MutableLiveData<RegisterUser>().also {    it.value = RegisterUser()}var validator = ObservableValidator(user.value!!, BR::class.java).apply {    addRule("name", ValidationFlags.FIELD_REQUIRED, "Enter your name")    addRule("email", ValidationFlags.FIELD_REQUIRED, "Enter your email")    addRule("email", ValidationFlags.FIELD_EMAIL, "Enter a valid email")    addRule("age", ValidationFlags.FIELD_REQUIRED, "Enter your age (Underage or too old?)")    addRule("age", ValidationFlags.FIELD_MIN, "You can't be underage!", limit = 18)    addRule("age", ValidationFlags.FIELD_MAX, "You sure you're still alive?", limit = 100)    addRule("password", ValidationFlags.FIELD_REQUIRED, "Enter your password")    addRule("passwordConfirmation", ValidationFlags.FIELD_REQUIRED, "Enter password confirmation")    addRule("passwordConfirmation", ValidationFlags.FIELD_MATCH, "Passwords don't match", "password")}

}

And setup your xml file:

<com.google.android.material.textfield.TextInputLayoutstyle="@style/textFieldOutlined"error='@{viewModel.validator.getValidation("email")}'android:layout_width="match_parent"android:layout_height="wrap_content"><com.google.android.material.textfield.TextInputEditText    android:id="@+id/email"    style="@style/myEditText"    android:layout_width="match_parent"    android:layout_height="wrap_content"    android:hint="Your email"    android:imeOptions="actionNext"    android:inputType="textEmailAddress"    android:text="@={viewModel.user.email}" />