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.
Dependency Injection with Dagger and Hilt Course
Learn Dependency Injection in Android and master Dagger and Hilt dependency injection frameworks.
Go to CourseConclusion
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.
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.
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.
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
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.
Couldn’t agree more. This is why code gets expensive to maintain and why people want to switch frameworks (because no one knows what is actually going on).
Reasons NOT to do this:
– APK size
– Re-enforces the idea that random extensions are better than your own code
– Android studio already handles the compilation for you so it’s no safer at all anymore
– If you look at Kotlin, large namespaces are the norm — if you want something else do a different language (Jason[ette/elle]? RN)
– Reason posted by John Adams before with inability to search
– Source code no longer looks like compiled code (yes obviously but to a larger and artificially higher degree)
– Inability to use this on complex resources (at least without additional configuration that may not have been done by the original dev, and the new devs may not know anything about binding)
– Can no longer copy and paste from one project to another and expect things to just work
– Setup is not complex but takes longer to read up on and implement than copying and pasting the findViewById will ever take
Reasons to use:
Literally saving zero time because copy and paste is a thing
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)
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.
WHY?
You either use ViewBinding, or use findViewByID. Yes, it will be runtime crash, but we all run the code at least once, right?
The point of ViewBinding is to save the thing you wrote. The type safety is a benefit, but it goes with your trust the Bindings are generated correctly on change. As usual, findViewById’s lack of type safety is a build time speed up in return.
But back to correctness: https://developer.android.com/topic/libraries/view-binding states “Note: Fragments outlive their views. Make sure you clean up any references to the binding class instance in the fragment’s onDestroyView() method.”
So when using a bindings field, this will be much easier to do so (some libraries are able to make this automagically on your behave). But in your case, the lateinit properties are the point of failure. To make it correct, you basically end up in writing ViewBindings framework, involving nullability, etc. So what exactly you got, I don’t know.
Also I noticed you don’t really follow your own SLAP principles, understandably, but still.
Personally, I would go with reversed approach. Define local extensions for concrete ViewBindings if possible and use the Fragment just to connect the dots. And most importantly, if you don’t need any of those views to be updated in runtime (like btnSend, usually will be set once), you don’t need hold them.
PS: Funny fact! You are using ViewBinding for type safety (I guess?) while your xml ids are btn*, txt*, edt* prefixed, so either you don’t trust VB to make its work, or you have a kind of ‘visually noticeable’ type safety based on naming conventions. Since everything observable by human can be checked by lint, you can enforce the ids of views to be prefixed with some predefined rules based on type, then enforce that on the findViewById’s side giving you type safety on demand by running lint.
> You are using ViewBinding for type safety (I guess?) while your xml ids are btn*, txt*, edt* prefixed, so either you don’t trust VB to make its work, or you have a kind of ‘visually noticeable’ type safety based on naming conventions
it’s like declaring var personList: List. there is nothing wrong with that.
Nulling out View references in `onDestroyView()` is missing in your examples.
To lower the headache, you can add getters, like
val txtStatus: TextView get() = binding!!.txtStatus
This gives the same findViewById() verbosity except for nulling out where you can just make `binding = null`.
Hi Mike,
Nulling out View references in onDestroyView isn’t mandatory. I prefer avoiding unneeded code, so I pretty much never do that.
Well, it’s okay when you don’t put your fragments into backstack. Which is true only for “home” fragments.
Oops, I mean the opposite. We don’t put into backstack only top fragments which initiate no transactions.
You don’t have to null-out Views in onDestroyView, regardless of where you Fragment resides. I discussed this aspect in another post, so, if you’re curious, you’re welcome to read it and comment there if you still disagree.
this posts reminds me of js coders who don’t want to use class because they don’t want to “pollute” their code with `this`
I don’t think this is a fair comparison. Classes in JS give you encapsulation, cohesion and reusability, which are very important properties if you leverage them properly. So, devs who pass on that, lose meaningful features. On the other hand, as I explained in this post, ViewBinding gives you very little and the scope of this benefit is miniscule. I don’t think anyone can make a claim that whether to use ViewBinding or not is an important decision. It’s mostly a matter of personal preference at this point.