In the previous article, we covered View binding implementation and its internal working. If you haven’t checked it then do check it out as I have explained the workings of View binding in detail which will help to understand Data binding internal working easily just because Data binding is an extension of View binding.
DataBinding
The Data Binding Library is a support library that allows you to bind UI components in your layouts to data sources in your app using a declarative format rather than programmatically.
Implementation
Step 1: Enable Data binding in the project
android {
...
buildFeatures {
dataBinding true
}
}
Step 2: Create a data class, which will be used to set data in a declarative manner
internal data class User(
val name: String
)
Step 3: Create a layout file, with all required views and the User data class as a variable
<?xml version="1.0" encoding="utf-8"?>
<!-- Data binding specific tag -->
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<!-- User class as a variable, which we have used in TextView -->
<variable
name="user"
type="com.abhishelf.bindingtest.User" />
</data>
<!-- Normal Xml Layout View -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<!-- TextView setText from user which we have declared under data tag -->
<TextView
android:id="@+id/custom_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.name}" />
</LinearLayout>
</layout>
Step 4: Find the view reference and set the user variable from the kotlin side of the code
// ActivityMainBinding, auto generated class similar to ViewBinding
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
// Create a User instance set to ActivityMainBinding instance
binding.user = User("@abhishelf")
}
Done! We have basic Data binding implemented in our project. Now, we will move to the main agenda of the article which is internal working.
Internal Working
Data binding working is quite similar to View binding. So, when we enable the Data binding in the project we get some extra tasks on which the assemble task depends.
// For Debug Build
:app:dataBindingMergeDependencyArtifactsDebug SKIPPED
:app:dataBindingGenBaseClassesDebug SKIPPED
:app:dataBindingTriggerDebug SKIPPED
// For Release Build
:app:dataBindingMergeDependencyArtifactsRelease SKIPPED
:app:dataBindingGenBaseClassesRelease SKIPPED
:app:dataBindingTriggerRelease SKIPPED
The main purpose of the above tasks is to generate the Data binding reference classes which we can use to set the data at runtime. The first two tasks dataBindingMergeDependencyArtifacts and dataBindingGenBaseClasses are the same as view binding. Internally this task checks if we have enabled view binding or data binding and then based on the flag it either generates view binding classes or data binding classes. One new task is specifically added when we enable data binding which is dataBindingTrigger. This generates an empty class annotated with a data binding annotation (it could be any data binding annotation), so that the Java compiler still invokes data binding in the case that data binding is used (e.g., in layout files) but the source code does not use data binding annotations.
Now, when we assemble our project or execute data-binding specific tasks then we will get a few auto-generated classes as shown below
Few important files to keep an eye on are DataBinderMapperImpl, ActivityMainBindingImpl, and ActivityMainBinding. We will come to these classes later.
We will cover the flow in the top-down approach. Starting from calling the static method setContentView on DatabindingUtil (step 4 of our implementation) where we are passing activity and layoutId as a parameter. Internally this method calls setContentView from the Activity class (which we generally do in the traditional approach without Data binding) and after that it calls the bind method in the same class which returns ActivityMainBindingImpl which is used to set data on layout (step 4 of implementation).
Let’s breakdown the bind method implementation into steps to understand how the bind method return ActivityMainBindingImpl instance
- bind method is using sMapper global variable which has the reference of DataBinderMapperImpl (package: androidx.databinding and extends MergedDataBinderMapper). This class merges and manages all project DataBinderMapperImpl classes (the generated class for our package). In our case only one is applicable.
- when we call sMapper.getDataBinder, it calls getDataBinder from MergedDataBinderMapper and then the call goes to all available DataBinderMapperImpl classes and if the result is != null then it returns without going to the next DataBinderMapperImpl
- In project DataBinderMapperImpl class, it has mapper for different layout and when call comes for getDataBinder, it checks the mapper and return respective binding impl class in our case ActivityMainBindingImpl.
Now, we have the pointer at ActivityMainBindingImpl constructor where it does the following operations
- It gets the object for all tagged views under the root view which is one of the params in getDataBinder using mapBindings method from ViewDataBinding class
- After getting all objects it sets the tag to those views and calls the super class constructor which is nothing but ActivityMainBinding class. Yes, the same class that is used by view binding and which extends ViewDataBinding.
- So, once the ActivityMainBinding constructor is called it sets all view references to a global variable and is available for our use. The rest are similar to View binding.
when and how data is getting attached to view?
ActivityMainBindingImpl overrides executeBindings and sets all the required data to the view, in our case we have only one text view to set text.
This executeBindings will be executed from different points like when view attached to view, or when onStart
of lifecycle is called in the case of LiveData where we set lifecycle owner. These all operations are done by adding the required observer in ViewDataBinding class.
Check the Data binding documentation for more usage information.
Thanks for reading. Happy coding!