Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement validation using ViewModel and Databinding?

What is the best approach to validate form data using ViewModel and Databinding?

I have a simple Sign-Up activity that links binding layout and ViewModel

class StartActivity : AppCompatActivity() {

    private lateinit var binding: StartActivityBinding
    private lateinit var viewModel: SignUpViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        viewModel = ViewModelProviders.of(this, SignUpViewModelFactory(AuthFirebase()))
                .get(SignUpViewModel::class.java);

        binding = DataBindingUtil.setContentView(this, R.layout.start_activity)
        binding.viewModel = viewModel;

        signUpButton.setOnClickListener {

        }
    }
}

ViewModel with 4 ObservableFields and signUp() method that should validate data before submitting data to the server.

class SignUpViewModel(val auth: Auth) : ViewModel() {
    val name: MutableLiveData<String> = MutableLiveData()
    val email: MutableLiveData<String> = MutableLiveData()
    val password: MutableLiveData<String> = MutableLiveData()
    val passwordConfirm: MutableLiveData<String> = MutableLiveData()

    fun signUp() {

        auth.createUser(email.value!!, password.value!!)
    }
}

I guess we can add four boolean ObservableFields for each input, and in signUp() we can check inputs and change state of boolean ObservableField that will produce an appearing error on layout

val isNameError: ObservableField<Boolean> = ObservableField()


fun signUp() {

        if (name.value == null || name.value!!.length < 2 ) {
            isNameError.set(true)
        }

        auth.createUser(email.value!!, password.value!!)
    }

But I am not sure that ViewModel should be responsible for validation and showing an error for a user and we will have boilerplate code

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>

        <import type="android.view.View" />

        <variable
            name="viewModel"
            type="com.maximdrobonoh.fitnessx.SignUpViewModel" />
    </data>

    <android.support.constraint.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/colorGreyDark"
        android:orientation="vertical"
        android:padding="24dp">

        <TextView
            android:id="@+id/appTitle"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginEnd="8dp"
            android:layout_marginStart="8dp"
            android:layout_marginTop="8dp"
            android:text="@string/app_title"
            android:textColor="@color/colorWhite"
            android:textSize="12sp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <LinearLayout
            android:id="@+id/screenTitle"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginEnd="8dp"
            android:layout_marginStart="8dp"
            android:orientation="horizontal"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/appTitle">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginEnd="4dp"
                android:text="@string/sign"
                android:textColor="@color/colorWhite"
                android:textSize="26sp"
                android:textStyle="bold" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/up"
                android:textColor="@color/colorWhite"
                android:textSize="26sp" />
        </LinearLayout>

        <LinearLayout
            android:id="@+id/form"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginEnd="8dp"
            android:layout_marginStart="8dp"
            android:layout_marginTop="24dp"
            android:orientation="vertical"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/screenTitle">

            <android.support.v7.widget.AppCompatEditText
                style="@style/SignUp.InputBox"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="@string/sign_up_name"
                android:inputType="textPersonName"
                android:text="@={viewModel.name}" />

            <android.support.v7.widget.AppCompatEditText
                style="@style/SignUp.InputBox"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="@string/sign_up_email"
                android:inputType="textEmailAddress"
                android:text="@={viewModel.email}"
               />

            <android.support.v7.widget.AppCompatEditText
                style="@style/SignUp.InputBox"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="@string/sign_up_password"
                android:inputType="textPassword"
                android:text="@={viewModel.password}" />

            <android.support.v7.widget.AppCompatEditText
                style="@style/SignUp.InputBox"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:hint="@string/sign_up_confirm_password"
                android:inputType="textPassword"
                android:text="@={viewModel.passwordConfirm}" />

            <Button
                android:id="@+id/signUpButton"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="16dp"
                android:background="@drawable/button_gradient"
                android:text="@string/sign_up_next_btn"
                android:textAllCaps="true"
                android:textColor="@color/colorBlack" />

        </LinearLayout>

    </android.support.constraint.ConstraintLayout>
</layout>
like image 765
Maxim Drobonog Avatar asked Sep 17 '18 15:09

Maxim Drobonog


People also ask

What is the role of the ViewModel in the data binding?

Using ViewModel components with the Data Binding Library allows you to move UI logic out of the layouts and into the components, which are easier to test. The Data Binding Library ensures that the views are bound and unbound from the data source when needed.

What is data binding in MVVM?

Data Binding allows you to effortlessly communicate across views and data sources. This pattern is important for many Android designs, including model view ViewModel (MVVM), which is currently one of the most common Android architecture patterns.

How do I verify my email with Kotlin?

In this post, we will learn how to do email validation in Android using Kotlin. We will create one function that will check and validate different types of email addresses. We have two different ways to do a email validation : By using a regular expressionn or regex and by using Android's utility class.


1 Answers

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.

like image 184
Khemraj Sharma Avatar answered Oct 02 '22 06:10

Khemraj Sharma