From a19540f0e4eaca9bdc11e63d1811c4e229b35658 Mon Sep 17 00:00:00 2001 From: Christophe Beyls Date: Fri, 23 Feb 2024 20:10:33 +0100 Subject: [PATCH] Simplify and reduce overhead of lazy view binding in Fragments (#4269) This reduces complexity of view binding inflation in Fragments, and also reduces overhead (no `KProperty` objects need to be generated by the compiler) by implementing `Lazy` instead of `ReadOnlyProperty`. For a full explanation, see this [detailed blog post](https://medium.com/@bladecoder/viewlifecyclelazy-and-other-ways-to-avoid-view-memory-leaks-in-android-fragments-4aa982e6e579). --- .../tusky/util/ViewBindingExtensions.kt | 73 ++++++------------- 1 file changed, 24 insertions(+), 49 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/util/ViewBindingExtensions.kt b/app/src/main/java/com/keylesspalace/tusky/util/ViewBindingExtensions.kt index 86bca03f6..4ea3edeab 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/ViewBindingExtensions.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/ViewBindingExtensions.kt @@ -4,16 +4,14 @@ import android.view.LayoutInflater import android.view.View import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment -import androidx.lifecycle.DefaultLifecycleObserver import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.Observer import androidx.viewbinding.ViewBinding -import kotlin.properties.ReadOnlyProperty -import kotlin.reflect.KProperty /** - * https://medium.com/@Zhuinden/simple-one-liner-viewbinding-in-fragments-and-activities-with-kotlin-961430c6c07c + * Original code: https://medium.com/@Zhuinden/simple-one-liner-viewbinding-in-fragments-and-activities-with-kotlin-961430c6c07c + * Refactor: https://bladecoder.medium.com/viewlifecyclelazy-and-other-ways-to-avoid-view-memory-leaks-in-android-fragments-4aa982e6e579 */ inline fun AppCompatActivity.viewBinding( @@ -22,55 +20,32 @@ inline fun AppCompatActivity.viewBinding( bindingInflater(layoutInflater) } -class FragmentViewBindingDelegate( - val fragment: Fragment, - val viewBindingFactory: (View) -> T -) : ReadOnlyProperty { - private var binding: T? = null +private class ViewLifecycleLazy( + private val fragment: Fragment, + private val initializer: (View) -> T +) : Lazy, LifecycleEventObserver { + private var cached: T? = null - init { - fragment.lifecycle.addObserver(object : DefaultLifecycleObserver { - val viewLifecycleOwnerLiveDataObserver = - Observer { - val viewLifecycleOwner = it ?: return@Observer - - viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver { - override fun onDestroy(owner: LifecycleOwner) { - binding = null - } - }) - } - - override fun onCreate(owner: LifecycleOwner) { - fragment.viewLifecycleOwnerLiveData.observeForever( - viewLifecycleOwnerLiveDataObserver - ) + override val value: T + get() { + return cached ?: run { + val newValue = initializer(fragment.requireView()) + cached = newValue + fragment.viewLifecycleOwner.lifecycle.addObserver(this) + newValue } - - override fun onDestroy(owner: LifecycleOwner) { - fragment.viewLifecycleOwnerLiveData.removeObserver( - viewLifecycleOwnerLiveDataObserver - ) - } - }) - } - - override fun getValue(thisRef: Fragment, property: KProperty<*>): T { - val binding = binding - if (binding != null) { - return binding } - val lifecycle = fragment.viewLifecycleOwner.lifecycle - if (!lifecycle.currentState.isAtLeast(Lifecycle.State.INITIALIZED)) { - throw IllegalStateException( - "Should not attempt to get bindings when Fragment views are destroyed." - ) - } + override fun isInitialized() = cached != null - return viewBindingFactory(thisRef.requireView()).also { this.binding = it } + override fun toString() = cached.toString() + + override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { + if (event == Lifecycle.Event.ON_DESTROY) { + cached = null + } } } -fun Fragment.viewBinding(viewBindingFactory: (View) -> T) = - FragmentViewBindingDelegate(this, viewBindingFactory) +fun Fragment.viewBinding(viewBindingFactory: (View) -> T): Lazy = + ViewLifecycleLazy(this, viewBindingFactory)