Yet Another View binding Article?
Synthetics import are deprecated, ViewBinding is the new norm.
Featured on:
AndroidBoss #Issue16

OnCreate Digest #Issue34


Sections
There are many view binding articles out now, what values does it add? after completing this one you would be familiar with these points
- Setup View Binding 🛠
- Location of Generated Files 🏠
- Generated Xml class 🖇 Format
- Generated View Field Name 🖇
- View Reference is never null? 🤥Nullable Generated Field
- Generated Fields for `Include` and `Merge` Tags 🧐
- Functions in binding class 🧛🏼♀️
- How to ignore view from generation 😵
- Binding in Activity 👼
- Binding in Fragments 😈
- Inflate vs Bind
- Binding In Recycler Adapter 🤖
- Binding ViewGroups|CompoundViews|CustomViews 👾
- Reducing the boiler plate | Delegate Pattern 👨🏻💻
- Reducing the boiler plate | Base Classes 👨🏻💻❤️
- Bonus | Common mistake | Double Inflating
- Bonus | Anti Pattern in Fragments
- Bonus | Anti Pattern in Adapters
- Bonus | Cleaner Code
Kotlin 1.4.20 release on 19th-Nov, It’s a welcome release like always, but it deprecated Kotlin-Android-Extensions
compiler plugin, means no more synthetic imports, If you don’t know about it then check this commit message. They have accounted that in Jan-2019, since then they have given enough time to devs to migrate to their official solution - ViewBinding or DataBinding
It’s really sad it was that it was the only zero boiler plate solution we had, but now it’s gone, and ViewBinding is here to stay. So here are 15 things you need to know about ViewBinding.
Setup View Binding 🛠
Docs does pretty much great job explaining how to step up View binding, a point of interest I would like to add is to know your gradle plugin version. If your on Gradle plugin is 3.6.0
, you need to modify build.gradle
using this :
// https://developer.android.com/studio/releases/gradle-plugin#3-6-0
// Available in Android Gradle Plugin 3.6.0
android {
...
viewBinding {
enabled = true
}
}
If your Gradle plugin is 4.0.0
, you need to modify build.gradle
like this:
// https://developer.android.com/studio/releases/gradle-plugin#4-0-0
// Available in Android Gradle Plugin 4.0
android {
...
buildFeatures {
viewBinding true
}
}
Each module need to have this feature enabled in order to generate binding classes, it wouldn’t work if you just add this flag enabled in app module
and not in rest of the feature module
.i.e every feature module need to have this flag enabled.
Enjoying the Post?
a clap is much appreciated if you enjoyed. No sign up or cost associated :)
If you want to support my content do consider dropping a tip/coffee/donation💰
Location of Generated Files 🏠
Fun fact, even though library is called ViewBinding, but the package in which it generates XML binding classes is called databinding 😛, after completing setup of view binding you just need to rebuild your project in-order to make them visible.
You can find them at :
<Module-Name>/build/generated/data_binding_base_class_source_out/debug/out/<package-Name>/databinding/
example :
app/build/generated/data_binding_base_class_source_out/debug/out/com/example/ch8n/databinding
This is to check the source code
of generated file because the Android-Studio
on navigation will take you to xml layout
file not to the binding file.
Generated Xml class 🖇 Format
In ViewBinding, every XML layout
file in a module gets a corresponding generated binding class, whose name is in Pascal Case
format, with the word Binding
appended at the end and implements ViewBinding
interface.
Incase you don't know about PascalCase
- PascalCase: NewObject;
- CamelCase: newObject;
- PascalCase: LongFunctionName()
- CamelCase: longFunctionName()
// fun fact 2, `Compose UI Framework` functions are all in `PascalCase`
For example,
// file name : profile_layout.xml
<FrameLayout ... >
<TextView android:id="@+id/text_name" ... />
<ImageView android:id="@+id/image_avatar" ... />
<Button android:id="@+id/button_support_me" ... />
</FrameLayout>
Generated Class Name will look like,
public final class ProfileLayoutBinding implements ViewBinding {
...
}
Note : Files are generated into Java not Kotlin, reason for that would be discussed later, but it has nothing todo with interops between Java-Kotlin
Generated View Field Name 🖇
// file name : profile_layout.xml
<FrameLayout ... >
<TextView android:id="@+id/text_name" ... />
<ImageView ... />
<Button android:id="@+id/button_support_me" ... />
</FrameLayout>
This class has two fields: a TextView
called text_name
and a Button
called button_support_me
. The ImageView
in the layout has no ID
, so there is no reference to it in the binding class.
So the generated class would have fields like,
public final class ProfileLayoutBinding implements ViewBinding {
//---- Root View is always generated ---//
@NonNull
private final FrameLayout rootView;
@NonNull
public final Button buttonSupportMe;
@NonNull
public final TextView textName;
...
}
Note : Root view always get generated, it doesn't require any Id to be given, but `child-views` requires an `Id` to get generated into the binding class
View Reference is never null? 🤥 Nullable Generated Field
When a view is not present in all of the configurations
of the layouts, the field reference containing in its binding class will be marked as @Nullable

