From 4f9b283432df2b067377d60e78e51d2825ed2671 Mon Sep 17 00:00:00 2001 From: Nik Clayton Date: Tue, 19 Mar 2024 16:42:08 +0100 Subject: [PATCH] refactor: Simplify and optimize viewBinding delegate (#544) Based on Christophe Beyl's technique described in https://bladecoder.medium.com/viewlifecyclelazy-and-other-ways-to-avoid-view-memory-leaks-in-android-fragments-4aa982e6e579 --- .../extensions/ViewBindingExtensions.kt | 71 +++++++------------ 1 file changed, 24 insertions(+), 47 deletions(-) diff --git a/core/common/src/main/kotlin/app/pachli/core/common/extensions/ViewBindingExtensions.kt b/core/common/src/main/kotlin/app/pachli/core/common/extensions/ViewBindingExtensions.kt index 564c38554..34140e64b 100644 --- a/core/common/src/main/kotlin/app/pachli/core/common/extensions/ViewBindingExtensions.kt +++ b/core/common/src/main/kotlin/app/pachli/core/common/extensions/ViewBindingExtensions.kt @@ -21,16 +21,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 + * https://bladecoder.medium.com/viewlifecyclelazy-and-other-ways-to-avoid-view-memory-leaks-in-android-fragments-4aa982e6e579 + * by Christophe Beyls */ inline fun AppCompatActivity.viewBinding( @@ -39,53 +37,32 @@ inline fun AppCompatActivity.viewBinding( bindingInflater(layoutInflater) } -class FragmentViewBindingDelegate( - val fragment: Fragment, - val viewBindingFactory: (View) -> T, -) : ReadOnlyProperty { +private class ViewLifecycleLazy( + private val fragment: Fragment, + private val viewBindingFactory: (View) -> T, +) : Lazy, LifecycleEventObserver { private var binding: 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 fun onDestroy(owner: LifecycleOwner) { - fragment.viewLifecycleOwnerLiveData.removeObserver(viewLifecycleOwnerLiveDataObserver) - } - }, - ) - } - - override fun getValue(thisRef: Fragment, property: KProperty<*>): T { - val binding = binding - if (binding != null) { - return binding + override val value: T + get() { + return binding ?: run { + val newValue = viewBindingFactory(fragment.requireView()) + binding = newValue + fragment.viewLifecycleOwner.lifecycle.addObserver(this) + newValue + } } - 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() = binding != null - return viewBindingFactory(thisRef.requireView()).also { this.binding = it } + override fun toString() = binding.toString() + + override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { + if (event == Lifecycle.Event.ON_DESTROY) { + binding = null + } } } -fun Fragment.viewBinding(viewBindingFactory: (View) -> T) = - FragmentViewBindingDelegate(this, viewBindingFactory) +fun Fragment.viewBinding(viewBindingFactory: (View) -> T): Lazy = + ViewLifecycleLazy(this, viewBindingFactory)