Home Android & Kotlin Books Advanced Android App Architecture

11
MVVM Sample with Data Binding Written by Aldo Olivares

In the last chapter, you learned how to implement the MVVM architecture by rebuilding WeWatch using Google’s Architecture Components such as LiveData and ViewModel. In this chapter, you’ll learn how to further improve your app’s architecture by using the Data Binding library to decouple the XML layouts from the activities.

Along the way you’ll learn:

  • How to use data binding with XML layouts.
  • How to use data binding with RecyclerView adapters.
  • How to implement one-way data binding.
  • How to implement two-way data binding.
  • How to create observable fields.
  • How to use data binding to observe ViewModels.

What is data binding?

Before implementing data binding in your Android projects you first need to understand what data binding can do for you. According to the official documentation developer.android.com/topic/libraries/data-binding:

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.

Simply put, data binding lets you display the values of variables or properties inside your XML layouts such as ConstraintLayouts or RecyclerViews.

Typically (and without data binding), when you want to change or display a value inside your XML layout, you first need to get a reference to the View by using findViewById(). Once you have that, you can apply your changes:

findViewById<TextView>(R.id.text_view).apply {
    text = viewModel.userName
}

While this approach isn’t bad, it leads to a lot of boilerplate code that can create a high level of coupling between your layouts and your activities or fragments. Developers are usually forced to create many set up methods that get called immediately after the initial creation of their Views. With data binding, you can keep the UI updated by assigning the variables directly into your layout files:

<TextView
    android:text="@{viewmodel.userName}" />

In this example, the @{} syntax lets you display a property from viewmodel within the assignment expression. We’ll refer to this syntax as the One-way data binding syntax. This approach helps you remove boilerplate code from your activities and fragments. And because you can assign default values, it also prevents memory leaks and NullPointerExceptions.

In the next few sections, you’ll learn how to use data binding in the WeWatch app from the previous chapter.

Getting Started

Start by opening the starter project for this chapter. You can also use your own project from the previous chapter.

Implementing data binding

By default, data binding is not enabled. To use the Data Binding library, open build.gradle and add the following lines inside the Android block:

dataBinding {
  enabled = true
}

Adding data binding to MainActivity

There are four steps to implement data binding in your Views:

<data>
    <variable
      name="movie"
      type="com.raywenderlich.wewatch.data.model.Movie"/>
</data>
android:text="@{movie.title}"
android:text="@{movie.releaseDate}"
inner class MovieHolder(val binding: ItemMovieMainBinding) : RecyclerView.ViewHolder(binding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MovieHolder {
  val layoutInflater = LayoutInflater.from(parent.context)
  val binding = DataBindingUtil.inflate<ItemMovieMainBinding>(layoutInflater, R.layout.item_movie_main, parent, false)
  return MovieHolder(binding)
}
//1
override fun onBindViewHolder(holder: MovieHolder, position: Int) {
  //2
  val movie = movies[position]
  //3
  holder.binding.movie = movie
  //4
  holder.binding.checkbox.setOnCheckedChangeListener{ checkbox, isChecked ->
    if (!selectedMovies.contains(movie) && isChecked) {
      selectedMovies.add(movies[position])
    }else{
      selectedMovies.remove(movies[position])
    }
  }
  //5
  holder.binding.checkbox.isChecked = selectedMovies.contains(movie)
}
@BindingAdapter("imageUrl")
fun ImageView.setImageUrl(url: String?) {
  Picasso.get().load(url).into(this)
}

@BindingAdapter("imageUrl")
fun ImageView.setImageUrl(int: Int) {
  this.setImageDrawable(resources.getDrawable(int,null))
}
if (movie.posterPath != null) {
  holder.binding.movieImageView.setImageUrl(
		RetrofitClient.TMDB_IMAGEURL + movie.posterPath)
} else {
  holder.binding.movieImageView.setImageUrl(
		R.drawable.ic_local_movies_gray)
}

Adding data binding to AddMovieActivity

To implement data binding in AddMovieActivity, you need to follow similar steps, with one key difference: You’ll use two-way data binding.

<CheckBox
    android:id="@+id/checkbox"
    android:checked="@{viewmodel.watched}"
    android:onCheckedChanged="@{viewmodel.watchedChanged}"
/>
<CheckBox
    android:id="@+id/checkbox"
    android:checked="@={viewmodel.watched}"
/>
<variable
  name="viewModel"
  type="com.raywenderlich.wewatch.viewmodel.AddViewModel"/>
var title = ObservableField<String>("")
var releaseDate = ObservableField<String>("")
//1
private val saveLiveData = MutableLiveData<Boolean>()
//2
fun getSaveLiveData(): LiveData<Boolean> = saveLiveData
//3
fun saveMovie() {
  if (canSaveMovie()) {
    repository.saveMovie(Movie(title = title.get(), releaseDate = releaseDate.get()))
    saveLiveData.postValue(true)
  } else {
    saveLiveData.postValue(false)
  }
}
//4
fun canSaveMovie(): Boolean {
  val title = this.title.get()
  title?.let {
    return title.isNotEmpty()
  }
  return false
}
android:text="@={viewModel.title}"
android:text="@={viewModel.releaseDate}"
android:onClick="@{()-> viewModel.saveMovie()}"
override fun onCreate(savedInstanceState: Bundle?) {
  super.onCreate(savedInstanceState)
  //1  
  val binding = DataBindingUtil.setContentView<ActivityAddBinding>(this, R.layout.activity_add)
  //2
  viewModel = ViewModelProviders.of(this).get(AddViewModel::class.java)
  //3
  binding.viewModel = viewModel
}
private fun configureLiveDataObservers() {
  viewModel.getSaveLiveData().observe(this, Observer { saved ->
    saved?.let {
      if (saved) {
        finish()
      } else {
        showMessage(getString(R.string.enter_title))
      }
    }
  })
}
configureLiveDataObservers()

Challenge

You now know how to use data binding to improve your MVVM architecture. It’s time to put that knowledge into practice by refactoring SearchMovieActivity.

Key points

  • Data binding lets you display the values of variables or properties inside XML layouts.
  • Data binding is not enabled by default; you need to activate it in the app-level build.gradle.
  • Two-way data binding lets you set values and react to changes at the same time.
  • The two-way binding syntax @={}, lets you update the appropriate values in the ObservableFields.
  • The one-way binding syntax @{}, lets you display a certain property from the viewmodel in the assignment expression.

Where to go from here?

The Data Binding library works well with other Android Architecture Components such as the ViewModel. Also, data binding makes your code easy-to-read and maintain by providing a reliable way to bind your XML Layouts, thus reducing boilerplate.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.

Have feedback to share about the online reading experience? If you have feedback about the UI, UX, highlighting, or other features of our online readers, you can send them to the design team with the form below:

© 2021 Razeware LLC

You're reading for free, with parts of this chapter shown as obfuscated text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

Unlock Now

To highlight or take notes, you’ll need to own this book in a subscription or purchased by itself.