From 74509ed8c3505f7cdd3c067b9afcc7ff2438ad08 Mon Sep 17 00:00:00 2001 From: Christophe Beyls Date: Fri, 23 Feb 2024 12:57:43 +0100 Subject: [PATCH 1/2] simplify and reduce overhead of lazy viewBinding in Fragments --- .../tusky/util/ViewBindingExtensions.kt | 70 ++++++------------- 1 file changed, 22 insertions(+), 48 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 86bca03f66..f9b34bb4bf 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/ViewBindingExtensions.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/ViewBindingExtensions.kt @@ -4,13 +4,10 @@ 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 @@ -22,55 +19,32 @@ inline fun AppCompatActivity.viewBinding( bindingInflater(layoutInflater) } -class FragmentViewBindingDelegate( - val fragment: Fragment, - val viewBindingFactory: (View) -> T -) : ReadOnlyProperty { - 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 - ) +private class ViewLifecycleLazy( + private val fragment: Fragment, + private val initializer: (View) -> T +) : Lazy, LifecycleEventObserver { + private var cached: T? = null + + 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 isInitialized() = cached != null - override fun getValue(thisRef: Fragment, property: KProperty<*>): T { - val binding = binding - if (binding != null) { - return binding - } + override fun toString() = cached.toString() - 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 onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { + if (event == Lifecycle.Event.ON_DESTROY) { + cached = null } - - return viewBindingFactory(thisRef.requireView()).also { this.binding = it } } } -fun Fragment.viewBinding(viewBindingFactory: (View) -> T) = - FragmentViewBindingDelegate(this, viewBindingFactory) +fun Fragment.viewBinding(viewBindingFactory: (View) -> T): Lazy = + ViewLifecycleLazy(this, viewBindingFactory) From 17990526c1804a422aa0e950ebfb4b61b81d2c0c Mon Sep 17 00:00:00 2001 From: Christophe Beyls Date: Fri, 23 Feb 2024 17:20:06 +0100 Subject: [PATCH 2/2] add URL to the article explaining the refactoring rationale --- .../java/com/keylesspalace/tusky/util/ViewBindingExtensions.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 f9b34bb4bf..4ea3edeabc 100644 --- a/app/src/main/java/com/keylesspalace/tusky/util/ViewBindingExtensions.kt +++ b/app/src/main/java/com/keylesspalace/tusky/util/ViewBindingExtensions.kt @@ -10,7 +10,8 @@ import androidx.lifecycle.LifecycleOwner import androidx.viewbinding.ViewBinding /** - * 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(