Android ViewBinding using in Activity, Fragment, Dialog, View, RecyclerViewHolder

Mesut G.
Innovance Blog
Published in
8 min readJan 19, 2022

--

In this post, I will try to explain how we can use once ViewBinding at Activity, Fragment, Dialog, View and RecyclerViewHolder.

Overview

View binding is a feature that allows you to more easily write code that interacts with views. Once view binding is enabled in a module, it generates a binding class for each XML layout file present in that module. An instance of a binding class contains direct references to all views that have an ID in the corresponding layout.

In most cases, view binding replaces findViewById.

A binding class is usually generated for each layout file when using view binding. The binding class stores all the references to particular views.

View binding is null-safe and fast. It allows developers to avoid common errors during programming.

Advantages of View Binding

  • It supports null safety. This feature prevents developers from calling non-existent views or ids. As a result, it prevents the app from sudden crashes.
  • Helps to reduce boilerplate code.
  • Facilitates type safety. The binding class that is generated matches the views declared in the layout file. Once again, this feature prevents an application from crashing.

Setup Instructions

View binding is enabled on a module by module basis. To enable view binding in a module, set the viewBinding build option to true in the module-level build.gradle file, as shown in the following example:

android {
...
buildFeatures {
viewBinding = true
}
}

If you want a layout file to be ignored while generating binding classes, add the tools:viewBindingIgnore="true" attribute to the root view of that layout file:

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

Once enabled for a project, view binding will generate a binding class for all of your layouts automatically. 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.

Once this is done, you can use the binding class whenever you inflate layouts such as Fragment, Activity,Dialog , View and RecyclerViewHolder.

Extension Methods for ViewBinding

We have extension methods for using ViewBinding. I will try to explain step by step. The full codes are below.

ViewBindingExtensions.kt

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.viewbinding.ViewBinding
import java.lang.reflect.ParameterizedType
internal fun <V : ViewBinding> Class<*>.getBinding(layoutInflater: LayoutInflater): V {
return try {
@Suppress("UNCHECKED_CAST")
getMethod(
"inflate",
LayoutInflater::class.java
).invoke(null, layoutInflater) as V
} catch (ex: Exception) {
throw RuntimeException("The ViewBinding inflate function has been changed.", ex)
}
}
internal fun <V : ViewBinding> Class<*>.getBinding(
layoutInflater: LayoutInflater,
container: ViewGroup?
): V {
return try {
@Suppress("UNCHECKED_CAST")
getMethod(
"inflate",
LayoutInflater::class.java,
ViewGroup::class.java,
Boolean::class.java
).invoke(null, layoutInflater, container, false) as V
} catch (ex: Exception) {
throw RuntimeException("The ViewBinding inflate function has been changed.", ex)
}
}
internal fun Class<*>.checkMethod(): Boolean {
return try {
getMethod(
"inflate",
LayoutInflater::class.java
)
true
} catch (ex: Exception) {
false
}
}
internal fun Any.findClass(): Class<*> {
var javaClass: Class<*> = this.javaClass
var result: Class<*>? = null
while
(result == null || !result.checkMethod()) {
result = (javaClass.genericSuperclass as? ParameterizedType)
?.actualTypeArguments?.firstOrNull {
if
(it is Class<*>) {
it.checkMethod()
} else {
false
}
} as? Class<*>
javaClass = javaClass.superclass
}
return result
}inline fun <reified V : ViewBinding> ViewGroup.toBinding(): V {
return V::class.java.getMethod(
"inflate",
LayoutInflater::class.java,
ViewGroup::class.java,
Boolean::class.java
).invoke(null, LayoutInflater.from(context), this, false) as V
}********************************************************************
internal fun
<V : ViewBinding> BindingActivity<V>.getBinding(): V {
return findClass().getBinding(layoutInflater)
}
internal fun <V : ViewBinding> BindingFragment<V>.getBinding(
inflater: LayoutInflater,
container: ViewGroup?
): V {
return findClass().getBinding(inflater, container)
}
internal fun <V : ViewBinding> BindingSheetDialog<V>.getBinding(
inflater: LayoutInflater,
container: ViewGroup?
): V {
return findClass().getBinding(inflater, container)
}
internal fun <V : ViewBinding> BindingComponent<V>.getBinding(
inflater: LayoutInflater,
container: ViewGroup?
): V {
return findClass().getBinding(inflater, container)
}

