Stop using findViewById() in Android!

M S Sandeep Kamath
4 min readJan 20, 2023

--

This article explains why viewBinding should be used instead of our preferred findViewById function.

In every application development be it web, android, desktop applications, we always have to connect the UI to our backend(Logic) code. In android the traditional way to do it is findViewById(viewId). This is similar to Document.getElementById(id) in js. The UI for android is built using XML which has a tree structure(Document Object Model). Therefore each findViewById is a dept first search on that XML tree(source) thus making it in-efficient when there is lot of nesting.

Android logo

Lets consider a layout(UI) named activity_main.xml

<?xml version="1.0" encoding="utf-8"?>

<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">


<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:text="Button"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />

<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Display4"
android:textStyle=""
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Awesome Title" />

<TextView
android:id="@+id/subtext"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="63dp"
android:text="TextView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/title" />

</androidx.constraintlayout.widget.ConstraintLayout>

And lets consider the Activity named MainActivity.kt

package com.example.activity

import android.app.Activity
import com.example.R

class MainActivity : AppCompatActivity() {

private lateinit var button: Button //declare a button object
private lateinit var title: TextView
private lateinit var subtext: TextView

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
button= findViewById(R.id.button) // find for the button by its id
title=findViewById(R.id.title)
subtext=findViewById(R.id.subtext)
button.setOnClickListener{it ->

//..

}

//..

}

}

The more efficient way to do this is View Binding(Part of Android Jetpack).

What is View Binding?

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 simple words its a Java class of the XML layout. In the above example the name of the layout was activity_main.xml, Therefore the corresponding binding class for that XML layout will be ActivityMainBinding.java.

First we will have to enable the view binding in build.gradle (module level).

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

Once enabled for a project, view binding will generate a binding class for all of your layouts automatically and the same can be used in Activity, Fragment and Recycler view. So lets have look at its usage,

View Binding in Activity:

package com.example.activity

import android.app.Activity
import com.example.R
import com.example.databinding.ActivityMainBinding


class MainActivity : AppCompatActivity() {

private lateinit var bindingMainActivity: ActivityMainBinding // Declare a object of binding class

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
bindingMainActivity = ActivityMainBinding.inflate(layoutInflater) //Initialize by inflating the layout
setContentView(bindingMainActivity.root) // getRoot() or root returns root view
bindingMainActivity.button.setOnClickListener{it->

//..

}

//..

}

}

View Binding in Fragment:

package com.example.fragment

import android.app.Activity
import com.example.tripfactory_concierge_android.R
import com.example.tripfactory_concierge_android.databinding.FragmentHomeBinding


class HomeFragment : Fragment(),OnClickListener {

private lateinit var bindingHomeFragment: FragmentHomeBinding // Declare a object of binding class

override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?,
): View {

bindingHomeFragment = FragmentHomeBinding.inflate(inflater, container, false) // attach to root is your choice(true/false)
bindingHomeFragment.button.setOnClickListener{it->

//..

}
//..
return bindingHomeFragment.root //return the root view
}

}

There’s even a better and safer implementation for fragment, have a look at it here.

Quick guide:

  • inflate( inflater ) – Use this in an Activity’s onCreate where there is no parent view to pass to the binding object.
  • inflate( inflater, parent, attachToParent ) – Use this in a Fragment or a Recycler View where you need to pass the viewGroup to the binding object.

Internal implementation of binding class:

public final class ActivityMainBinding implements ViewBinding {
@NonNull
private final ConstraintLayout rootView;
@NonNull
public final Button button;
@NonNull
public final TextView subtext;
@NonNull
public final TextView title;

private ActivityMainBinding(@NonNull ConstraintLayout rootView, @NonNull Button button,
@NonNull TextView subtext, @NonNull TextView title) {

//..

}

@NonNull
public static ActivityMainBinding inflate(@NonNull LayoutInflater inflater) {
View root = inflater.inflate(R.layout.activity_main, null, false);
return bind(root);
}

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, But in case of findViewById we could pass a Invalid ID.
  • 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. example: Declaring button object but initializing it findViewById(id of textView)
View binding VS ButterKnife VS Kotlin synthetics(Source: Stack Overflow).

Bonus:

You can access the binding object of the active activity in the fragment. This can be helpful when you have bottom navigation or navigation drawer in the activity and there is a need to access it in your fragment.

Example:

package com.example.fragment

import android.app.Activity
import android.view.LayoutInflater
import android.view.View
import android.view.View.OnClickListener
import android.view.ViewGroup
import androidx.core.view.GravityCompat
import androidx.fragment.app.Fragment
import com.example.tripfactory_concierge_android.R
import com.example.activity.MainActivity
import com.example.FragmentHomeBinding


class HomeFragment : Fragment(),OnClickListener {


private lateinit var bindingHomeFragment: FragmentHomeBinding
private lateinit var bindingMainActivity: ActivityMainBinding

override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?,
): View {
//viewBinding fragment_home
bindingHomeFragment = FragmentHomeBinding.inflate(inflater, container, false)
//viewBinding activity_main
bindingMainActivity=(activity as MainActivity).bindingMainActivity
//Close drawer on navigationDrawerHeader click
bindingMainActivity.vwNavigation.getHeaderView(0).setOnClickListener {
bindingMainActivity.lytDrawer.closeDrawers()
}
//on click declarations
bindingHomeFragment.ivHamburger.setOnClickListener(this)
bindingHomeFragment.lytResturants.setOnClickListener(this)
bindingHomeFragment.lytPlaces.setOnClickListener(this)
bindingHomeFragment.lytRestRooms.setOnClickListener(this)
return bindingHomeFragment.root
}

override fun onClick(v: View?) {
when(v?.id)
{
//Open drawer on hamburgerIcon click
R.id.ivHamburger-> bindingMainActivity.lytDrawer.openDrawer(GravityCompat.START)
R.id.lytResturants->{
intentProvider(RestaurantActivity())
}
R.id.lytPlaces->{
intentProvider(PlaceActivity())
}
R.id.lytRestRooms->{
intentProvider(RestRoomActivity())
}
}
}
private fun intentProvider(activityName:Activity)
{
//Intent to respective activities
val intent= Intent(activity as MainActivity,activityName::class.java)
(activity as MainActivity).startActivity(intent)
}

}

If interested to know more about view binding read it from here. My sincere apologies for any errors in the article, and I hope that reading it will inspire someone to move from Kotlin synthesis to View binding. Thank you.

--

--

M S Sandeep Kamath

Cheerful techie with a passion for Android development, building backends. Let's geek out together! 🚀😄