Databinding in Android using Kotlin

December 3, 2020

If you have ever wondered if there is a way to link the UI directly to the data source? You are in the right place. Repeating the dreaded findViewbyID statement in your code can be tedious.

It takes so much time that one may end up forgetting variables’ IDs. Well, there is a solution. That solution is data binding. We will learn how to implement this concept in this tutorial.

Introduction

Without going any further, it’s essential to understand what data binding is and it’s advantages. The data binding library is part of Android Jetpack utilities.

According to the Android developer documentation, the data binding library allows users to bind layouts and UI components to data sources declaratively.

The data binding library seeks to eliminate something like this:

findViewById(R.id.name).apply {
text = viewModel.name
}

By introducing this.

<TextView
     android:text="@{viewmodel.name}"/>

The goal of the tutorial

In this project, we will create a simple Notes app in Android using Kotlin. As you guessed, the app will make use of the data binding library.

At the end of the tutorial, your application should be similar to the one shown in the video below.

Prerequisites

This tutorial is intended for those who have some experience in Android programming using Kotlin. You can download the full source code here.

1. Installing the required dependencies

Launch Android studio and create a new project. Once the project is ready, go to the Gradle scripts folder and open build.gradle (module: app).

Add buildFeatures and set databinding to true. This notifies the Android system that our app uses data binding. Therefore, the proper files will be generated.

buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
        buildFeatures {
            dataBinding true
        }
    }
}

In the same build.gradle file, add the lifecycle library to your dependencies. This library helps connect the UI to a ViewModel and LiveData.

Read more about MVVM architecture here.

dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])
    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    implementation 'androidx.core:core-ktx:1.3.2'
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
    implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
    implementation 'androidx.recyclerview:recyclerview:1.1.0'
    implementation 'androidx.cardview:cardview:1.0.0'
    implementation 'com.google.android.material:material:1.2.0'
}

Remember to add apply plugin: kotlin-kapt at the top of the file.

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'

2. Model

In your primary folder, create a new package and name it model. Then create a Note.kt file in this package.

As shown, the data class NoteItem only holds two variables (title and description). These values are required to initialize the class.

data class NoteItem(
    var title: String,
    var description: String
)

3. ViewModel

The ViewModel makes it easy to update data changes on the UI.

Create a package named viewmodels in your main folder.

Then create a new file and name it MainViewModel.kt. The file will contain the following variables.

    val isStringEmpty = MutableLiveData<Boolean>()
    @Bindable
    val inputTitle = MutableLiveData<String>()
    @Bindable
    val inputDescription = MutableLiveData<String>()
    val list = MutableLiveData<ArrayList<NoteItem>>()
    private val arraylst = ArrayList<NoteItem>()

The isStringEmpty variable is a Boolean. It will help determine whether or not the user’s input is empty.

The inputTitle and inputdescription variables will store the user’s data. The values stored in these variables will change according to the user’s input; hence, we use MutableLiveData.

The list will store the NoteItem arraylst. It’s capable of refreshing itself when it detects a change in its content.

The NotesViewModel also contains three important methods; init, addData, and clearData.

The init method will initialize the isStringEmpty variable to false. This method launches automatically once the NotesViewModel is created.

    init {
        isStringEmpty.value = false
    }

The addData method takes the user input and checks if it’s empty.

The isStringEmpty variable is updated to true if the data is empty.

Otherwise, the data is entered into a NoteItem object and passed to the arraylst.

The arraylst is then stored in list.

 fun addData() {
        val title = inputTitle.value!!
        val description = inputDescription.value!!
        if(title.isBlank()|| description.isBlank()){
            isStringEmpty.value = true
        }else{
            inputTitle.value = " "
            inputDescription.value = " "
            var noteItem =NoteItem(title, description)
            arraylst.add(noteItem)
            list.value = arraylst
        }

    }

The clearData function resets the arraylst and list, as shown below.

    fun clearData(){
        arraylst.clear()
        list.value = arraylst
    }

Note: To use the bindable component, the NotesViewModel must extend the Observable class. You will then need to implement the methods below.

    override fun addOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback?) {
    }

    override fun removeOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback?) {
    }

Your final NotesViewModel file should look like this.

import androidx.databinding.Bindable
import androidx.databinding.Observable
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModel
import com.wanja.notesapp.model.NoteItem