Example:
// file : res/layout/profile_layout.xml
<FrameLayout ... >
<TextView android:id="@+id/text_name" ... />
<Button android:id="@+id/button_support_me" ... />
</FrameLayout>
// file : res/layout-sw600dp/profile_layout.xml
<FrameLayout ... >
<TextView android:id="@+id/text_name" ... />
<ImageView android:id="@+id/image_avatar" ... />
<Button android:id="@+id/button_support_me" ... />
</FrameLayout>
Generated file would be having
public final class ProfileLayoutBinding implements ViewBinding {
@NonNull
private final FrameLayout rootView;
@NonNull
public final Button buttonSupportMe;
@NonNull
public final TextView textName;
//------
@Nullable
public final ImageView imageAvatar;
//------
...
}
from the above example, ImageView imageAvatar
is marked as @Nullable
because it is present in layout-sw600dp/profile_layout.xml
but not in layout/profile_layout.xml
, i.e it's not contained into all the configurations layouts.
Generated Fields for `Include` and `Merge` Tags 🧐
Included Layout
- Note that for using included layout with
<include>
tag needs to have anid: android:id="@+id/includes"
. This is required for View binding to generate the binding class like normal layout. - In case of included layouts, View Binding will first generate the binding class of the included layout, then add that as a field reference into the
Parent Binding class
.
// file name : profile_layout.xml
<FrameLayout ... >
<TextView android:id="@+id/text_name" ... />
<!-- include layout example -->
<include
android:id="@+id/includes"
layout="@layout/included_layout" />
</FrameLayout>
// file name : included_layout.xml
<LinearLayout ... >
<TextView android:id="@+id/text_demo1" ... />
</LinearLayout>
Generated class would look like.
public final class ProfileLayoutBinding implements ViewBinding {
@NonNull
private final FrameLayout rootView;
@NonNull
public final TextView textName;
//------
@NonNull
public final IncludedLayoutBinding includes;
//------
...
}
public final class IncludedLayoutBinding implements ViewBinding {
// this rootView is parent of included layout
@NonNull
private final LinearLayout rootView;
@NonNull
public final TextView textDemo1;
...
}
Use in codebase like :
// access to included files
binding.includes.textDemo1
Enjoying the Post?
a clap is much appreciated if you enjoyed. No sign up or cost associated :)
If you want to support my content do consider dropping a tip/coffee/donation💰
Merged Layout
- When we include layout with
<merge>
tag, the system ignores the<merge>
element and its child views directly added into the layout, in place of the<include/>
tag. - As per this behaviour, View binding should have generated the fields automatically inside the included parent binding class but unfortunately that doesn't happen.
- Even if you try to provide an
ID
to the included layout withmerge
tag, it will generate therootView
inside the binding class, but on runtime it will crash the app, withmissing id exception
, this behaviour is obvious asfindViewById
cannot get the root view of the merged layout as it has been flatten out by the system to be included inside the parent layout.
// file name : profile_layout.xml
<FrameLayout ... >
<TextView android:id="@+id/text_name" ... />
<!-- include layout example -->
<include
android:id="@+id/merges" [❌ wrong!]
layout="@layout/merged_layout" />
<!-- merge layout example -->
<include layout="@layout/merged_layout" /> [✅]
</FrameLayout>
// file name : merged_layout.xml
<merge ... >
<TextView android:id="@+id/text_demo2" ... />
</merge>
Generated class for merged layout would be
public final class ProfileLayoutBinding implements ViewBinding {
@NonNull
private final FrameLayout rootView;
@NonNull
public final TextView textName;
[❌] no merged field should be there, we need to manually bind it
...
}
public final class MergedLayoutBinding implements ViewBinding {
//-----
@NonNull
private final View rootView;
//-----
@NonNull
public final TextView textDemo2;
...
}
Manually Binding the Merged Layout :
val parentBinding = ProfileLayoutBinding.inflate(..)
val mergedBinding = MergedLayoutBinding.bind(parentBinding.root)
Use in codebase like :
// access to included files
parentBinding.textDemo1
mergedBinding.textDemo2
Note: Due to this limitation of ViewBinding, we need to maintain multiple binding variables, which we have to manually release when the view is destroyed, we will discuss down the article how to release them.
Functions in binding class 🧛🏼♀️
Every binding class also includes a getRoot()
method which comes from ViewBinding
Interface, providing a direct reference to the root view of the corresponding layout file.
In this example, the getRoot()
method in the ProfileLayoutBinding
class returns the FrameLayout
root view.
Other than that it exposes three public static functions
to create a binding object
inflate(inflater)
– Use this in an ActivityonCreate()
where there is no parent view to pass to the binding object.inflate(inflater, parent, attachToParent)
– Use this in a Fragment or a RecyclerView Adapter (or ViewHolder) where you need to pass the parent ViewGroup to the binding object.bind(rootView)
– Use this when you’vealready inflated the view
and you just want to use view binding toavoid findViewById
.
How to ignore view from generation 😵
Until now you understand that if you don't provide id
to a view it will not generate the field into the binding class, but the ViewBinding class will always be generated with all its functions stated in point-7
, but if you don't even want that to be generated and save some compilation time then add this attribute tools:viewBindingIgnore="true"
to the root view of that xml layout file. Example:
<LinearLayout
...
tools:viewBindingIgnore="true" >
...
</LinearLayout>
Binding in Activity 👼
Binding in activity looks like:
// In activity class
private lateinit var binding: ProfileLayoutBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// activity already provide you with an instance of layoutInflater
binding = ProfileLayoutBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
}
You can now use the binding variable to reference any of the views:
binding.name.text = "ch8n"
binding.button.setOnClickListener { "sample message".toToast() }
Enjoying the Post?
a clap is much appreciated if you enjoyed. No sign up or cost associated :)
If you want to support my content do consider dropping a tip/coffee/donation💰
Binding in Fragments 😈
Doing binding in fragment isn't that straight forward as in Activity, If you use the similar way like making a global variable of the binding class in the fragment, this could lead to a memory leak, because the fragment outlives the view. So clearing the reference in onDestroyView
is one way to handle it. For instance,
// In Fragment class
private var _binding: ProfileLayoutBinding? = null
// This property is only valid between onCreateView and onDestroyView.
private val binding
get() = _binding?: throw IllegalStateException("Cannot access view in after view destroyed and before view creation")
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = ProfileLayoutBinding.inflate(inflater, container, false)
val view = binding.root
return view
}
override fun onDestroyView() {
super.onDestroyView()
//Fragments outlive their views. Make sure you clean up any references to the
//binding class instance in the fragment's onDestroyView() method.
_binding = null
}
Another solution to deal with it is to move the reference to the correct scope. In Fragments, the inflate()
method requires you to pass in a layout inflater. If the layout has already been inflated, you can instead call the binding class's static bind()
method.
class ProfileFragment : Fragment(R.layout.profile_layout) {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val binding = ProfileLayoutBinding.bind(view)
binding.textName.text = "ch8n"
// problem with this is to accessing binding variable into global
// space wont be possible and would require a global instance and
// clearing in onDestroyView or rebinding of view in the other scope.
}
}
Note: avoid calling bind multiple times,as everytime you call bind function it will trigger findViewById, which will re-initialise all the binding fields.
Checkout this article to know more when to use lateinit
and when to use nullable
for a field

Inflate vs Bind 🥊
Bind()
It internally uses findViewById
on the view you have passed to initialise generated view fields of that binding.
//Signature
@NonNull
public static ProfileLayoutBinding bind(@NonNull View rootView){...}
If you look at it’s generated code, it would look a bit longer and very repetitive, including lots of nullability check on each field for performing null safe calls on findViewById
. This is done on purpose so that the generated class has the best optimised compiled bytecode for size and performance, also this is the reason why the generated file is in Java
not in Kotlin
, because of the bytecode generated by Kotlin would be greater than that of Java.
You can checkout JakeWarton Blog
explaining how the bytecode optimizing works for ViewBinding.

Inflate()
The bind(rootView)
function is responsible for binding all the views, but it requires View
object to work, Inflate creates that for it, Inflate uses LayoutInflater
to generate the View and pass it to Bind()
@NonNull
public static ActivityDetailBinding inflate(
@NonNull LayoutInflater inflater,
@Nullable ViewGroup parent,
boolean attachToParent
) {..}
Since, View Inflation is an expensive task, inflate()
will always be slower than bind()
, but the catch in using bind()
is that you must have the view
instance, also keep in mind whenever you're calling bind()
you're re-initialising all the binding variables using findViewById
i.e. the view hierarchy is walked each time to find it.
Enjoying the Post?
a clap is much appreciated if you enjoyed. No sign up or cost associated :)
If you want to support my content do consider dropping a tip/coffee/donation💰
Binding In Recycler Adapter 🤖
Note : Don’t forget to check the Anti-pattern related to AdapterBinding at the end.
Binding in Recycler are easier just follow these steps:
// 1. inflate layout inside onCreateViewHolder
class SampleAdapter : RecyclerView.Adapter<YourViewHolder>(){
...
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): YourViewHolder {
val binding = ProfileLayoutBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
// 2. pass binding object not the view
returnYourViewHolder(binding)
}
...
}
class YourViewHolder(
binding: ProfileLayoutBinding
) : RecyclerView.ViewHolder(/* 3.pass root as view*/ binding.root){
// 4. Bind your view using binding variables
val name = binding.name
fun bind(){
// 5.access your views
name.setText("ch8n")
}
}
Adapters in fragment always leak when they are not released, so make sure you null the adapter reference
you hold in your fragment's onDestroyView()
.
Binding ViewGroups | CompoundViews | CustomViews 👾
You can use init block to apply your views,

