Advanced Data Binding in Android: Binding Adapters
In this advanced data binding tutorial, you’ll learn how you can interact directly with the components in your layouts, assign a value and handle events dispatched by the views using binding adapters. By Rodrigo Guerrero.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Advanced Data Binding in Android: Binding Adapters
25 mins
- Getting Started
- Introducing Data Binding
- Understanding Data Binding
- Initializing Data Binding
- Binding Data in RecyclerViews
- Understanding Binding Adapters
- Setting Values Automatically
- Creating Custom Binding Adapters
- Handling View Visibility
- Loading Images
- Using Multiple Attributes
- Adding Other Custom Binding Adapters
- Learning About Conversions
- Using Two-Way Data Binding
- Using a Binding Adapter
- Creating an InverseBindingAdapter
- Binding Listener Methods
- Where to Go From Here?
Using Multiple Attributes
Open ImageBindingAdapters.kt and modify the binding adapter like this:
@BindingAdapter("imageUrl", "placeholder")
fun ImageView.loadImage(url: String, placeholder: Drawable) {
Picasso.get().load(url).placeholder(placeholder).into(this)
}
This binding adapter receives two attributes: one with the image URL and the other with Drawable
that will show as a placeholder while the image loads.
Open item_rocket.xml and update it as follows:
<ImageView
android:id="@+id/rocket_image"
android:layout_width="@dimen/item_image_size"
android:layout_height="@dimen/item_image_size"
android:scaleType="centerCrop"
app:imageUrl="@{rocket.images[0]}"
app:placeholder="@{@drawable/splash_background}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
With this, you add the placeholder attribute to ImageView
. Build and run. You’ll see the placeholder while the image is loading.
Use this same binding adapter in the other screens too. Open item_crew.xml and add imageUrl
and placeholder
to ImageView
as follows:
<ImageView
android:id="@+id/crew_image"
android:layout_width="@dimen/item_image_size"
android:layout_height="@dimen/item_image_size"
android:scaleType="centerCrop"
app:imageUrl="@{crew.image}"
app:placeholder="@{@drawable/splash_background}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
Finally, open item_dragon.xml and modify its ImageView
as follows:
<ImageView
android:id="@+id/dragon_image"
android:layout_width="@dimen/item_image_size"
android:layout_height="@dimen/item_image_size"
android:scaleType="centerCrop"
app:imageUrl="@{dragon.images[0]}"
app:placeholder="@{@drawable/splash_background}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
Build and run. Go to the Crew tab and you’ll see the placeholder and the crew images, like in the image below:
Open the Dragons tab. You’ll now see the images there too.
Next, you’ll create other custom binding adapters to show more information in the lists.
Adding Other Custom Binding Adapters
For the rockets list, you need to show the rocket weight and height. Rocket
contains a Weight
that has the weight value in its kg
. The class also has Measurement
that has the height value in its meters
. To add the units, use R.string.rocket_weight_kg
and R.string.rocket_height_m
.
Open TextViewBindingAdapters.kt
and add the following code:
@BindingAdapter("rocketWeight")
fun TextView.addRocketWeight(weight: Weight) {
val formattedWeight = NumberFormat.getInstance().format(weight.kg)
text = context.getString(R.string.rocket_weight_kg, formattedWeight)
}
@BindingAdapter("rocketHeight")
fun TextView.addRocketHeight(height: Measurement) {
text = context.getString(R.string.rocket_height_m, height.meters)
}
This code creates two binding adapters that use rocketWeight
and rocketHeight
. For the rocket weight, you use NumberFormat
to separate the number with commas. You show the two values with the string resources.
Open item_rocket.xml and modify rocket_height
and rocket_weight
TextViews
to use these binding adapters, like this:
<TextView
android:id="@+id/rocket_height"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/text_margin"
android:paddingTop="@dimen/text_padding"
app:rocketHeight="@{rocket.height}"
app:layout_constraintStart_toEndOf="@id/rocket_image"
app:layout_constraintTop_toBottomOf="@id/rocket_name"
tools:text="22.25 meters" />
<TextView
android:id="@+id/rocket_weight"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/text_margin"
android:paddingTop="@dimen/text_padding"
app:rocketWeight="@{rocket.mass}"
app:layout_constraintStart_toEndOf="@id/rocket_image"
app:layout_constraintTop_toBottomOf="@id/rocket_height"
tools:text="30,000 kg" />
Here, you’re using app:rocketHeight
to display the height and app:rocketWeight
to display the weight. Build and run. You’ll see the rocket items with all the information, like this:
Next, you’ll count items in a list and show the result in the layout. Open TextViewBindingAdapters.kt and add the following code:
@BindingAdapter("launches")
fun TextView.numberOfLaunches(crew: Crew) {
val numberOfLaunches = crew.launches.count()
text = context.getString(R.string.launches, numberOfLaunches)
}
This code counts the number of launches for each crew member and set a string with this value. Open item_crew.xml and add app:launches
in it, like this:
<TextView
android:id="@+id/crew_launches"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/text_margin"
android:paddingTop="@dimen/text_padding"
android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle2"
app:launches="@{crew}"
app:layout_constraintStart_toEndOf="@id/crew_image"
app:layout_constraintTop_toBottomOf="@id/crew_agency" />
Finally, you need to show a formatted date in the Dragon items, documenting the first launch date. You also need to capitalize the first letter in the Dragon type. Open TextViewBindingAdapter.kt and add the following code:
@BindingAdapter("date")
fun TextView.formatDate(date: String) {
val formatter = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
formatter.parse(date)?.also {
val finalFormatter = SimpleDateFormat("MMMM dd, yyyy", Locale.getDefault())
text = finalFormatter.format(it)
}
}
This binding adapter formats the provided date and displays it in TextView
. Now, add the following code to capitalize the first letter of Dragon:
@BindingAdapter("capitalizeFirst")
fun TextView.capitalizeFirst(value: String) {
text = value.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() }
}
This binding adapter capitalizes the first letter of the provided string.
Open item_dragon.xml and modify dragon_date
and dragon_type
to use app:date
and app:capitalizeFirst
, as follows:
<TextView
android:id="@+id/dragon_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/text_margin"
android:paddingTop="@dimen/text_padding"
android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle1"
app:date="@{dragon.firstFlightDate}"
app:layout_constraintStart_toEndOf="@id/dragon_image"
app:layout_constraintTop_toBottomOf="@id/dragon_name" />
<TextView
android:id="@+id/dragon_type"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/text_margin"
android:paddingTop="@dimen/text_padding"
android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle2"
app:capitalizeFirst="@{dragon.type}"
app:layout_constraintStart_toEndOf="@id/dragon_image"
app:layout_constraintTop_toBottomOf="@id/dragon_date" />
Build and run. Go to the Crew tab and you’ll see the number of launches:
Finally, open the Dragons tab and you’ll see the Dragon first launch date and its type, as shown in the next image:
Sometimes, you don’t need to add any extra logic to the binding and only convert from one type of object to another. You’ll learn this next.
Learning About Conversions
If you look closely, the crew members’ names are missing. You’re going to convert Crew
to a string to display names.
Open Converters.kt and add the following code:
@BindingConversion
fun crewToName(crew: Crew): String = crew.name
A conversion is a method that receives an object from one type and returns another type. Add @BindingConversion
to indicate that this method is a conversion.
Open item_crew.xml and modify crew_name
TextView
to set the text using Crew
, as shown below:
<TextView
android:id="@+id/crew_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/text_margin"
android:text="@{crew}"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6"
app:layout_constraintStart_toEndOf="@id/crew_image"
app:layout_constraintTop_toTopOf="parent" />
Data binding looks for a method that receives an object of type Crew
and returns a String
. Before you added the binding conversion method above, such a method didn’t exist.
Build and run. Open the Crew tab and you’ll see each crew member with their name, as shown in the following image:
Another conversion that can come in handy is to set a text appearance using predefined strings. Open Converters.kt and add the following code:
@BindingConversion
fun convertStringToTextAppearance(style: String): Int {
return when (style) {
"title" -> R.style.TextAppearance_MaterialComponents_Headline6
"height" -> R.style.TextAppearance_MaterialComponents_Subtitle1
"weight" -> R.style.TextAppearance_MaterialComponents_Subtitle2
else -> R.style.TextAppearance_AppCompat_Body1
}
}
This conversion takes a style in a string form and returns the corresponding style for the text appearance.
Open item_rocket.xml and modify TextView
s to set the right text appearances, like this:
<TextView
android:id="@+id/rocket_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/text_margin"
android:text="@{rocket.name}"
android:textAppearance="@{@string/title_appearance}"
app:layout_constraintStart_toEndOf="@id/rocket_image"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/rocket_height"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/text_margin"
android:paddingTop="@dimen/text_padding"
android:textAppearance="@{@string/height_appearance}"
app:rocketHeight="@{rocket.height}"
app:layout_constraintStart_toEndOf="@id/rocket_image"
app:layout_constraintTop_toBottomOf="@id/rocket_name" />
<TextView
android:id="@+id/rocket_weight"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/text_margin"
android:paddingTop="@dimen/text_padding"
android:textAppearance="@{@string/weight_appearance}"
app:rocketWeight="@{rocket.mass}"
app:layout_constraintStart_toEndOf="@id/rocket_image"
app:layout_constraintTop_toBottomOf="@id/rocket_height" />
Since textAppearance
receives a style resource, it wouldn’t know how to set a string without the conversion you just created. Now, textAppearance
will use the code above to convert the string to a style resource.
Build and run. You’ll see that the rocket name, height, and weight now have an improved appearance:
You’ve learned how to set values to the views. However, sometimes you’ll also need to receive values or events from views — two-way data binding to the rescue.
Using Two-Way Data Binding
So far, you’ve been using one-way data bindings. One-way bindings set a value to the view and listen to the value changes, as shown in the diagram below.
With two-way data binding, you set a value to the view and listen to the value changes at the same time:
You’ll use two-way data binding to implement a filter. This filter will show crew members filtered by which space agency they belong to. First, create a binding adapter to set the value to the view.