ViewBinding – the New Standard for View Interaction Handling in Android

viewbinding-the-new-standard-for-view-interaction-handling-in-android-0

View Binding is Google’s new method of referencing views. It generates a binding class for each of your XML layout files, and is used to interact with all views that have an assigned id value in a null-safe and type-safe way.

In comparison to the well-known methods such as Kotlin synthetics, Butterknife and findViewById, it provides a safer and more concise way of handling view interactions inside your views. Before we compare them all side by side, let’s dive in the features of View Binding.

Advantages and disadvantages of View Binding

Some of the most relevant benefits of using View Binding are:

  • Null safety – Properties in the generated class are non-null. In the case of multiple layout versions, e.g. layout file for different screen orientation or size, if some configuration contains an id that is not present in others, the generated property will be nullable.
  • Type safety – Binding properties will be correctly typed, even with custom views.
  • Interoperability – Generated classes are in Java and are optimized for Kotlin-Java interoperability.
  • Injection capability – Generated class can be injected in activity or fragment.
  • Speed – There are no impacts on build speed, as it doesn’t use an annotation processor. After the first build with View Binding is enabled, it will dynamically generate new properties. And if you add new view elements to your XML, there is no need to rebuild every time.

On the other hand, there are also some limitations:

  • Large overhead possibility – It generates classes for each XML by default. This could be a large overhead if View Binding is not used in all classes.
  • Multiple binding class instances required – There is no clean way to handle single activity/fragments with multiple layout files, since every generated view binding class is bound to exactly one layout file and its configurations. This means that you have to use more than one binding class instance in your controller to handle more layouts.

Setup and usage example

View Binding is available since Android Studio 3.6. It has to be enabled in your application module’s build.gradle.

	android {
     viewBinding.enabled = true
     …
}

This setting will enable generation of a binding class for every layout XML file in the module on the next build. However, if you would like to skip generating a class for specific layout, the following tag must be added in root element of that layout.

	<LinearLayout
        ...
        tools:viewBindingIgnore="true" >
        ...
</LinearLayout>

The generated binding class is called ResultProfileBinding. This class has two fields: a TextView called name and a Button conveniently called, well, button. The ImageView in the layout has no ID, so there is no reference to it in the binding class.

If layout includes another layout file with \ tag, a property will be generated, holding the included layout generated view binding. The included layout, same as all other generated views, must have an id tag.

The following code block shows the initialization and usage of the generated View Binding class in activity.

	//ResultProfileActivity.kt
private lateinit var binding: ResultProfileBinding

override fun onCreate(savedInstanceState: Bundle) {
    super.onCreate(savedInstanceState)
    binding = ResultProfileBinding.inflate(layoutInflater)
    val view = binding.root
    setContentView(view)
}
…

//And now you can use the binding in the following way
binding.name.text = viewModel.name
binding.button.setOnClickListener { viewModel.userClicked() }

In addition to the described way of initializing, View Binding can also be injected in activities and fragments, using one of the dependency injection libraries such as Dagger. This makes the “view controller” classes even more cohesive and independent from view initializing.

Before View Binding, there was no convenient way to achieve this. In order to inject the View Binding, you need to @Provide it in the module of your activity or fragment. The following example shows View Binding injection in activity.

	@Module
class ViewBindingModule {
  //Method providing the View Binding class
  @Provides
  fun viewBinding(activity: ViewBindingActivity): ActivityViewBinding                    
   { 
       return ActivityViewBinding
                 .inflate(activity.layoutInflater)
   }
}

//This module makes sure the activity contributes itself to injection
@Module
interface ActivityModule {

   @ContributesAndroidInjector(modules = [ViewBindingModule::class])
   fun viewBindingActivity(): ViewBindingActivity
}
//Activity
class ViewBindingActivity : DaggerAppCompatActivity() {
   //The injected View Binding class
   @Inject
   lateinit var binding: ActivityViewBinding