import java.util.*
import kotlin.collections.ArrayList

class NotesViewModel(): ViewModel(), Observable {
    val isStringEmpty = MutableLiveData<Boolean>()
    @Bindable
    val inputTitle = MutableLiveData<String>()
    @Bindable
    val inputDescription = MutableLiveData<String>()
    val list = MutableLiveData<ArrayList<NoteItem>>()
    private val arraylst = ArrayList<NoteItem>()

    init {
        isStringEmpty.value = false
    }

    fun addData() {
        val title = inputTitle.value!!
        val description = inputDescription.value!!
        if(title.isBlank()|| description.isBlank()){
            isStringEmpty.value = true
        }else{
            inputTitle.value = " "
            inputDescription.value = " "
            var noteItem =NoteItem(title, description)
            arraylst.add(noteItem)
            list.value = arraylst
        }

    }

    fun clearData(){
        arraylst.clear()
        list.value = arraylst
    }

    override fun addOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback?) {
    }

    override fun removeOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback?) {
    }
}

4. ViewModelFactory

In the same viewmodels package, create a file named NotesViewModelFactory and add the code below.

The NotesViewModelFactory will throw an Exception in case the ViewModel is not found.

import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import java.lang.IllegalArgumentException

class NotesViewModelFactory(): ViewModelProvider.Factory{
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        if(modelClass.isAssignableFrom(NotesViewModel::class.java)){
            return NotesViewModel() as T
        }
        throw IllegalArgumentException ("UnknownViewModel")
    }
}

5. User Interface

Let’s create the app UI before finalizing with the MainActivity.

Since this is a Notes App, we need to allow the user to enter, save, and clear data. Therefore, the application will have two EditTexts and two Buttons.

Whatever the user types will be displayed in a TextView on the click of the submit button. The app UI is shown below.

To include data binding in the UI, enclose all content with <layout></layout>.

The ViewModel is introduced to the layout in the <data></data> section, as shown. Ensure that the type value points to the specific folder that has the required ViewModel.

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

<data>
    <variable
        name="NotesViewModel"
        type="com.wanjamike.co.notesapp.viewmodel.NotesViewModel" />
</data>

<!--other UI components-->
</layout>

The EditText widgets will be bound to the NotesViewModel using @={NotesViewModel.inputTitle} statement.

 <EditText
        android:id="@+id/editTextTextPersonName2"
        android:layout_width="match_parent"
        android:layout_marginEnd="40dp"
        android:layout_marginStart="40dp"
        android:layout_height="wrap_content"
        android:layout_marginBottom="36dp"
        android:text="@={NotesViewModel.inputTitle}"
        android:ems="10"
        android:inputType="textPersonName"
        android:hint="Description" />

<EditText
        android:id="@+id/editTextTextPersonName"
        android:layout_width="match_parent"
        android:layout_marginEnd="40dp"
        android:layout_marginStart="40dp"
        android:layout_height="wrap_content"
        android:layout_marginBottom="24dp"
        android:ems="10"
        android:text="@={NotesViewModel.inputDescription}"
        android:inputType="textPersonName"
        android:hint="Title" />

The submit and clear buttons will connect to the NotesViewModel by @{()->NotesViewModel.addData()} statement.

This is illustrated below.

 <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:backgroundTint="@color/colorAccent"
        android:layout_gravity="center"
        android:layout_marginEnd="20dp"
        android:onClick="@{()->NotesViewModel.addData()}"
        android:text="Submit"/>

<Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:backgroundTint="#E91E63"
        android:layout_gravity="center"
        android:layout_marginStart="20dp"
        android:onClick="@{()->NotesViewModel.clearData()}"
        android:text="Clear"/>

We’ll use the TextView with the ID content to display the user’s input.

Here is the full code for the activity_main.xml.

<?xml version="1.0" encoding="utf-8"?>

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

<data>
    <variable
        name="NotesViewModel"
        type="com.wanjamike.co.notesapp.viewmodel.NotesViewModel" />
</data>

<LinearLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_marginTop="16dp"
        android:text="Enter Notes" />

    <EditText
        android:id="@+id/editTextTextPersonName2"
        android:layout_width="match_parent"
        android:layout_marginEnd="40dp"
        android:layout_marginStart="40dp"
        android:layout_height="wrap_content"
        android:layout_marginBottom="36dp"
        android:text="@={NotesViewModel.inputTitle}"
        android:ems="10"
        android:inputType="textPersonName"
        android:hint="Description" />

    <EditText
        android:id="@+id/editTextTextPersonName"
        android:layout_width="match_parent"
        android:layout_marginEnd="40dp"
        android:layout_marginStart="40dp"
        android:layout_height="wrap_content"
        android:layout_marginBottom="24dp"
        android:ems="10"
        android:text="@={NotesViewModel.inputDescription}"
        android:inputType="textPersonName"
        android:hint="Title" />


   <LinearLayout
       android:layout_gravity="center"
       android:layout_width="wrap_content"
       android:layout_height="wrap_content">
       <Button
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:backgroundTint="@color/colorAccent"
           android:layout_gravity="center"
           android:layout_marginEnd="20dp"
           android:onClick="@{()->NotesViewModel.addData()}"
           android:text="Submit"/>

       <Button
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:backgroundTint="#E91E63"
           android:layout_gravity="center"
           android:layout_marginStart="20dp"
           android:onClick="@{()->NotesViewModel.clearData()}"
           android:text="Clear"/>
   </LinearLayout>


    <View
        android:id="@+id/divider"
        android:layout_width="match_parent"
        android:layout_height="2dp"
        android:layout_marginTop="10dp"
        android:layout_marginBottom="10dp"
        android:background="?android:attr/listDivider" />

    <TextView
        android:id="@+id/content"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:hint="Content Appears here"
        android:textSize="18dp"
        android:letterSpacing="0.1"
        android:padding="10dp"
        android:layout_marginBottom="4dp" />
</LinearLayout>
</layout>

6. MainActivity

In this class, we need to initialize the NotesViewModel and the ActivityMain data binding.

The lateinit allows variables to be initialized at a later stage.

The NotesViewModelFactory is initialized first and then passed to the NotesViewModel as a parameter.

The databinding is also assigned the required viewmodel and lifecycleowner.

 databinding = DataBindingUtil.setContentView(this,R.layout.activity_main)
 val factory = NotesViewModelFactory()         
 viewModel = ViewModelProviders.of(this, factory).get(NotesViewModel::class.java)
 databinding.notesViewModel = viewModel
 databinding.life

We will observe the list in the NotesViewModel using the following commands.

        viewModel.list.observe(this, Observer{
            databinding.content.text = it.toString()
    })

We’ll observe the isStringEmpty variable to determine if the user has clicked the submit button without entering data. A Toast message will appear in case the user inputs are empty.

 viewModel.isStringEmpty.observe(this, Observer{
            if(it == true){
                Toast.makeText(this, "No Notes Detected",Toast.LENGTH_SHORT).show();
            }
        })

The complete MainActivity.kt is shown below.

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import com.wanja.notesapp.databinding.ActivityMainBinding
import com.wanjamike.co.notesapp.viewmodel.NotesViewModel
import com.wanjamike.co.notesapp.viewmodel.NotesViewModelFactory

class MainActivity : AppCompatActivity() {
    private lateinit var viewModel: NotesViewModel
    private lateinit var databinding: ActivityMainBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        databinding = DataBindingUtil.setContentView(this,R.layout.activity_main)
        val factory = NotesViewModelFactory()
        viewModel = ViewModelProviders.of(this, factory).get(NotesViewModel::class.java)
        databinding.notesViewModel = viewModel
        databinding.lifecycleOwner = this
        viewModel.list.observe(this, Observer{
            databinding.content.text = it.toString()
    })
        viewModel.isStringEmpty.observe(this, Observer{
            if(it==true){
                Toast.makeText(this, "No Notes Detected",Toast.LENGTH_SHORT).show();
            }
        })
    }
}

When you run your application, it should appear as in the video below.

Conclusion

As a developer, data binding will allow you to save time by eliminating boilerplate code. The UI components are updated automatically in case of any data changes. This functionality enables you to create both high quality and productive applications.

References

Android Developer Documentation


Peer Review Contributions by: Peter Kayere


About the author

Michael Barasa

A lover of technology. An upright individual not afraid of getting out of the comfort zone and trying out new things.

This article was contributed by a student member of Section's Engineering Education Program. Please report any errors or innaccuracies to enged@section.io.