ViewBinding Delegate — one line
View Binding
Since Android Studio 3.6 Canary 11+, ViewBinding
has been available to us. ViewBinding
allows us to easily write code that interacts with Views and replaces legacy findViewBindId
!
We can enable ViewBinding
in a module using 2 lines below:
android {
...
buildFeatures {
viewBinding true
}
}
Problem with Fragment
When using ViewBinding
with Fragment
, a common problem is memory leak. This happened because we keep a reference to ViewBinding
after Fragment’s View
is destroyed (ViewBinding
keep a reference to Fragment's View
as well). How can we solve that problem?
Refer to https://github.com/android/architecture-components-samples/blob/master/ViewBindingSample
We can easily see, in onDestroyView
method, set fragmentBlankBinding
to null
. That breaks reference to Fragment's View
in order to avoid memory leak.
But in a large application, we have to repeat that. It’s awful and sometimes, we forget it and that’s a big problem.
DRY — Don’t repeat yourself
We can write an extension function and use it everywhere easily.
- Create two extension functions. First function has parameter type
(View) -> T
, second function has no any parameters. Both functions simply return aFragmentViewBindingDelegate
instance.
2. Make FragmentViewBindingDelegate
implements ReadOnlyProperty<Fragment, T>
, that can be used for implementing property delegates of read-only properties.
class FragmentViewBindingDelegate<T : ViewBinding>(...) : ReadOnlyProperty<Fragment, T> { ... }private val binding by FragmentViewBindingDelegate(...)
3. Define binding: T?
property. It will be set to null
when Fragment's View
is destroyed.
private var binding: T? = null
4. Make sure the constructor called on Main Thread. If viewBindingBind
lambda is provided, set bind
property equal to it. Otherwise, we use Kotlin Reflection to get bind
static method of T
class (bind
static method have type: static T bind(View view) { ... }
) and use lazy(NONE)
to cache it.
val method by lazy(NONE) {
viewBindingClazz!!.getMethod("bind", View::class.java)
}
fun(view: View): T = method.invoke(null, view) as T
5. To avoid memory leak, we add an observer to the Lifecycle of Fragment
fragment.lifecycle.addObserver(FragmentLifecycleObserver())private inner class FragmentLifecycleObserver : DefaultLifecycleObserver { ... }
- When
onCreate(owner: LifecycleOwner)
called, we start observingviewLifecycleOwnerLiveData
, the LiveData will emitsViewLifecycleOwner
after everyonCreateView
calling
- When receiving a
viewLifecycleOwner
, we start observing Lifecycle of View. WhenonDestroy(owner: LifecycleOwner)
called, remove Observer and setbinding
tonull
by aHandler
. - Why use
Handler
here? BecauseonDestroy(owner: LifecycleOwner)
called beforeFragment::onDestroyView
was called, but we want to be able to useViewBinding
property until the end ofFragment::onDestroyView
, useHandler
to defer this operation until all operation on Main Thread are completed.
6. Finally, we must implements getValue
method
- Ensure all operation executed on Main Thread.
- If
binding
has already been initialized, simply return it immediately. Ifbinding
isnull
andFragment's View
destroyed, throw IllegalStateException to signal invalid usage. - Otherwise, call
bind
lambda to initializebinding
property and cache result for later calling.
Conclusion
ViewBinding
is modern way to interact with Views.- Using
ViewBinding
with Fragment is easy, correct and concise with Kotlin Property Delegate and AndroidX Lifecycle-aware.
All the code in this post can be found at my library:
Thanks for your reading ❤
Notes:
Throws `IllegalStateException`: “Attempt to get view binding when fragment view is destroyed” when accessing delegate property in `onDestroyView`
Since version `1.0.0-alpha03 — Feb 16, 2021`, we cannot access ViewBinding delegate property in `onDestroyView` (this causes many problems). Recommended way is passing a lambda to `onDestroyView: (T.() -> Unit)? = null` parameter of extension functions, eg.