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
This commit is contained in:
Nik Clayton 2024-03-19 16:42:08 +01:00 committed by GitHub
parent c2fc3d1f08
commit 4f9b283432
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -21,16 +21,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 * https://bladecoder.medium.com/viewlifecyclelazy-and-other-ways-to-avoid-view-memory-leaks-in-android-fragments-4aa982e6e579
* by Christophe Beyls
*/ */
inline fun <T : ViewBinding> AppCompatActivity.viewBinding( inline fun <T : ViewBinding> AppCompatActivity.viewBinding(
@ -39,53 +37,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 viewBindingFactory: (View) -> T,
) : ReadOnlyProperty<Fragment, T> { ) : Lazy<T>, LifecycleEventObserver {
private var binding: T? = null private var binding: T? = null
init { override val value: T
fragment.lifecycle.addObserver( get() {
object : DefaultLifecycleObserver { return binding ?: run {
val viewLifecycleOwnerLiveDataObserver = val newValue = viewBindingFactory(fragment.requireView())
Observer<LifecycleOwner?> { binding = newValue
val viewLifecycleOwner = it ?: return@Observer fragment.viewLifecycleOwner.lifecycle.addObserver(this)
newValue
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
} }
val lifecycle = fragment.viewLifecycleOwner.lifecycle override fun isInitialized() = binding != null
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 } override fun toString() = binding.toString()
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
if (event == Lifecycle.Event.ON_DESTROY) {
binding = null
}
} }
} }
fun <T : ViewBinding> Fragment.viewBinding(viewBindingFactory: (View) -> T) = fun <T : ViewBinding> Fragment.viewBinding(viewBindingFactory: (View) -> T): Lazy<T> =
FragmentViewBindingDelegate(this, viewBindingFactory) ViewLifecycleLazy(this, viewBindingFactory)