ViewBinding vs FindViewById in Android

ViewBinding is relatively new way to “inflate” layout XML files and initialize references to View objects in Android applications. Simply put, ViewBinding is a replacement for calls to findViewById(id) method in your code.

In this post, I’ll show you the basics of ViewBinding and then describe several bad practices related to this framework that I see out there.

What’s the Problem With findViewById

The problem with using findViewById(id) calls is that they feel needlessly tedious and can lead to runtime crashes.

Consider this example:

    private lateinit var btnStart: Button
    private lateinit var edtParameter: EditText
    private lateinit var txtRemainingTime: TextView
    private lateinit var txtName: TextView
    private lateinit var txtStatus: TextView

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        val view = inflater.inflate(R.layout.layout_test, container, false)

        with(view) {
            btnStart = findViewById(R.id.btn_start)
            edtParameter = findViewById(R.id.edt_parameter)
            txtRemainingTime = findViewById(R.id.txt_remaining_time)
            txtName = findViewById(R.id.txt_name)
            txtStatus = findViewById(R.id.txt_status)
        }

        return view
    }

In this simple Fragment I wrote 10 lines of code just to initialize references to my View objects in code. And that’s in addition to defining these Views in XML.

Furthermore, if layout_test XML doesn’t contain R.id.btn_start, or the type of this View isn’t Button, this code will crash at runtime. In most cases, these issues don’t make it into production and you’ll find them during development, but even then you’ll still waste some time understanding and resolving these problems.

Android developers know about the above problems, but we learned to live with them. However, wouldn’t it be great if at least part of them could be solved? Enter ViewBinding.

ViewBinding to the Rescue

To use ViewBindind in your application, first enable this feature in module’s build.gradle file:

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

Once you enable ViewBinding in a module, it will generate special classes based on the contents of each individual layout file in that module. You can use these generated classes to replace findViewById(id) calls.

For example, if I’d use ViewBinding, our example Fragment would be simplified to this:

    private lateinit var binding: LayoutTestBinding

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        val binding = LayoutTestBinding.inflate(inflater, container, false)
        ...
        return binding.root
    }

As you can see, ViewBinding generated LayoutTestBinding class from my layout_test XML file and I use its inflate() static method to get a reference to a binding object. Once I have this object, I can access individual Views in this manner:

    private lateinit var binding: LayoutTestBinding

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        val binding = LayoutTestBinding.inflate(inflater, container, false)

        binding.btnStart.setOnClickListener {
            startTest(binding.edtParameter.text.toString())
            binding.txtStatus.text = "STARTED"
        }

        return binding.root
    }

The inflated instance of LayoutTestBinding class contains references to all the Views from the original layout file, with correct type specification. Therefore, if I’d reference a non-existing View or assume incorrect type, I’d get compiler error right away and my code wouldn’t even build. This is nice, isn’t it?

Readability Concern

Judging by small snippets of code like I showed in the previous section, one can get an impression that ViewBinding is ideal. However, that’s not exactly the case and there are still pitfalls.

Note how in the previous example I preface each access to any View with binding.. This might not seem as a problem in a small code snippet, but it’s a major issue on a larger scale. For example, consider this method from Google’s own IO Scheduler application and note how many times it accesses binding object.

For your convenience, I highlighted all the calls to binding. in this single method:

Evidently, the highlighted code doesn’t add any functionality. It’s just boilerplate that makes reading this code much harder for no reason.

Some developers suggest using Kotlin’s “magic” in this case to wrap such code into with(binding) scoping blocks. This will indeed make binding. calls go away, but will also introduce another level of nesting in every place that uses Views. This is also a very bad idea for long-term readability of your code, so don’t do that.

Proper Way to Use ViewBinding

The only way to use ViewBinding without hurting the readability of your code that I found so far is this:

    private lateinit var btnStart: Button
    private lateinit var edtParameter: EditText
    private lateinit var txtRemainingTime: TextView
    private lateinit var txtName: TextView
    private lateinit var txtStatus: TextView

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        val binding = LayoutTestBinding.inflate(inflater, container, false)

        binding.let {
            btnStart = it.btnStart
            edtParameter = it.edtParameter
            txtRemainingTime = it.txtRemainingTime
            txtName = it.txtName
            txtStatus = it.txtStatus
        }

        btnStart.setOnClickListener {
            startTest(edtParameter.text.toString())
            txtStatus.text = "STARTED"
        }

        return binding.root
    }

