android data binding ( techenum.com )

Android Data Binding: Replace findViewById() And More

We used to use findViewById() to map our views to the Java/Kotlin code. But there is something better than simply doing findViewById(). And this concept is not new as well, it was introduces a few years back. The concept is data binding, it’s not really a concept but let’s just call it that for now. And more importantly Android data binding will help you save time and make your code look less and cleaner. Also it is much more than just replacement for findViewById().

If you are new to Android this is the first thing you should consider learning after the basics. Let us see how we can actually use the data binding feature.

What is data binding in Android ?

If we look at the documentation, it defines data binding as a library that allows binding the UI components to the data sources using a declarative format.

What it means is that we do not need to write the binding code for UI components. It replaces the need to write code which looks something like this.

val profilePicture: ImageView = findViewById(R.id.my_image_view)Code language: Kotlin (kotlin)

The above snippet is the default way of binding our UI components with the data sources. And we will soon see how the replacement for the code above looks like.

Advantages of using data binding

There are quite a few advantages of using Android data binding. Here I have listed a few of them just to motivate to use it in your projects.

  • Cleaner and more readable code
  • No surprise exceptions such as id not found
  • One or two way data flow resulting in real-time update with less code
  • Debugging will be easier

Now that we have looked at the advantages let us dive right in to the step by step implementation.

Also Read: Android Notification Manager: Create Notification in Android

TECHENUM

Enabling data binding in our project

Android data binding is supported in API 14 and above. Also make sure all the gradle libraries are the latest versions.

Add the code below inside app > build.gradle file’s android { } block.

buildFeatures {
    dataBinding true
}Code language: Groovy (groovy)

This will enable the data binding in our Android project. Also make sure yo have the following block.

compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
}

kotlinOptions {
    jvmTarget = '1.8'
}Code language: Groovy (groovy)

Before we begin

Here are a list of things we will be using in data binding, I will be referencing these things below. So be sure to check back if you feel confused.

The @{} vs @={}

We will be seeing these expressions while passing values a lot. Let me quickly make these two expressions clear.

The @{} is the one way data binding. What do I mean by one way data binding ? One way data binding means passing the values from data sources to view but not vice versa. Any changes in the UI component will not be updated in the data source. Example:

<EditText
...
android:text="@{model.userName}"
... />Code language: HTML, XML (xml)

Now the @={} will allow two way data binding. Meaning if you update something in the EditText the userName variable will be updated too. Example:

<EditText
...
android:text="@={model.userName}"
... />Code language: HTML, XML (xml)

The <layout> block

The <layout> block is what makes the Android IDE take your .xml file and prepare the bindings. To use the data binding feature you must add the layout tag as the root tag in your xml. Example:

<layout>
    <ConstraintLayout>
        <!-- your UI components -->
    </ConstraintLayout>
</layout>Code language: HTML, XML (xml)

The <data>, <variable> and <import> block

These are the tags which allow us to declare the variable, import packages in the xml.

The <data> block is the root tag for <variable> and <import> statements.

The <import> is used for importing packages. The variable block will have all the <variable> declarations. Look at the example below

<layout>
    <import type ="java.lang.String" />

    <variable name="myName" type="java.lang.String" />
</layout>Code language: HTML, XML (xml)

Also Read: Learn Android Room Persistence Library for SQLite Database

TECHENUM

Create data source using BaseObservable

The most important part of using data binding in Android is the BaseObservable class. It helps us create observable objects which will automatically push updates to/from our UI components.

This class is the data source for our example if you are following along.

class TimeUpdateUiSource : BaseObservable() {

    private val mFormatter = SimpleDateFormat("hh:mm:ss", Locale.ENGLISH)

    @get:Bindable
    @set:Bindable
    var currentTime: String = ""
        set(value) {
            field = value
            notifyPropertyChanged(BR.currentTime)
        }

    @get:Bindable
    @set:Bindable
    var rawTimestamp: Long = 0L
        set(value) {
            field = value
            currentTime = mFormatter.format(value)
            notifyPropertyChanged(BR.rawTimestamp)
        }
    
}
Code language: Kotlin (kotlin)

