ViewBinding — Made easy with the power of Kotlin & Generics.

Chetan Tuteja
10 min readFeb 25, 2021

--

Kotlin Android Extensions is deprecated, which means that using Kotlin synthetics for view binding is no longer supported.

I remember when I was first shifting from Java to Kotlin, the discovery of Kotlin Android Synthetics was such a soothing experience. The need of no longer calling findViewById() for every single view was such a relief. From there on after, it was just Kotlin Synthetics all the way. Just directly referring the views by their XML IDs and you were all set.

Alas, not all good things last long, and just like that, we have to say our goodbyes to Kotlin Synthetics. Recently, it was announced with the Kotlin 1.4.20 release that the Kotlin Android Extensions are going to be deprecated. While the Parcelize plugin will be separated out to become its own entity, Synthetics are definitely leaving.

We have to keep moving forward though, so we do what is needed to be done, which in this case is :

YAY MEMES!!

Enter ViewBinding — part of Android Jetpack, the official and better solution for accessing Views and the replacement for the now-dead Kotlin Synthetics. Also, it comes with null-safety alike for both Java and Kotlin. So what is it? What does it do?

If view binding is enabled for a module, a binding class is generated for each XML layout file that the module contains. Each binding class contains references to the root view and all views that have an ID. The name of the binding class is generated by converting the name of the XML file to Pascal case and adding the word “Binding” to the end.

What are we doing here?

So, while ViewBinding is the correct thing to use, I personally feel that the ease of access while using Kotlin Synthetics is more in comparison. We are targeting here to achieve the same using ViewBinding, by basically making it a one time setup that is to be done, in turn making ViewBinding usage pretty straightforward.

The idea is simple, we extract out all the boilerplate stuff to an abstract class and with the power of generics, we can just simply extend the class and avoid doing all the stuff over and over again, basically stripping it down to the bare essentials. We will also use the help of Lifecycle methods to handle the cleaning up process. It might sound complicated for now, but, it is a one-time effort. Without any further ado, let’s get started.

Note:- It is recommended for you to go through the basics of ViewBinding to know what you are dealing with here. Also, since I work with Kotlin, the following will be Kotlin based strictly, but it should be easy to achieve the same for Java.

Getting Started

I have made a starter project that you can clone and follow along. The starter project initially uses Kotlin Synthetics, but we will be replacing it with ViewBinding as we go along.

  • Open up Android Studio and click on Get from Version Control or if any already existing project is already opened then go to File > New > Project from Version Control.
  • Make sure Git is selected here in Version Control and enter the following URL to clone the starter project.
https://github.com/chetan-tuteja/easy-binding-tutorial.git

Run the app after it has finished syncing, and you should see the following screens:

Screenshot of the Sample App and sorry if it annoys anyone, but the views of the Activity & Fragment were not aligned intentionally to show they are different.

Now, I will give a brief explanation of what the starter code contains:

  • MainActivity.kt: An activity with a a button which further loads a fragment on tap and a welcome text.
  • ExampleFragment.kt: A Fragment with a button which further shows a Toast on tap and a welcome text.
  • Extensions.kt: It contains an extension function to help perform a fragment transaction.
  • res/activity_main.xml and res/fragment_example.xml: The layout files for the activity and the fragment.

That was all for the starter project. Now, we can start our process of migration to ViewBinding.

Migration to ViewBinding:

The first step involves us modifying the module build.gradle file, we need to enable ViewBinding by setting it to true and removing the Kotlin Android Extensions.

  • Remove the Kotlin Android Extensions and use the independent kotlin-parcelize plugin if your app needs its functionality. So remove the following as per your version of gradle.
plugins {
id 'kotlin-android-extensions' // Remove this line
}

You could also find the plugin as follows. In either case, you can get rid of it.

apply plugin: `kotlin-android-extensions`
  • Next we have to enable, ViewBinding for the project, you can do that by adding the following to your module’s build.gradle file and syncing the project.
android {
...
buildFeatures {
viewBinding true
}
}

