Working with views can sometimes be a frustrating experience. You've probably found yourself writing the same method over and over again just so you can access the views you need. Fortunately, it's now possible to avoid this repetitive task with a helpful feature called view binding. In this topic, you will find out more about the benefits of this approach. You will also learn how to set view binding up and use it when writing apps.
Why use View Binding?
We regularly need to initialize UI elements from XML in our projects to work with them in code. findViewById() is used for this purpose but can become very annoying when we have to utilize it frequently. If we need to use ten elements in our code, we have to write this construction ten times. Not good.
In the past, it was common to use a library based on annotations called ButterKnife. However, this still resulted in a lot of unnecessary code, and the tool is now deprecated. With the arrival of Kotlin Android Extensions (otherwise known as Kotlin Synthetics), we gained a more convenient way to interact with view objects directly. This approach has significant drawbacks as well, though: inaccessibility in Java, in addition to the use of global naming, and problems with views with the same id from different layouts.
Google has created a special feature called view binding to solve this problem. It has many advantages:
Speed: it doesn't affect build speed, and it's much faster to write the code itself!
Null safety: view binding creates direct references to views, meaning there's no risk of a
NullPointerExceptiondue to an invalid view ID.Type safety: all view binding fields are generated by the same type as those referenced in the XML, so there is no need for type-casting.
Let's look at how to take advantage of this valuable new tool.
Setting up
With view binding enabled, a special class will be generated for each XML markup file, storing the fields responsible for the corresponding UI elements. To do this, we must go to our module-level buildscript, typically app/build.gradle, and enter the following:
android {
viewBinding {
enabled true
}
...
}If there's a layout we don't want to generate a special Binding class for, we need to mark its root element in this way:
<androidx.constraintlayout.widget.ConstraintLayout xmlns:tools="http://schemas.android.com/tools"
tools:viewBindingIgnore="true"
android:layout_width="match_parent"
android:layout_height="match_parent">
...
</androidx.constraintlayout.widget.ConstraintLayout>Great, now we're ready to use the handy new feature!
Using
To begin with, we declare the object binding in our activity. The name of the Binding class is generated automatically according to the XML name: activity_main.xml -> ActivityMainBinding:
private lateinit var binding: ActivityMainBindingNext, in onCreate, initialize it with the inflate method result, passing in the layoutInflater. Here, all findViewById calls are located inside the generated inflate method.
binding = ActivityMainBinding.inflate(layoutInflater)As you remember, we used to pass our layout resource ID in the format R.layout.activity_main. When using viewBinding we call another setContentView overload, passing the root view of the binding:
setContentView(binding.root)Excellent! Now let's use the following layout as an example:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<TextView
android:id="@+id/test_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:id="@+id/test_button"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>As soon as we change the XML, the Binding class is re-generated.
If we want to refer to elements, we can only refer to those with an id and to the root element. We can't interact with EditText — unlike TextView and Button, EditText has no ID, so no binding field is created for it.
Finally, let's turn to our views and add something:
binding.testButton.text = "Meow"
binding.testButton.setOnClickListener {
Toast.makeText(this, "Meow", Toast.LENGTH_SHORT).show()
}Fast and convenient!
Also, nothing prevents us from using the view binding in Fragments. The approach is similar to the one we took for the activity but Fragments can have their Views created and destroyed multiple times, requiring us to null out the binding to avoid memory leak:
class BlankFragment : Fragment() {
private var _binding: FragmentBlankBinding? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentBlankBinding
.inflate(inflater, container, false)
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}Similarly to plain old inflater.inflate(layoutId, container, false), we use attachToParent=false here, letting FragmentManager attach the returned view on its own.
Since dealing with nullable properties is inconvenient by requiring !! when accessing the value, it's convenient to have a property which does this for us:
private val binding get() = _binding!!Conclusion
You have now learned how to utilize view binding to write code that interacts with views in a much faster and more convenient way. This is an approach you're likely to see used in future topics. Let's consolidate this new skill!