Since in many cases you’ll be referencing your Views much more than once, it’s worth writing several lines of code in one place to avoid having lots of harmful boilerplate all over the place.

Surprisingly, if you scroll to the beginning of this post, you’ll realize that this approach is roughly equivalent to just using findViewById(id) calls. Yep, that’s exactly the case. However, note that even though these approaches are similar visually, when you use ViewBinding you also get compile-time verification that protects you from runtime crashes.

Productivity Concern

If ViewBinding is roughly equivalent to using findViewById(id), but safer, then the reasonable conclusion would be to switch to it immediately. However, there is (at least) one more factor to take into account: productivity. To understand what I mean, let’s take a step back and consider the “standard” approach first.

When you add new Views with IDs into XML, there is a background process that compiles all these IDs into the global R.id class. Then you can call view.findViewById(R.id.some_id) to retrieve Views in code.

ViewBinding works similarly, but, in addition to just adding the IDs into a global list, it parses more information from XML and generates code for each layout file. Obviously, this is much more computation-intensive process. Therefore, inevitably, enabling ViewBinding introduces some performance overhead.

Is the overhead of ViewBinding something we should be concerned with? I don’t know for sure. However, I do know that Android Studio lags even on my monstrous computer, especially when using Kotlin. Furthermore, since performance overhead tends to increase with project size, even if ViewBinding is fine today, it might still become a problem in the future, when it will be very time-consuming to get rid of it.

In addition, except for performance concerns, every tool that you add into your application is one more potential source of bugs. Given Google’s notoriously low standard of quality and support, this is also a big concern for me.

All in all, since I’ve never had a real problem with findViewById(id) calls (simple to fix crashes during development once in a while don’t bother me much), I don’t want to take the risk of ViewBinding. Therefore, I’m not going to use this new tool which brings relatively minor benefits in my professional work.

Conclusion

ViewBinding is far from being the first attempt to get rid of findViewById(id) calls in Android applications. Butterknife, DataBinding, Kotlin Synthetics and, probably, more tools, all tried to achieve this “noble” goal. None of these alternatives withstood the test of time, but Android developers wasted a lot of effort learning these approaches, implementing them and then refactoring them out of their code.

After years of debates, hype and churn, findViewById is the last man standing. Luckily, I never used any other approach, so I avoided all this waste.

Does this mean that ViewBinding will also fail and become legacy? I wouldn’t say that because, unlike the other tools, ViewBinding stands on much stronger conceptual foundation. However, it’s evident to me that ViewBinding is not a panacea and there are serious trade-offs to be considered before you adopt this tool.

Check Out My Courses on Udemy

6 comments on "ViewBinding vs FindViewById in Android"

  1. Great post! Thank you for sharing your opinion on this topic.

    Also if we use ViewStub and ViewBinding we may end up not levering on ViewStub features if we bind our inflated view directly (Kotlin you can use Lazy delegate).

    If we do: val inflatedView: View = InflatedBinding.bind(binding.toinflate.inflate()) then ViewStub becomes… unnecessary.

    Reply
  2. I hate how you can’t “Find Usages” anymore on the view id in XML if using view binding. That’s a pretty big drawback IMO.

    Reply
  3. How can anyone look at viewbinding and agree with your assessment of the problems is a mistery to me. All the problems you described are pure fiction of your imagination

    Reply
    • Just saying as a friend, I noticed that continuously typing `bind.{view}` is very irritating and adds unnecessary code affecting the readability. The way the author solved this problem is a good takeaway for me even though it is a little bit wordy. Just saying.

      Reply
  4. What are your opinion on creating a private extension function to access the binding data instead of using `with(binding)`, like below:

    // sample function specifically made to map the header information
    private fun SomeLayoutBinding.mapHeaderInformation(data: HeaderData) {
    title.text = data.title
    subtitle.text = data.subtitle
    userActiveState.isVisible = data.isUserActive
    }

    // for example in onViewCreated
    override fun onViewCreated(….) {
    binding.mapHeaderInformation(headerData)
    }

    At the moment I prefer to use it this way instead of using with(…), especially because I could specifically said that this function is only going to be used to map the header data (as in the example above)

    Reply
    • I guess this could work, but I wouldn’t write code like that. In general, I try to avoid extension functions as much as I can. These are, fundamnetally, static calls.

      Reply

Leave a Comment