Skip to content

Instantly share code, notes, and snippets.

@Zhuinden
Last active June 20, 2024 14:58
Show Gist options
  • Save Zhuinden/ea3189198938bd16c03db628e084a4fa to your computer and use it in GitHub Desktop.
Save Zhuinden/ea3189198938bd16c03db628e084a4fa to your computer and use it in GitHub Desktop.
Fragment view binding delegate
// https://github.com/Zhuinden/fragmentviewbindingdelegate-kt
import android.view.View
import androidx.fragment.app.Fragment
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.Observer
import androidx.viewbinding.ViewBinding
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
class FragmentViewBindingDelegate<T : ViewBinding>(
val fragment: Fragment,
val viewBindingFactory: (View) -> T
) : ReadOnlyProperty<Fragment, T> {
private var binding: T? = null
init {
fragment.lifecycle.addObserver(object : DefaultLifecycleObserver {
val viewLifecycleOwnerLiveDataObserver =
Observer<LifecycleOwner?> {
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
}
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) =
FragmentViewBindingDelegate(this, viewBindingFactory)
@flamesoft
Copy link

I haven't tried this. I have just tested it with fragments and that works. Try to use fragment and see if it works. @Drjacky

@AndroidDeveloperLB
Copy link

How would I use this in DialogFragment?

@Zhuinden
Copy link
Author

I just don't use this in a DialogFragment.

@AndroidDeveloperLB
Copy link

@Zhuinden Isn't it possible to add support for it, or have a similar solution for it?

@gmk57
Copy link

gmk57 commented Dec 23, 2022

@AndroidDeveloperLB There are two "flavors" of DialogFragment, they have different view lifecycle. You can find examples for both cases here.

@AndroidDeveloperLB
Copy link

AndroidDeveloperLB commented Dec 24, 2022

@gmk57 Oh these make sense. Thank you!

@Zhuinden
Copy link
Author

Zhuinden commented Feb 16, 2023

sounds like you are missing val binding = binding in your onViewCreated OR you are actually accessing the view after onDestroyView and potentially have a memory leak in your app anyway.

Btw there is an updated version of this gist as a library in https://github.com/Zhuinden/fragmentviewbindingdelegate-kt

@manju23reddy
Copy link

manju23reddy commented Apr 13, 2023

@Zhuinden what is the real reason behind val binding = binding in most cases we directly access binding. is this required ? .

@Zhuinden
Copy link
Author

Zhuinden commented Apr 13, 2023

@manju23reddy I debugged a guy's code who had trouble with callbacks of a WebView running on a different thread and this was the fix, so I do do it in my code personally.

@ParticleCore
Copy link

ParticleCore commented Jun 20, 2024

I've had to do a modification on this idea that allows us to use the binding right before it gets nullified.

An example scenario is when we need to null the adapter of a recyclerView when a fragment is destroyed. Using this gist will lead to a crash because the life cycle state DESTROYED is set before any of the onDestroy* methods are called inside the fragment, which causes the throw in line 50 of this gist.

I've added an optional onDestroyListener parameter to the class, ex:

class FragmentViewBindingDelegate<T : ViewBinding>(
    val fragment: Fragment,
    val viewBindingFactory: (View) -> T,
    val onDestroyListener: () -> Unit = {}
) : ReadOnlyProperty<Fragment, T> {

Which is then used inside the observer's onDestroy, ex:

                    viewLifecycleOwner.lifecycle.addObserver(object : DefaultLifecycleObserver {
                        override fun onDestroy(owner: LifecycleOwner) {
                            onDestroyListener()
                            binding = null
                        }
                    })

This guarantees that any sync code inside that listener has access to the binding before it is null, which allows me to, for example, remove the adapter of a recyclerView when the fragment is destroyed.

@Zhuinden
Copy link
Author

@ParticleCore i did eventually move this code to a library in https://github.com/Zhuinden/fragmentviewbindingdelegate-kt because these oddities kept coming up inherited from Google's code so it made sense to version it instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment