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?
Setting Values Automatically
The Data Binding Library provides ready-to-use binding adapters for some views. Let’s try this type of binding adapter in the Capsules tab.
To begin, add the capsule’s serial and type. Open item_capsule.xml. Modify capsule_name
TextView
to add the text attribute, as follows:
<TextView
android:id="@+id/capsule_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/text_margin"
android:text="@{capsule.serial}"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6"
app:layout_constraintStart_toEndOf="@id/capsule_image"
app:layout_constraintTop_toTopOf="parent" />
To set the value, add the binding expression @{capsule.serial}
. Since the serial
is of type String
, the data binding library is going to look for setText(text: String)
in TextView
. This method already exists, so there’s nothing else you have to do to make this binding adapter work.
ViewModel
and call its method instead.
Now, modify capsule_type
TextView
and add a line to bind it with the capsule type value, as follows:
<TextView
android:id="@+id/capsule_type"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/text_margin"
android:paddingTop="@dimen/text_padding"
android:text="@{capsule.type}"
android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle1"
app:layout_constraintStart_toEndOf="@id/capsule_image"
app:layout_constraintTop_toBottomOf="@id/capsule_name" />
Same as before, you added the binding expression @{capsule.type}
to text
.
Build and run. Select the Capsules tab. Now you see the capsule name and type, like in the image below.
The Capsule list item is ready. There are other values you can set automatically. One of them is the rocket name in the Rockets tab.
Open item_rocket.xml, add <layout>
as the parent element and add the following <data>
:
<data>
<variable
name="rocket"
type="com.raywenderlich.android.uspace.ui.models.Rocket" />
</data>
This variable
is called rocket
and its type is Rocket
. Remember, for the type
you need to add the complete package name where the class lives.
Once you’ve added rocket
, you can use it. Set the rocket name, as follows:
<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}"
app:layout_constraintStart_toEndOf="@id/rocket_image"
app:layout_constraintTop_toTopOf="parent" />
The last step to make data binding work in RecyclerView
is to open RocketsAdapter.kt and update bind()
like this:
fun bind(rocket: Rocket) {
binding.setVariable(BR.rocket, rocket)
}
Rebuild the project and import the BR class like you did earlier. This will set the rocket variable used in the layout.
<layout>
and <data>
set. bind()
sets the needed variable for adapters.
The rocket name is the only value you’re allowed to assign in this item layout. Later, you’ll use custom binding adapters to set the remaining values.
Another value you can assign is name
in the Dragons tab. Open item_dragon.xml and modify dragon_name
TextView
like this:
<TextView
android:id="@+id/dragon_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/text_margin"
android:textAppearance="@style/TextAppearance.MaterialComponents.Headline6"
android:text="@{dragon.name}"
app:layout_constraintStart_toEndOf="@id/dragon_image"
app:layout_constraintTop_toTopOf="parent" />
Finally, set the agency name in the Crew item. Open item_crew.xml and modify crew_agency
TextView
to display the agency name, like this:
<TextView
android:id="@+id/crew_agency"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/text_margin"
android:paddingTop="@dimen/text_padding"
android:text="@{crew.agency}"
android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle1"
app:layout_constraintStart_toEndOf="@id/crew_image"
app:layout_constraintTop_toBottomOf="@id/crew_name" />
In all these TextView
s, you’re using automatic binding adapters, since the method to set the text with a string value already exists. Build and run. Open the Rockets tab and you can see the rocket name:
Open the Crew tab and you’ll see the agency name:
Go to the Dragons tab and you can see the Dragon names:
The remaining attributes need to execute some logic before they can have their values set. To do this, you have to create binding adapters.
Creating Custom Binding Adapters
With custom binding adapters, you’re implementing some logic that executes before binding data. You use these kinds of adapters when there’s no default adapter in the Data Binding Library.
In the following sections, you’ll create several custom binding adapters with logic that handles view visibility, load images and format strings.
Handling View Visibility
You may have noticed that there’s a progress bar in the Rockets tab that never goes away. You’ll fix that using a custom binding adapter to handle the view visibility.
Open ViewBindingAdapter.kt and add the following code:
@BindingAdapter("android:visibility")
fun View.setVisibility(visible: Boolean) {
visibility = if (visible) {
View.VISIBLE
} else {
View.GONE
}
}
To create a custom binding adapter, you need to create an extension function of the view that will use the adapter. Then, you add the @BindingAdapter
annotation. You have to indicate the name of the view attribute that will execute this adapter as a parameter in the annotation.
android:visibility
. This attribute already exists in View
class. However, you can create your own attributes whenever you need to.
In this case, every time android:visibility
receives a Boolean
, it’ll execute our setVisibility
extension function.
Now, set loading
to visibility
. Open fragment_rockets.xml and modify ProgressBar
as shown below:
<ProgressBar
android:id="@+id/progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="@{loading}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
Using android:visibility="@{loading}"
sets the view visibility using the binding adapter.
For ProgressBar
to change its visibility, you need to set loading
whenever the progress bar changes. Open RocketsFragment.kt and modify onViewCreated()
as follows:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupList()
viewModel.rockets.observe(viewLifecycleOwner) { result ->
binding?.loading = false
handleResult(result)
}
binding?.loading = true
viewModel.getRockets()
}
binding
references the loading
you created in the layout. Using this reference, we set loading
whenever it’s needed.
Build and run. Now, you see the ProgressBar
while the data is loading, and it disappears when the app completes loading the data.
Custom binding adapters are also useful for loading images. You’ll learn this next.
Loading Images
Open ImageBindingAdapters.kt and create the following binding adapter:
@BindingAdapter("imageUrl")
fun ImageView.loadImage(url: String) {
Picasso.get().load(url).into(this)
}
Since you’ll use this adapter in ImageView
, this adapter needs to be an extension function of that view. The attribute that the view will use is imageUrl
. This is an example of a custom attribute. This binding adapter will use Picasso
to load the image.
Next, modify ImageView
in item_rocket.xml, 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:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
Here, you use app:imageUrl="@rocket.images[0]"
to call the binding adapter. This is the custom attribute you created previously and the value in rocket
, which has the image URL.
Build and run. You’ll see the rocket images loading, as in the next image:
This binding adapter uses only one attribute, but you can add more. Let’s extend this binding adapter to show a placeholder while the image loads.