Implement Data Binding with Kotlin

Chris Chen
4 min readAug 12, 2019
Photo by Dan Price on Makeuseof

Android launch Data Binding library for several years. The Data Binding Library offers both flexibility and broad compatibility. You can use it with devices running Android 4.0 (API level 14) or higher. It’s an option to apply since we still can use the local way to write our code. So what’s the benefit to apply it to our project? I would say. It will let our code clean. Let’s see how we implement it.

In build.gradle we can easily enable it by a toggle.

apply plugin: 'kotlin-kapt'android {
... //skip above your config

dataBinding {
enabled = true
}

}

Then no matter what architecture pattern you using (MVC,MVP, or MVVM)

You will have a model like this User.kt

data class User(
var name: String?,
var id: Int,
var avatarUrl: String?,
var company: String?
)

Then in your xml file. Please wrap your view with <Layout></Layout> tag.

And add <data></data> this means you will binding the views and data model together in this activity_user.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="user"
type="your.package.name.User"/>
</data>
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white">

<ImageView android:id="@+id/avatar"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintWidth_percent="0.4"
app:layout_constraintDimensionRatio="W, 1:1"
android:scaleType="centerCrop"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:imageUrl="@{user.avatarUrl}"/>

<TextView android:id="@+id/nameTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
app:layout_constraintTop_toBottomOf="@id/avatar"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:text="@{user.company}"
tools:text="Chris Wanstrath"/>
<!-- ...skip layouts --></android.support.constraint.ConstraintLayout></layout>

Then we need build project to generate some component.

Choose Make Project

In UserActivity we claim to bind as lateinit var because it must be inited in onCreate and never be null.

class UserActivity : Activity() {


private lateinit var binding: ActivityUserBinding

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// setContentView(R.layout.activity_user) remove this line for now
binding = DataBindingUtil.setContentView(this, R.layout.activity_user)

val user = User("Chris", 0, "url", "google")

binding.user = user
}
}

That’s it. The view and data binding together. If the data value changed. the view will automatically change then.

Also support primitive type and String for data in xml.

<data>
<variable
name="myBoolean"
type="boolean" />

<variable
name="myInt"
type="int" />
<variable
name="myLong"
type="long" />
<variable
name="myFloat"
type="float" />
<variable
name="myDouble"
type="double" />
<variable
name="myString"
type="String" />
</data>

That’s the basic usage for data binding. Then you might interest that what about load image? This looks like doesn’t work at all. It’s true. Data binding provide some further usage method. I will introduce how to use BindingAdapter.

With ViewModel

If you want to bindLivaData in view model. Remember to add lifecycleOwner . Let’s see the comment from google:

Sets the {@link LifecycleOwner} that should be used for observing changes of
LiveData in this binding. If a {
@link LiveData} is in one of the binding expressions and no LifecycleOwner is set, the LiveData will not be observed and updates to it will not be propagated to the UI.

class UserActivity : Activity() {


private lateinit var binding: ActivityUserBinding
private lateinit var viewModel: MainViewModeloverride fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_user)
viewModel = MainViewModel()
binding.lifecycleOwner = this
binding.vm = viewModel
}
}

BindingAdapter

Here is our requirement. I want a circle avatar. First of all, we create a singleton class UiUtil and implement loadImageInCircle method. It’s implemented by Glide so maybe you need to add dependance in your build.gradle file.

object UiUtil {
@BindingAdapter(value = ["imageUrl", "placeholder"], requireAll = false)
@JvmStatic
fun loadImageInCircle(view: ImageView, imageUrl: String?, placeholder: Drawable? = null) {
Glide.with(view.context)
.load(imageUrl)
.apply(RequestOptions.circleCropTransform())
.error(placeholder)
.into(view)
}
}

The value means in the xml, we need to define both imageUrl and placeholder. The requireAll means we can just define one of them rather than all. After rebuild, we can use app:imageUrl in xml now. Finally will look like

<ImageView android:id="@+id/avatar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
app:imageUrl="@{user.avatarUrl}"/>

We don’t need to call loadImageInCircle in our code. Just add one line then we can support circle crop image. :)

Logic in xml

We can also write some logic in xml. Like below isShow is a boolean type to determine the nameTextView show or hide. And we need to import android.view.View in xml data block.

<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>
<import type="android.view.View"/>
<variable
name="user"
type="your.package.name.User"/>
<variable
name="isShow"
type="boolean"/>
</data>
<!--- skip layout ...-><TextView android:id="@+id/nameTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{isShow ? View.GONE: View.VISIBLE}"
android:text="@{user.name}"
tools:text="Chris Wanstrath"/>
<!--- skip layout ...->
</layout>

Although we can use this way. But in my personal opinion. I would encourage you to put this kind of logic in Presenter or ViewModel. Because you can write Test to protect your business logic.

BindingConversion

In some case, a custom conversion is required between specific types. For example, the android:background expects a Drawable, but the color value specified is an integer. So we can define BindingConversion here.

@BindingConversion
fun convertColorToDrawable(color: Int) = ColorDrawable(color)

Then android:background support color now. we can define in our xml.

<View
android:background="@{isError ? @color/red : @color/white}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>

Conclusion

Let’s wrap up. Here are some pros and cons of data binding.

Pros:

  • Reduces boilerplate code to make the code base clean.
  • Easy implement custom attrs and custom views.
  • The library extracts the views including the IDs from the view hierarchy in a single pass. This mechanism can be faster than calling the findViewById() method for every view in the layout. (reference)

Cons:

  • If your data model is nested. you might need to check the null case. Hence, better to create a method in your parent cover the null check.
  • Auto generates class causes the app size to increase.
  • UI isn’t testable if you write business in xml. You should avoid doing that.

Github example: here

--

--