Use View Binding in ViewHolder

To set up an instance of the binding class for use with a RecyclerView Adapter, you need to pass the generated binding class object to the holder class constructor.

We have row_character XML file for RecyclerView row item and the generated class is RowCharacterBinding .

row_character.xml

<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView 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"
android:id="@+id/card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardElevation="4dp"
app:cardUseCompatPadding="false"
> <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
> <com.google.android.material.textview.MaterialTextView
android:id="@+id/tvName"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Mr.Sanchez"
/> </androidx.constraintlayout.widget.ConstraintLayout></com.google.android.material.card.MaterialCardView>
  • We have BindingViewHolder class from extends ViewHolder class.

BindingViewHolder.kt

import android.content.Context
import androidx.recyclerview.widget.RecyclerView
import androidx.viewbinding.ViewBinding
/**
* A Simple
[BindingViewHolder] providing easier support for ViewBinding
*/
open class BindingViewHolder<VB : ViewBinding>(val binding: VB) :
RecyclerView.ViewHolder(binding.root) {
val context: Context = binding.root.context
}
  • The example usage in the CharactersViewHolderclass is as follows.

CharactersViewHolder.kt

inner class CharactersViewHolder(binding: RowCharacterBinding) :
BindingViewHolder<RowCharacterBinding>(binding) {
fun bind(item: Character) {
....
binding
.tvName.text = item.name
....
binding
.root.setOnClickListener {
....

}
}
}
  • Use “toBinding()” method from ViewBindingExtensions.kt file.
inline fun <reified V : ViewBinding> ViewGroup.toBinding(): V {
return V::class.java.getMethod(
"inflate",
LayoutInflater::class.java,
ViewGroup::class.java,
Boolean::class.java
).invoke(null, LayoutInflater.from(context), this, false) as V
}
  • Finally, we use the onCreateViewHolder to pass “parent.toBinding()” the generated binding class to the ViewHolder class.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return CharactersViewHolder(parent.toBinding())
}

Use View Binding in Activity

To set up an instance of the binding class for use with an Activity, perform the following steps in the BindingActivity:

  • We have BindingActivity() class from extends AppCompatActivity() class.
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.viewbinding.ViewBinding
open class BindingActivity<VB : ViewBinding> : AppCompatActivity() {
.......
}
  • Create and use “getBinding()” method from ViewBindingExtensions.kt file.
internal fun <V : ViewBinding> BindingActivity<V>.getBinding(): V {
return findClass().getBinding(layoutInflater)
}
  • Pass “getBinding()” to setContentView() at BindingActivity.kt file.

BindingActivity.kt

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.viewbinding.ViewBinding
open class BindingActivity<VB : ViewBinding> : AppCompatActivity() { lateinit var binding: VB override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (::binding.isInitialized.not()) {
binding = getBinding()
setContentView(binding.root)
}
}
}
  • Once these is done, you can now use the instance of the binding class to reference and utilize any of the views extends from BindingActivity() .

MainActivity.kt

class MainActivity : BindingActivity<ActivityMainBinding>() {    override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) ......
binding.tvName.text = "NAME"
}
}

Use View Binding in Fragment

To set up an instance of the binding class for use with a Fragment, perform the following steps in the BindingFragment:

  • We have BindingFragment() class from extends Fragment() class.
open class BindingFragment<VB : ViewBinding> : Fragment() {    .....}
  • Create and use “getBinding()” method from ViewBindingExtensions.kt file.
internal fun <V : ViewBinding> BindingFragment<V>.getBinding(
inflater: LayoutInflater,
container: ViewGroup?
): V {
return findClass().getBinding(inflater, container)
}
  • Pass “getBinding()” to onCreateView() at BindingFragment.kt file.

BindingFragment.kt

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.viewbinding.ViewBinding
open class BindingFragment<VB : ViewBinding> : Fragment() { private var _binding: VB? = null val binding: VB
get() = _binding
?: throw RuntimeException("Should only use binding after onCreateView and before onDestroyView")
protected fun requireBinding(): VB = requireNotNull(_binding) final override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = getBinding(inflater, container)
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
  • Once these is done, you can now use the instance of the binding class to reference and utilize any of the views extends from BindingFragment() .

CharactersFragment.kt

class CharactersFragment : BindingFragment<FragmentCharactersBinding>() {    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) ......
binding.tvName.text = "NAME"
}
}

