Android App Development
Android View Binding using in Activity, Fragment, Dialog, View, RecyclerViewHolder
January 19, 2022In 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.ParameterizedTypeinternal 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 extendsViewHolder
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
CharactersViewHolder
class 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 theViewHolder
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 extendsAppCompatActivity()
class.
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.viewbinding.ViewBindingopen 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.ViewBindingopen 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 extendsFragment()
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.ViewBindingopen 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 extendsBottomSheetDialogFragment()
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.BottomSheetDialogFragmentopen 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 extendsConstraintLayout()
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:
- View binding doesn’t support layout variables or layout expressions, so it can’t be used to declare dynamic UI content straight from XML layout files.
- View binding doesn’t support two-way 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:
- It makes it easy to migrate to new tech/architecture.
- 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!
- Plus all the benefits composition brings over inheritance!
References
- https://developer.android.com/topic/libraries/view-binding
- https://www.section.io/engineering-education/view-binding-in-android/
- https://medium.com/@samuelwahome/view-binding-in-android-f61403bd9d58
- https://medium.com/androiddevelopers/use-view-binding-to-replace-findviewbyid-c83942471fc
- https://javarevisited.blogspot.com/2013/06/why-favor-composition-over-inheritance-java-oops-design.html
- https://developer.android.com/topic/libraries/data-binding/expressions
- https://developer.android.com/topic/libraries/data-binding/two-way
Happy coding!
Author: Mesut Genç