Before, we get started with the upcoming steps, I have written a library called Easy-Binding which will simply do what we are going to do below. So, if you want to skip all of the following, you can just add the library in dependencies and get going.

You can save the effort of writing with this one-liner to your rescue. Just make sure you have mavenCentral()added in your build.gradle . See the documentation of the library for more info, you can still read along below to understand what is being implemented.

implementation 'io.github.chetan-tuteja:easy-binding:1.0.0'

Shameless Plug:- Easy-Binding, a Kotlin based library to reduce the Boilerplate code for ViewBinding in Android to bare necessity, to make it easy to use. Basically, what we will be doing here, all done for you.

If you have decided to do it all by yourself and not use the library, we can start the abstraction process to remove the boilerplate code in turn making it easy to re-use. We will be doing the following process for Activity and Fragment. We will begin with Activity first. If at any point you get stuck there is a final version of the code we are going to below, so you can just refer to do that.

For Activities :-

  • Create a new abstract class called BindingActivity.kt, it will take in a generic ViewBinding type and extend AppCompatActivity() and LifecycleObserver.
abstract class BindingActivity<ViewBindingType : ViewBinding> : AppCompatActivity(),
LifecycleObserver {
}
  • Next, we are going to create a _binding variable which will be of the generic ViewBinding type we receive and will be nullable by default. We will also create another variable binding which we will use to access views and avoid continuous null checks.
// Variables
private var _binding: ViewBindingType? = null
// Binding variable to be used for accessing views.
protected val binding
get() = requireNotNull(_binding)
  • Next, we are going to create some abstract functions. First a function called setupViewBinding() which will return the generated binding class for wherever we are implementing this. Secondly, a function called init() which will be called after onCreate().
abstract fun init()abstract fun setupViewBinding(inflater: LayoutInflater): ViewBindingType
  • Next we will override the onCreate() method and add a Lifecycle observer, besides that, following is what we are going to do:
  1. Call the static inflate() method included in the generated binding class. This creates an instance of the binding class for the activity to use.
  2. Get a reference to the root view by either calling the getRoot() method.
  3. Pass the root view to setContentView() to make it the active view on the screen.
    /*
* Calls the abstract function to return the ViewBinding and set
* up LifeCycle Observer to get
* rid of binding once done.
*/
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
_binding = setupViewBinding(layoutInflater)
setContentView(requireNotNull(_binding).root)
lifecycle.addObserver(this)
init()
}
  • Next, we have to take care of the cleaning up process, we need to get rid of the binding as it should only be valid for the views lifecycle especially in case of Fragments. So, we are going to create a function called clearViewBinding() and hook it up to the Lifecycle OnDestroy event, similarly we will also override onDestroy() method call just in case a back up to achieve the same.
// Clears the binding and removes the observer when the activity is // destroyed.
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
private fun clearViewBinding() {
_binding = null
lifecycle.removeObserver(this)
}
/*
* Safe call method, just in case, if anything is messed up and
* lifecycle Event does not gets
* called.
*/
override fun onDestroy() {
_binding = null
super.onDestroy()
}

That’s the BindingActivity.kt all built up, ready to use. The following gist contains the code blocks shown above in a single snippet.

For Fragments :-

  • Create a new abstract class called BindingFragment.kt, it will take in a generic ViewBinding type and extend Fragment() and LifecycleObserver.
abstract class BindingFragment<ViewBindingType : ViewBinding> : Fragment(), LifecycleObserver {
}
  • The second step is the same as in case of Activity, we are going to create a _binding variable which will be a nullable and another variable binding which we will use to access views and avoid continuous null checks.
// Variables
private var _binding: ViewBindingType? = null
// Binding variable to be used for accessing views.
protected val binding
get() = requireNotNull(_binding)
  • This step is also similar to what we did above, we are going to create some abstract functions. First a function called setupViewBinding() which will return the generated binding class for wherever we are implementing this, but since this for Fragment, it is going take some extra parameters in. Secondly, a function called init() which will be called after onViewCreated().