Use View Binding in Dialog

To set up an instance of the binding class for use with an BottomSheetDialogFragment , perform the following steps in the BindingSheetDialog:

  • We have BindingSheetDialog() class from the extends BottomSheetDialogFragment() class.
open class BindingSheetDialog<VB : ViewBinding> : BottomSheetDialogFragment() {    .......
}
  • Create and use “getBinding()” method from ViewBindingExtensions.kt file.
internal fun <V : ViewBinding> BindingSheetDialog<V>.getBinding(
inflater: LayoutInflater,
container: ViewGroup?
): V {
return findClass().getBinding(inflater, container)
}
  • Pass “getBinding()onCreateView() at BindingSheetDialog.kt file.
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.viewbinding.ViewBinding
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
open class BindingSheetDialog<VB : ViewBinding> : BottomSheetDialogFragment() { private var _binding: VB? = null protected val binding: VB
get() = _binding
?: throw RuntimeException("Should only use binding after onCreateView and before onDestroyView")
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = getBinding(inflater, container)
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}

Use View Binding in View/Layout

To set up an instance of the binding class for use with an Layout, perform the following steps in the layout:

  • We have BindingComponent() class from extends ConstraintLayout() class.
open class BindingComponent<VB : ViewBinding> @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : ConstraintLayout(context, attrs) {
.....
}
  • Create and use “getBinding()” method from ViewBindingExtensions.kt file.
internal fun <V : ViewBinding> BindingComponent<V>.getBinding(
inflater: LayoutInflater,
container: ViewGroup?
): V {
return findClass().getBinding(inflater, container)
}
  • Pass “getBinding()” to BindingComponent.kt file.
val Context.inflater get() = LayoutInflater.from(this)********************************************************************open class BindingComponent<VB : ViewBinding> @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : ConstraintLayout(context, attrs) {
lateinit var binding: VB init {
initBinding()
}
private fun initBinding() {
if (::binding.isInitialized.not()) {
binding = getBinding(context.inflater, container = this)
}
}
}

Differences from findViewById

View binding has important advantages over using findViewById:

  • Null safety: Since view binding creates direct references to views, there’s no risk of a null pointer exception due to an invalid view ID. Additionally, when a view is only present in some configurations of a layout, the field containing its reference in the binding class is marked with @Nullable.
  • Type safety: The fields in each binding class have types matching the views they reference in the XML file. This means that there’s no risk of a class cast exception.

These differences mean that incompatibilities between your layout and your code will result in your build failing at compile time rather than at runtime.

Comparison with data binding

View binding and data binding both generate binding classes that you can use to reference views directly. However, view binding is intended to handle simpler use cases and provides the following benefits over data binding:

  • Faster compilation: View binding requires no annotation processing, so compile times are faster.
  • Ease of use: View binding does not require specially-tagged XML layout files, so it is faster to adopt in your apps. Once you enable view binding in a module, it applies to all of that module’s layouts automatically.

Conversely, view binding has the following limitations compared to data binding:

Because of these considerations, it is best in some cases to use both view binding and data binding in a project. You can use data binding in layouts that require advanced features and use view binding in layouts that do not.

Summary

To make your code reusable don’t try putting everything in a giant binding activity, fragment, view or recyclerViewHolder(inheritance), instead introduce composition with the help of extension functions or dividing logic into different classes. Because:

  1. It makes it easy to migrate to new tech/architecture.
  2. It makes it easy to start writing new activities and fragments, that are well know by all developers, instead of making them understand your version of activities and fragments!
  3. Plus all the benefits composition brings over inheritance!

References

Full Project

You can access the source code of the project from the link below.

Thanks 🚀

I hope it helps you. If it is useful to you, you can clap this article and follow me for such these articles.

Happy coding!

Que tengas un buen día, continuará 😊 🏄‍ 🚀

--

--

Mesut G.
Innovance Blog

Hola everybody, Sharing Software Development Experience, focus on Mobile. https://developersancho.medium.com/subscribe Continue as long as