ViewBinding Delegate — one line

Petrus Nguyễn Thái Học
3 min readSep 3, 2020

--

https://carbon.now.sh/

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.

  1. Create two extension functions. First function has parameter type (View) -> T , second function has no any parameters. Both functions simply return a FragmentViewBindingDelegate 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 observing viewLifecycleOwnerLiveData, the LiveData will emits ViewLifecycleOwner after every onCreateView calling
  • When receiving a viewLifecycleOwner , we start observing Lifecycle of View. When onDestroy(owner: LifecycleOwner) called, remove Observer and set binding to null by a Handler .
  • Why use Handler here? Because onDestroy(owner: LifecycleOwner) called before Fragment::onDestroyView was called, but we want to be able to use ViewBinding property until the end of Fragment::onDestroyView , use Handler 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. If binding is null and Fragment's View destroyed, throw IllegalStateException to signal invalid usage.
  • Otherwise, call bind lambda to initialize binding 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.

--

--

No responses yet