abstract fun setupViewBinding(
inflater: LayoutInflater, container: ViewGroup?): ViewBindingType
abstract fun init()
  • Since this is a Fragment, next we are going to override the onCreateView() method and do the following:
  1. Call the static inflate() method included in the generated binding class. This creates an instance of the binding class for the activity to use.
  2. Get a reference to the root view by either calling the getRoot() method.
  3. Pass the root view to setContentView() to make it the active view on the screen.
// Calls the abstract function to return the ViewBinding.
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
_binding = setupViewBinding(inflater, container)
return requireNotNull(_binding).root
}
  • An extra step we are going to perform for Fragments, is to override the onViewCreated() method call, since that is when the views become available for Fragments, add a Lifecycle Observer and call the init() method.
// Set up the LifeCycle observer to get rid of binding once done.
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewLifecycleOwner.lifecycle.addObserver(this)
init()
}
  • To wrap it all, we are going to manage the cleaning up process as to make sure the binding is only valid for the lifecycle of the views. So, we are going to create a function called clearViewBinding() and hook it up to the Lifecycle OnDestroy event, similarly we will also override onDestroyView() method call just in case a back up to achieve the same.
// Clears the binding and removes the observer when the Fragment's // views get destroyed.
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
private fun clearViewBinding() {
_binding = null
viewLifecycleOwner.lifecycle.removeObserver(this)
}
/*
* Safe call method, just in case, if anything is messed up and
* lifecycle Event does not gets called.
*/
override fun onDestroyView() {
_binding = null
super.onDestroyView()
}

That’s the BindingFragment.kt all built up, ready to use. The following gist contains the code blocks shown above in a single snippet.

Since we are all done with the process of creating these Abstract Implementation, now let’s move forward on how to use them.

Using our Abstract Implementations :-

Again, if you want to avoid the trouble of writing all this, you can use the Easy-Binding Library which makes these abstract implementations available to you. But, if you have already taken the trouble to do this all, now comes the part of using them. Let us see how to do that then.

For Activities:

We have a basic activity called MainActivity.kt and it's corresponding XML Layout as activity_main.xml. The generated binding class for such an activity will be called ActivityMainBinding.

To use our implementation, you have to extend your activity file with BindingActivity and pass on the binding class and implement the abstract methods. The following will help make this clearer for you.

We have replaced the Kotlin Synthetics with ViewBinding in the above and are using our abstract implementation and accessing the views using the binding variable we created earlier.

For Fragments :-

We have a basic Fragment called ExampleFragment.kt and it's corresponding XML Layout as fragment_example.xml. The generated binding class for such an Fragment will be called FragmentExampleBinding.

To use our implementation, you have to extend your Fragment file with BindingFragment and pass on the binding class and implement the abstract methods. The following will help make this clearer for you.

We have replaced the Kotlin Synthetics with ViewBinding in the above and are using our abstract implementation and accessing the views using the binding variable we created earlier.

You can go ahead and run the app again now, just make sure you have removed all the previous imports related to Kotlin Synthetics. Once the build has completed and installed you should see the following screens which now make use of ViewBinding:

Screenshot of the Sample App and sorry if it annoys anyone, but the views of the Activity & Fragment were not aligned intentionally to show they are different.

That was quite the journey! We are finally done, this one time effort will help you save a lot of time ( I hope so! ). You can use the above abstract implementations for all your Fragments and Activities. The complete code after the migration is available in the final branch of the repository.

Support and Feedback :-

  • By making a PR on the repository as I know there are a lot better ways to do the same and would love to see your approaches.
  • Connect with me on LinkedIn or GitHub.
  • Star the GitHub repository and also the Library’s repository if you are using it.
  • Follow me for more related content.
  • Lastly share the article & library with your friends!

If you liked this article, show your support by clapping 👏 on this article as this is my first article and that’d motivate me to write more often.

--

--

Chetan Tuteja
Chetan Tuteja

Written by Chetan Tuteja

Android Developer | CS Grad|Project-based learner 👨🏻‍💻

Responses (3)