   override fun onCreate(savedInstanceState: Bundle?) {
       super.onCreate(savedInstanceState)
       setContentView(binding.root)
       ...
}

Comparison with popular methods of handling view interaction

In the following sections, I will try to make the case of why developers should start using View Binding by comparing it to other popular methods of handling view interaction: Kotlin synthetics, Butterknife, and the good old findViewById.

The following is an example of referencing and setting a text in TextView using all four methods.

	//findViewById
val textView: TextView
...
textView = findViewById(R.id.textView) //This is not null or type safe
textView.text = "Hello"

//Butterknife
@BindView(R.id.cardTextView)
lateinit var textView: TextView
...
//Initialized by passing the reference of activity/fragment
Butterknife.bind(this)
...
textView.text = "Hello" //Not null safe


//Kotlin synthetics - Only supported in Kotlin classes
textView.text = "Hello"



//ViewBinding
lateinit var binding: ActivityViewBinding
...
ActivityViewBinding.inflate(layoutInflater)
setContentView(binding.root)
...
binding.textView.text = "Hello" //Completely null and type safe

Syntax

Using View binding requires using an instance of generated binding class, which means there is an additional variable in each view reference. On the other hand, you don’t have to create properties for views that you will be using in your activity, as is the case with Butterknife and findViewById.

Kotlin synthetics definitely wins this one, because an extension is created for each view with id tag and it’s referenced just by the id value.

Code generation

In terms of the generated code, Butterknife and ViewBinding behave very similarly. Each creates classes which bind views to properties based on the view’s id. The difference is that the Butterknife-generated class is based on the view controller class passed in the initialization, while View Binding is based on the XML layout file.

In the case of Butterknife, this can lead to nullability and safety issues as activities and fragments can inflate more than one different layout. Kotlin synthetics generate an extension. In order to reference the view, it must be inflated.

However, Kotlin synthetics have no way of checking if the right layout is inflated, so referencing a view that is not currently on the screen is allowed and leads to a null pointer exception.

FindViewById approach is easily the winner of this category, as it doesn’t generate any additional code, even though it does require some additional properties and is far from being safe.

Type and null safety

View Binding is the only member of this group that ensures existence and the right type of every view for a given layout. This is one of the main advantages of View Binding, as it also works for different configurations of the layout. View Binding must be inflated with the layoutInflater from the screen controller, which means it ensures referencing the right layout.

Butterknife and Kotlin synthetics are not safe in terms of referencing the current layout and this can lead to null pointer exceptions. The same goes for findViewById as it is completely layout-ignorant and uses only the id to reference a view.

	//Example of Kotlin synthetics causing a null pointer exception
…
//activity_two layout imported
import kotlinx.android.synthetic.activity_two_synthetics.* 
…
//Inflating the activity_one layout
setContentView(R.layout.activity_one)
…
//Referencing textView from another layout which is not inflated causes an exception
textView.text = "Some text" 

Java and Kotlin interoperability

Kotlin synthetics work only with Kotlin classes, which is a main disadvantage of this approach as there are still many Java Android developers and projects. Other mentioned approaches work with both Java and Kotlin.

It’s worth noting that View Binding generates Java performance optimized code and outperforms the rest of the group.

An overview of approaches to handling view interactions

Finally, what should you use?

View Binding and Butterknife have a lot in common in terms of the idea behind the approach. However, View Binding addresses and successfully solves some of the issues of using Butterknife. That is probably why the official Butterknife Github page recommends switching to View Binding.

Even though it might seem that Kotlin synthetics offers a cleaner way of referencing views, the issues of null and type safety, as well as performance and interoperability issues should be reason enough to go with View Binding instead. As for the findViewById approach, it is legacy and it should stay that way considering all the disadvantages.

When in need of a safe way of interacting with your views, without having to worry about null and type issues, View Binding is definitely the way to go.