Explaining the code

First, we must extend the BaseObservable class from package androidx.databinding.BaseObservable.

In the code above we have simple data formatter for formatting our time from Unix timestamp at Line 3.

Line 5, 6: @get:Bindable and @set:Bindable is the annotation that binds the getter and setter of the variable. It tells the BaseObservable to watch out for changes in these variables.

Line 10, 19: notifyPropertyChanged(BR.rawTimestamp) is notifying the BaseObservable for changes in variable named rawTimestamp. And BR is the IDE generated class in <you_package_name>.databinding.

And, set(value) { field = value } is just Kotlin setter block, read more about it here.

Also Read: Android Content URI: How to Get the File URI

TECHENUM

Preparing the layout for our example

Here is the layout for using the data binding. Follow similar structuring ways for your project.

Notice the <data> block of code and the android:text="@{model.currentTime}". We are doing one way data binding here.

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

    <data>

        <variable
            name="model"
            type="com.techenum.databinding.TimeUpdateUiSource" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout 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"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{model.currentTime}"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

    </androidx.constraintlayout.widget.ConstraintLayout>

</layout>Code language: HTML, XML (xml)

After preparing the BaseObservable and UI let us rebuild project by doing Build > Rebuild Project.

Finally, using data binding in Activity

Now that we have everything set up we can simply use it look at the comments in the code. I have explained what the code does in the section below too.

class MainActivity : AppCompatActivity() {

    private lateinit var mBinding: ActivityMainBinding
    private val mTimeUpdateUiSource = TimeUpdateUiSource()

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

        // the class generated is <layout_file_name>Binding
        // so for us it is ActivityMainBinding because our layout is activity_main.xml
        mBinding = ActivityMainBinding.inflate(layoutInflater)

        // we have to call the .getRoot() method to get the inflated view 
        setContentView(mBinding.root)

        // pass the instance we have prepared above to the model variable 
        // for the model declared in activity_main.xml
        mBinding.model = mTimeUpdateUiSource

        // infinite loop to update time in a new thread
        Executors.newSingleThreadExecutor().submit {
            while (true) {
                Thread.sleep(500)
                mTimeUpdateUiSource.rawTimestamp = System.currentTimeMillis()
            }
        }

    }
}
Code language: Kotlin (kotlin)

Code explanation

Line 3: We have declared the binding variable instance we will be needing later. Also,

Line 4: Create a new instance of our child class of BaseObservable to pass it to the mBinding.model variable. Also,

Line 11: Created new instance of the ActivityMainBinding using the inflate() method passing in the value obtained from getLayoutInflater(). Finally,

Line 14: We have replaced the R.layout.activity_main with mBinding.getRoot(). This is the most important portion we have to pass in the root view. Also,

Line 18: We have set the value of the model in mBinding to the one we created in Line 4.

Line 21 – 26: We have created a new thread with endless loop that will update the variables once every 500 ms. If you want to learn more about asynchronous processing, read this: Async Task in Android is Deprecated: There are Better Ways

Also Read: Android RecyclerView: How to Insert, Update and Delete Item

TECHENUM

The output

We have the output of something like below for our example code. The time is updating twice every seconds.

Try out the explanation in your own project and if you get stuck somewhere comment below.

Data binding in Fragment ?

The things you do are almost exactly same except you have to pass in LayoutInflater, ViewGroup and Boolean value instead of the default return value in your onCreateView() method.

You will have something like this in your onCreateView().

override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    // Inflate the layout for this fragment
    // FragmentBlankBinding.inflate(inflater, container, false)
    return inflater.inflate(R.layout.fragment_blank, container, false)
}Code language: Kotlin (kotlin)

Which after applying data binding you will have to change it to.

override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    // Inflate the layout for this fragment
    val mBinding = FragmentBlankBinding.inflate(inflater, container, false)
    return mBinding.root
}Code language: Kotlin (kotlin)

And that’s it, you need not change a thing more.

Related Posts