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:
parent
7e5eef4060
commit
a19540f0e4
|
@ -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)
|
||||||
|
|
Loading…
Reference in New Issue