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.
Quick Navigation
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.