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).
This commit is contained in:
Christophe Beyls 2024-02-23 20:10:33 +01:00 committed by GitHub
parent 7e5eef4060
commit a19540f0e4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 24 additions and 49 deletions

View File

@ -4,16 +4,14 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.Observer
import androidx.viewbinding.ViewBinding 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 <T : ViewBinding> AppCompatActivity.viewBinding( inline fun <T : ViewBinding> AppCompatActivity.viewBinding(
@ -22,55 +20,32 @@ inline fun <T : ViewBinding> AppCompatActivity.viewBinding(
bindingInflater(layoutInflater) bindingInflater(layoutInflater)
} }
class FragmentViewBindingDelegate<T : ViewBinding>( private class ViewLifecycleLazy<out T : Any>(
val fragment: Fragment, private val fragment: Fragment,
val viewBindingFactory: (View) -> T private val initializer: (View) -> T
) : ReadOnlyProperty<Fragment, T> { ) : Lazy<T>, LifecycleEventObserver {
private var binding: T? = null private var cached: T? = null
init { override val value: T
fragment.lifecycle.addObserver(object : DefaultLifecycleObserver { get() {
val viewLifecycleOwnerLiveDataObserver = return cached ?: run {
Observer<LifecycleOwner?> { val newValue = initializer(fragment.requireView())
val viewLifecycleOwner = it ?: return@Observer cached = newValue
fragment.viewLifecycleOwner.lifecycle.addObserver(this)
viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver { newValue
override fun onDestroy(owner: LifecycleOwner) {
binding = null
} }
})
} }
override fun onCreate(owner: LifecycleOwner) { override fun isInitialized() = cached != null
fragment.viewLifecycleOwnerLiveData.observeForever(
viewLifecycleOwnerLiveDataObserver
)
}
override fun onDestroy(owner: LifecycleOwner) { override fun toString() = cached.toString()
fragment.viewLifecycleOwnerLiveData.removeObserver(
viewLifecycleOwnerLiveDataObserver
)
}
})
}
override fun getValue(thisRef: Fragment, property: KProperty<*>): T { override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
val binding = binding if (event == Lifecycle.Event.ON_DESTROY) {
if (binding != null) { cached = 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."
)
}
return viewBindingFactory(thisRef.requireView()).also { this.binding = it }
} }
} }
fun <T : ViewBinding> Fragment.viewBinding(viewBindingFactory: (View) -> T) = fun <T : ViewBinding> Fragment.viewBinding(viewBindingFactory: (View) -> T): Lazy<T> =
FragmentViewBindingDelegate(this, viewBindingFactory) ViewLifecycleLazy(this, viewBindingFactory)