Data Binding & Binding Adapters
Modern Android development involves a variety of tools and libraries to streamline and enhance the development process. Among these, Data Binding stands out for its ability to bind UI components to data sources declaratively. In this article, we’ll dive into how you can use Data Binding along with Retrofit and LiveData to create a seamless and efficient Android application.
Why Use Data Binding?
Data Binding allows you to:
- Reduce boilerplate code: You don’t need to manually find views and update them.
- Improve performance: Data Binding minimizes the need for UI framework calls.
- Simplify code maintenance: Your UI code becomes cleaner and more modular.
Project Overview
We’ll build a simple application that fetches random dog images from the Dog CEO API and displays them using Data Binding, Retrofit for network requests, and LiveData for handling UI-related data.
Step 1: Setting Up the Binding Adapter
To start, we need a Binding Adapter to load images into an ImageView using Picasso. This adapter will help us bind a URL directly to an ImageView in our layout.
package com.android.databindingdemo.view
import android.widget.ImageView
import androidx.databinding.BindingAdapter
import com.squareup.picasso.Picasso
object BindingAdapter {
@JvmStatic
@BindingAdapter("app:image")
fun loadImage(view: ImageView, url: String?) {
url?.let {
Picasso.get().load(url).into(view)
}
}
}Get Abhishek Pathak’s stories in your inbox
Join Medium for free to get updates from this writer.
Step 2: Setting Up Retrofit
Next, let’s configure Retrofit to handle network requests. Retrofit is a powerful HTTP client for Android that makes it easy to consume RESTful web services.
package com.android.databindingdemo.data.remote
object Constant {
const val BASE_URL = "https://dog.ceo/api/breeds/image/"
const val END_POINT = "random"
}
data class DogResponse(
val message: String,
val status: String
)
interface ApiService {
@GET(Constant.END_POINT)
fun getRandomDogs(): Call<DogResponse>
}
object RetrofitBuilder {
private lateinit var retrofit: Retrofit
fun getRetrofit(): Retrofit {
if (!this::retrofit.isInitialized) {
retrofit = Retrofit.Builder()
.baseUrl(Constant.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
return retrofit
}
}Step 3: Creating the ViewModel
The ViewModel will manage UI-related data in a lifecycle-conscious way. It will fetch data from the API and expose it to the UI through LiveData.
package com.android.databindingdemo.viewmodel
import android.util.Log
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.android.databindingdemo.data.remote.ApiService
import com.android.databindingdemo.data.remote.DogResponse
import com.android.databindingdemo.data.remote.RetrofitBuilder
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
class DogViewModel : ViewModel() {
private var _dogLiveData = MutableLiveData<DogResponse>()
val dogLiveData: LiveData<DogResponse> = _dogLiveData
private lateinit var apiService: ApiService
fun fetchRandomDog() {
val retrofit = RetrofitBuilder.getRetrofit()
apiService = retrofit.create(ApiService::class.java)
apiService.getRandomDogs().enqueue(object : Callback<DogResponse> {
override fun onResponse(call: Call<DogResponse>, response: Response<DogResponse>) {
if (response.isSuccessful) {
_dogLiveData.value = response.body()
}
}
override fun onFailure(call: Call<DogResponse>, t: Throwable) {
Log.i("tag", t.message.toString())
}
})
}
}Step 4: Setting Up the Activity
Finally, we set up our MainActivity to use Data Binding and ViewModel. The ActivityMainBinding class is generated from our layout file, which we will see shortly.
package com.android.databindingdemo.view
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.ViewModelProvider
import com.android.databindingdemo.R
import com.android.databindingdemo.databinding.ActivityMainBinding
import com.android.databindingdemo.viewmodel.DogViewModel
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var dogViewModel: DogViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.lifecycleOwner = this
dogViewModel = ViewModelProvider(this)[DogViewModel::class.java]
binding.viewmodel = this.dogViewModel
}
}Step 5: Creating the Layout
In the layout file activity_main.xml, we set up the UI components and bind them to the ViewModel.
<?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="viewmodel"
type="com.android.databindingdemo.viewmodel.DogViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".view.MainActivity">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/imgDog"
android:layout_width="match_parent"
android:layout_height="300dp"
android:scaleType="centerCrop"
app:image = "@{viewmodel.dogLiveData.message}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/txtDogUrl"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="20dp"
android:text="@{viewmodel.dogLiveData.message}"
app:layout_constraintTop_toBottomOf="@+id/imgDog" />
<Button
android:id="@+id/btnDog"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Next random dog"
android:onClick="@{() -> viewmodel.fetchRandomDog()}"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>Conclusion
By combining Data Binding with Retrofit and LiveData, we can create a clean, efficient, and maintainable Android application. This approach reduces boilerplate code, ensures a clear separation of concerns, and enhances the overall user experience. Whether you’re a seasoned developer or just starting, integrating these tools into your projects will significantly improve your development process. Happy coding!