// layout file => avatar_layout.xml
class AvatarView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {
private lateinit var binding: AvatarLayoutBinding
init {
// inflate binding and add as view
binding = AvatarLayoutBinding.inflate(
LayoutInflater.from(context),
this,
true
)
addView(binding.root)
}
fun setData(image: Bitmap, name: String, email: String) = with(binding) {
image.setImageBitmap(image)
name.text = name
email.text = email
}
}
Reducing the boiler plate | Delegate Pattern 👨🏻💻
Curtsy of EpicPandaForce aka Gobar Varadi 🧠, Copy-paste his snippets into your code-base.

After pasting these snippets, Use it like this:
class ProfileActivity : AppCompatActivity() {
private val binding by viewBinding(ProfileLayoutBinding::inflate)
// use binding freely... no need to clear it out!
}
class ProfileFragment : Fragment(R.layout.profile_layout) {
private val binding by viewBinding(ProfileLayoutBinding::bind)
// use binding freely... no need to clear it out!
}
Reducing the boiler plate | Base Classes 👨🏻💻❤️
Make this as your BaseActivity
class
// Author : ChetanGupta.net @Androidbites
abstract class ViewBindingActivity<VB : ViewBinding> : AppCompatActivity() {
private var _binding: ViewBinding? = null
abstract val bindingInflater: (LayoutInflater) -> VB
@Suppress("UNCHECKED_CAST")
protected val binding: VB
get() = _binding as VB
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
_binding = bindingInflater.invoke(layoutInflater)
setContentView(requireNotNull(_binding).root)
setup()
}
abstract fun setup()
override fun onDestroy() {
super.onDestroy()
_binding = null
}
}
Make this as your BaseFragment
// Author : ChetanGupta.net @Androidbites
abstract class ViewBindingFragment<VB : ViewBinding> : Fragment() {
private var _binding: ViewBinding? = null
abstract val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> VB
@Suppress("UNCHECKED_CAST")
protected val binding: VB
get() = _binding as VB
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = bindingInflater.invoke(inflater, container, false)
return requireNotNull(_binding).root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setup()
}
abstract fun setup()
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
Use it like :
class ProfileActivity :ViewBindingActivity<ProfileLayoutBinding>() {
override val bindingInflater: (LayoutInflater) -> ViewBinding
= ProfileLayoutBinding::inflate
override fun setup(){
//.. do stuff with binding variable
}
}
class ProfileFragment :ViewBindingFragment<ProfileLayoutBinding>() {
override val bindingInflater: (LayoutInflater) -> ViewBinding
= ProfileLayoutBinding::inflate
override fun setup(){
//.. do stuff with binding variable
}
}
Bonus | Common mistake | Double Inflating
Double Inflating
In Activity, Calling setContentView(…)
with the layout resource id
instead of binding.root
would causes the layout to be inflated twice and listeners to be installed on the wrong layout object.
// in activity
private lateinit var binding: ProfileLayoutBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.profile_layout) [ ❌ ]
binding = ProfileLayoutBinding.inflate(layoutInflater)
}
// in fragment
class ProfileFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val rootView = inflater.inflate(R.layout.profile_layout,container, false)
return rootView
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val binding = ProfileLayoutBinding
.inflate(LayoutInflater.from(requireContext())) [ ❌ 🙅♀️ dont do it]
}
}
Enjoying the Post?
a clap is much appreciated if you enjoyed. No sign up or cost associated :)
If you want to support my content do consider dropping a tip/coffee/donation💰
Bonus | Anti Pattern in Fragments
// in fragments
private var _binding: ProfileLayoutBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val rootView = inflater.inflate(R.layout.profile_layout,container, false)
_binding = ProfileLayoutBinding.bind(rootView) [ ❌ 🙅♀️ can be done but dont do it]
return rootView
}
Bonus | Anti Pattern in Adapters
// In Adapters
class SampleAdapter : RecyclerView.Adapter<YourViewHolder>(){
...
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): YourViewHolder {
val binding = ProfileLayoutBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
[❌🙅♀️ can be done but dont do it]
return YourViewHolder(binding.root)
}
...
}
class YourViewHolder(
view: View
) : RecyclerView.ViewHolder(view){
//----
[
🧐 can be done but, this is fine upto a point
Reason 1 :
Inflate internally called bind for you, why to waste bind call
and rebind it again?
Reason 2 :
Since you're binding it here now this binding variable is owned
by the instance of viewholder and would be released only when
this instance will be nulled out and GC collected, I don't know
when that happen, isRecycled callback only reuses this viewholder
instance not release it. So anyone expert in recyclerView can
provide some advice would be great, but IMO its okish, but avoid
when you can.
]
val binding = ProfileLayoutBinding.bind(view)
//----
val name = binding.name
fun bind(){
name.setText("ch8n")
}
}
-------------------
------ OR --------
-------------------
class YourViewHolder(
view: View
) : RecyclerView.ViewHolder(view){
fun bind(){
[❌🙅♀️ can be done but dont do it, You on re-binding
everytime, this is bad.]
val binding = ProfileLayoutBinding.bind(view)
binding.name.setText("ch8n")
}
}
Bonus | Cleaner Code
Instead of repeatedly writing binding in front of every view you can use scope functions to make it neat. For Instance,
// In Activities
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ProfileLayoutBinding.inflate(layoutInflater)
setContentView(binding.root)
setup()
}
fun setup() = with(binding){
name = "ch8n"
twitter = "@ch8n2"
// do other stuff..
}
// In fragments
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val binding = ProfileLayoutBinding.bind(view)
binding.text_name.text = "ch8n"
setup(binding)
}
fun setup(binding:ProfileLayoutBinding)= with(binding){
name = "ch8n"
twitter = "@ch8n2"
// do other stuff..
}
Conclusion 💆🏻♀️
That's all to the View-Binding, major concept of using it is to be aware of Bind
and inflate
function.
Hope you find it informative and if you have any feedback
or post request
or want to subscribe to my mailing list
forms are below.
Do consider Clap
to show your appreciation, until next time. Happy Hacking! 👩💻
Enjoying the Post?
a clap is much appreciated if you enjoyed. No sign up or cost associated :)
If you want to support my content do consider dropping a tip/coffee/donation💰
Reach me out :
