Skip to content

Instantly share code, notes, and snippets.

@Zhuinden
Last active February 11, 2025 09:25
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)
@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.

@chinh-phi
Copy link

chinh-phi commented Feb 10, 2025

@Drjacky

Fatal Exception: java.lang.IllegalStateException: Can't access the Fragment View's LifecycleOwner when getView() is null i.e., before onCreateView() or after onDestroyView()
       at androidx.fragment.app.Fragment.getViewLifecycleOwner(Fragment.java:361)
       at net.example.company.utils.views.FragmentViewBindingDelegate.getValue(FragmentViewBindingDelegate.java:61)
       at net.example.company.views.smartdogtrainer.common.FragBlah.getBinding(FragBlah.java:80)
       at net.example.company.views.smartdogtrainer.common.FragBlah.visibilityConnectingContainer(FragBlah.java:295)
       at net.example.company.views.smartdogtrainer.common.FragBlah.resetVisibility(FragBlah.java:289)
       at net.example.company.views.smartdogtrainer.common.FragBlah.showErrorConnection(FragBlah.java:311)
       at net.example.company.views.smartdogtrainer.common.FragBlah.performErrorConnection$lambda-8$lambda-7(FragBlah.java:332)
       at net.example.company.views.smartdogtrainer.common.FragBlah$$InternalSyntheticLambda$0$deef3459a1d7d120afdfd9de73e823d74825ffa0a9101b2fc6484805f55a8632$0.run$bridge(FragBlah.java:13)
       at android.app.Activity.runOnUiThread(Activity.java:7154)
       at net.example.company.views.smartdogtrainer.common.FragBlah.performErrorConnection(FragBlah.java:331)
       at net.example.company.views.smartdogtrainer.common.FragBlah.access$performErrorConnection(FragBlah.java:63)
       at net.example.company.views.smartdogtrainer.common.FragBlah$sdtCollarScannerCallback$1.onStopScanning(FragBlah.java:120)
       at net.example.company.scanner.BleScanner.stopScanningForDevices(BleScanner.java:128)
       at net.example.company.scanner.BleScanner.durationTimeoutScanning(BleScanner.java:116)
       at net.example.company.scanner.BleScanner.access$durationTimeoutScanning(BleScanner.java:15)
       at net.example.company.scanner.BleScanner$durationScanningRunnable$1.run(BleScanner.java:52)
       at android.os.Handler.handleCallback(Handler.java:938)
       at android.os.Handler.dispatchMessage(Handler.java:99)
       at android.os.Looper.loop(Looper.java:246)
       at android.app.ActivityThread.main(ActivityThread.java:8595)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:602)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1130)

And my delegate:

class FragmentViewBindingDelegate<T : ViewBinding>(
    val fragment: Fragment,
    val viewBindingFactory: (Fragment) -> T,
    val cleanUp: ((T?) -> Unit)?
) : ReadOnlyProperty<Fragment, T> {

    // A backing property to hold our value
    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) {
                            cleanUp?.invoke(binding)
                            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).not()) {
            throw IllegalStateException("Should not attempt to get bindings when Fragment views are destroyed.")
        }

        return viewBindingFactory(thisRef).also { this.binding = it }
    }
}

inline fun <T : ViewBinding> Fragment.viewBinding(
    crossinline viewBindingFactory: (View) -> T,
    noinline cleanUp: ((T?) -> Unit)? = null
): FragmentViewBindingDelegate<T> =
    FragmentViewBindingDelegate(this, { f -> viewBindingFactory(f.requireView()) }, cleanUp)

inline fun <T : ViewBinding> Fragment.viewInflateBinding(
    crossinline bindingInflater: (LayoutInflater) -> T,
    noinline cleanUp: ((T?) -> Unit)? = null,
): FragmentViewBindingDelegate<T> =
    FragmentViewBindingDelegate(this, { f -> bindingInflater(f.layoutInflater) }, cleanUp)

inline fun <T : ViewBinding> AppCompatActivity.viewInflateBinding(
    crossinline bindingInflater: (LayoutInflater) -> T
) =
    lazy(LazyThreadSafetyMode.NONE) {
        bindingInflater.invoke(layoutInflater)
    }

Have you found the cause of this error? I also encountered this error when implementing it in a fragment.
My code below:
override val binding by viewBinding {
rcData.adapter = null
}

@Drjacky
Copy link

Drjacky commented Feb 10, 2025

Have you found the cause of this error? I also encountered this error when implementing it in a fragment. My code below: override val binding by viewBinding { rcData.adapter = null }

@chinh-phi In getValue, had to use thisRef instead of view: https://github.com/Drjacky/MVVMTemplate/blob/6b9a39454e9260b6d979ca5c02f633e5e2d4bd2d/presentation/src/main/kotlin/app/web/drjackycv/presentation/extension/Extensions.kt

@chinh-phi
Copy link

Have you found the cause of this error? I also encountered this error when implementing it in a fragment. My code below: override val binding by viewBinding { rcData.adapter = null }

@chinh-phi In getValue, had to use thisRef instead of view: https://github.com/Drjacky/MVVMTemplate/blob/6b9a39454e9260b6d979ca5c02f633e5e2d4bd2d/presentation/src/main/kotlin/app/web/drjackycv/presentation/extension/Extensions.kt

Actually, it encounters an error when executing the following code even though onDestroyView has already been called:
binding.rvData.post {
binding.rvData.layoutManager?.scrollToPosition(0)
}
Do you know any way to fix it?. Thanks

@Zhuinden
Copy link
Author

@Drjacky

Fatal Exception: java.lang.IllegalStateException: Can't access the Fragment View's LifecycleOwner when getView() is null i.e., before onCreateView() or after onDestroyView()
       at androidx.fragment.app.Fragment.getViewLifecycleOwner(Fragment.java:361)
       at net.example.company.utils.views.FragmentViewBindingDelegate.getValue(FragmentViewBindingDelegate.java:61)
       at net.example.company.views.smartdogtrainer.common.FragBlah.getBinding(FragBlah.java:80)
       at net.example.company.views.smartdogtrainer.common.FragBlah.visibilityConnectingContainer(FragBlah.java:295)
       at net.example.company.views.smartdogtrainer.common.FragBlah.resetVisibility(FragBlah.java:289)
       at net.example.company.views.smartdogtrainer.common.FragBlah.showErrorConnection(FragBlah.java:311)
       at net.example.company.views.smartdogtrainer.common.FragBlah.performErrorConnection$lambda-8$lambda-7(FragBlah.java:332)
       at net.example.company.views.smartdogtrainer.common.FragBlah$$InternalSyntheticLambda$0$deef3459a1d7d120afdfd9de73e823d74825ffa0a9101b2fc6484805f55a8632$0.run$bridge(FragBlah.java:13)
       at android.app.Activity.runOnUiThread(Activity.java:7154)
       at net.example.company.views.smartdogtrainer.common.FragBlah.performErrorConnection(FragBlah.java:331)
       at net.example.company.views.smartdogtrainer.common.FragBlah.access$performErrorConnection(FragBlah.java:63)
       at net.example.company.views.smartdogtrainer.common.FragBlah$sdtCollarScannerCallback$1.onStopScanning(FragBlah.java:120)
       at net.example.company.scanner.BleScanner.stopScanningForDevices(BleScanner.java:128)
       at net.example.company.scanner.BleScanner.durationTimeoutScanning(BleScanner.java:116)
       at net.example.company.scanner.BleScanner.access$durationTimeoutScanning(BleScanner.java:15)
       at net.example.company.scanner.BleScanner$durationScanningRunnable$1.run(BleScanner.java:52)
       at android.os.Handler.handleCallback(Handler.java:938)
       at android.os.Handler.dispatchMessage(Handler.java:99)
       at android.os.Looper.loop(Looper.java:246)
       at android.app.ActivityThread.main(ActivityThread.java:8595)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:602)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1130)

And my delegate:

class FragmentViewBindingDelegate<T : ViewBinding>(
    val fragment: Fragment,
    val viewBindingFactory: (Fragment) -> T,
    val cleanUp: ((T?) -> Unit)?
) : ReadOnlyProperty<Fragment, T> {

    // A backing property to hold our value
    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) {
                            cleanUp?.invoke(binding)
                            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).not()) {
            throw IllegalStateException("Should not attempt to get bindings when Fragment views are destroyed.")
        }

        return viewBindingFactory(thisRef).also { this.binding = it }
    }
}

inline fun <T : ViewBinding> Fragment.viewBinding(
    crossinline viewBindingFactory: (View) -> T,
    noinline cleanUp: ((T?) -> Unit)? = null
): FragmentViewBindingDelegate<T> =
    FragmentViewBindingDelegate(this, { f -> viewBindingFactory(f.requireView()) }, cleanUp)

inline fun <T : ViewBinding> Fragment.viewInflateBinding(
    crossinline bindingInflater: (LayoutInflater) -> T,
    noinline cleanUp: ((T?) -> Unit)? = null,
): FragmentViewBindingDelegate<T> =
    FragmentViewBindingDelegate(this, { f -> bindingInflater(f.layoutInflater) }, cleanUp)

inline fun <T : ViewBinding> AppCompatActivity.viewInflateBinding(
    crossinline bindingInflater: (LayoutInflater) -> T
) =
    lazy(LazyThreadSafetyMode.NONE) {
        bindingInflater.invoke(layoutInflater)
    }

Have you found the cause of this error? I also encountered this error when implementing it in a fragment. My code below: override val binding by viewBinding { rcData.adapter = null }

Stop trying to update views after the views are destroyed, check with if(isAdded()) {} or cancel your task in onDestroyView so that this doesn't happen.

You can also wrap it in a try-catch if you just want to ignore it.

@chinh-phi
Copy link

chinh-phi commented Feb 11, 2025

@Drjacky

Fatal Exception: java.lang.IllegalStateException: Can't access the Fragment View's LifecycleOwner when getView() is null i.e., before onCreateView() or after onDestroyView()
       at androidx.fragment.app.Fragment.getViewLifecycleOwner(Fragment.java:361)
       at net.example.company.utils.views.FragmentViewBindingDelegate.getValue(FragmentViewBindingDelegate.java:61)
       at net.example.company.views.smartdogtrainer.common.FragBlah.getBinding(FragBlah.java:80)
       at net.example.company.views.smartdogtrainer.common.FragBlah.visibilityConnectingContainer(FragBlah.java:295)
       at net.example.company.views.smartdogtrainer.common.FragBlah.resetVisibility(FragBlah.java:289)
       at net.example.company.views.smartdogtrainer.common.FragBlah.showErrorConnection(FragBlah.java:311)
       at net.example.company.views.smartdogtrainer.common.FragBlah.performErrorConnection$lambda-8$lambda-7(FragBlah.java:332)
       at net.example.company.views.smartdogtrainer.common.FragBlah$$InternalSyntheticLambda$0$deef3459a1d7d120afdfd9de73e823d74825ffa0a9101b2fc6484805f55a8632$0.run$bridge(FragBlah.java:13)
       at android.app.Activity.runOnUiThread(Activity.java:7154)
       at net.example.company.views.smartdogtrainer.common.FragBlah.performErrorConnection(FragBlah.java:331)
       at net.example.company.views.smartdogtrainer.common.FragBlah.access$performErrorConnection(FragBlah.java:63)
       at net.example.company.views.smartdogtrainer.common.FragBlah$sdtCollarScannerCallback$1.onStopScanning(FragBlah.java:120)
       at net.example.company.scanner.BleScanner.stopScanningForDevices(BleScanner.java:128)
       at net.example.company.scanner.BleScanner.durationTimeoutScanning(BleScanner.java:116)
       at net.example.company.scanner.BleScanner.access$durationTimeoutScanning(BleScanner.java:15)
       at net.example.company.scanner.BleScanner$durationScanningRunnable$1.run(BleScanner.java:52)
       at android.os.Handler.handleCallback(Handler.java:938)
       at android.os.Handler.dispatchMessage(Handler.java:99)
       at android.os.Looper.loop(Looper.java:246)
       at android.app.ActivityThread.main(ActivityThread.java:8595)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:602)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1130)

And my delegate:

class FragmentViewBindingDelegate<T : ViewBinding>(
    val fragment: Fragment,
    val viewBindingFactory: (Fragment) -> T,
    val cleanUp: ((T?) -> Unit)?
) : ReadOnlyProperty<Fragment, T> {

    // A backing property to hold our value
    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) {
                            cleanUp?.invoke(binding)
                            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).not()) {
            throw IllegalStateException("Should not attempt to get bindings when Fragment views are destroyed.")
        }

        return viewBindingFactory(thisRef).also { this.binding = it }
    }
}

inline fun <T : ViewBinding> Fragment.viewBinding(
    crossinline viewBindingFactory: (View) -> T,
    noinline cleanUp: ((T?) -> Unit)? = null
): FragmentViewBindingDelegate<T> =
    FragmentViewBindingDelegate(this, { f -> viewBindingFactory(f.requireView()) }, cleanUp)

inline fun <T : ViewBinding> Fragment.viewInflateBinding(
    crossinline bindingInflater: (LayoutInflater) -> T,
    noinline cleanUp: ((T?) -> Unit)? = null,
): FragmentViewBindingDelegate<T> =
    FragmentViewBindingDelegate(this, { f -> bindingInflater(f.layoutInflater) }, cleanUp)

inline fun <T : ViewBinding> AppCompatActivity.viewInflateBinding(
    crossinline bindingInflater: (LayoutInflater) -> T
) =
    lazy(LazyThreadSafetyMode.NONE) {
        bindingInflater.invoke(layoutInflater)
    }

Have you found the cause of this error? I also encountered this error when implementing it in a fragment. My code below: override val binding by viewBinding { rcData.adapter = null }

Stop trying to update views after the views are destroyed, check with if(isAdded()) {} or cancel your task in onDestroyView so that this doesn't happen.

You can also wrap it in a try-catch if you just want to ignore it.

i call:
binding.rvData.post {
binding.rvData.layoutManager?.scrollToPosition(0)
}
onViewCreated(). However, for some reason, it is not executed immediately but instead runs after onDestroyView is called.
=> crash
Do you have any way to fix it? Thanks

@Zhuinden
Copy link
Author

@Drjacky

Fatal Exception: java.lang.IllegalStateException: Can't access the Fragment View's LifecycleOwner when getView() is null i.e., before onCreateView() or after onDestroyView()
       at androidx.fragment.app.Fragment.getViewLifecycleOwner(Fragment.java:361)
       at net.example.company.utils.views.FragmentViewBindingDelegate.getValue(FragmentViewBindingDelegate.java:61)
       at net.example.company.views.smartdogtrainer.common.FragBlah.getBinding(FragBlah.java:80)
       at net.example.company.views.smartdogtrainer.common.FragBlah.visibilityConnectingContainer(FragBlah.java:295)
       at net.example.company.views.smartdogtrainer.common.FragBlah.resetVisibility(FragBlah.java:289)
       at net.example.company.views.smartdogtrainer.common.FragBlah.showErrorConnection(FragBlah.java:311)
       at net.example.company.views.smartdogtrainer.common.FragBlah.performErrorConnection$lambda-8$lambda-7(FragBlah.java:332)
       at net.example.company.views.smartdogtrainer.common.FragBlah$$InternalSyntheticLambda$0$deef3459a1d7d120afdfd9de73e823d74825ffa0a9101b2fc6484805f55a8632$0.run$bridge(FragBlah.java:13)
       at android.app.Activity.runOnUiThread(Activity.java:7154)
       at net.example.company.views.smartdogtrainer.common.FragBlah.performErrorConnection(FragBlah.java:331)
       at net.example.company.views.smartdogtrainer.common.FragBlah.access$performErrorConnection(FragBlah.java:63)
       at net.example.company.views.smartdogtrainer.common.FragBlah$sdtCollarScannerCallback$1.onStopScanning(FragBlah.java:120)
       at net.example.company.scanner.BleScanner.stopScanningForDevices(BleScanner.java:128)
       at net.example.company.scanner.BleScanner.durationTimeoutScanning(BleScanner.java:116)
       at net.example.company.scanner.BleScanner.access$durationTimeoutScanning(BleScanner.java:15)
       at net.example.company.scanner.BleScanner$durationScanningRunnable$1.run(BleScanner.java:52)
       at android.os.Handler.handleCallback(Handler.java:938)
       at android.os.Handler.dispatchMessage(Handler.java:99)
       at android.os.Looper.loop(Looper.java:246)
       at android.app.ActivityThread.main(ActivityThread.java:8595)
       at java.lang.reflect.Method.invoke(Method.java)
       at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:602)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1130)

And my delegate:

class FragmentViewBindingDelegate<T : ViewBinding>(
    val fragment: Fragment,
    val viewBindingFactory: (Fragment) -> T,
    val cleanUp: ((T?) -> Unit)?
) : ReadOnlyProperty<Fragment, T> {

    // A backing property to hold our value
    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) {
                            cleanUp?.invoke(binding)
                            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).not()) {
            throw IllegalStateException("Should not attempt to get bindings when Fragment views are destroyed.")
        }

        return viewBindingFactory(thisRef).also { this.binding = it }
    }
}

inline fun <T : ViewBinding> Fragment.viewBinding(
    crossinline viewBindingFactory: (View) -> T,
    noinline cleanUp: ((T?) -> Unit)? = null
): FragmentViewBindingDelegate<T> =
    FragmentViewBindingDelegate(this, { f -> viewBindingFactory(f.requireView()) }, cleanUp)

inline fun <T : ViewBinding> Fragment.viewInflateBinding(
    crossinline bindingInflater: (LayoutInflater) -> T,
    noinline cleanUp: ((T?) -> Unit)? = null,
): FragmentViewBindingDelegate<T> =
    FragmentViewBindingDelegate(this, { f -> bindingInflater(f.layoutInflater) }, cleanUp)

inline fun <T : ViewBinding> AppCompatActivity.viewInflateBinding(
    crossinline bindingInflater: (LayoutInflater) -> T
) =
    lazy(LazyThreadSafetyMode.NONE) {
        bindingInflater.invoke(layoutInflater)
    }

Have you found the cause of this error? I also encountered this error when implementing it in a fragment. My code below: override val binding by viewBinding { rcData.adapter = null }

Stop trying to update views after the views are destroyed, check with if(isAdded()) {} or cancel your task in onDestroyView so that this doesn't happen.
You can also wrap it in a try-catch if you just want to ignore it.

i call: binding.rvData.post { binding.rvData.layoutManager?.scrollToPosition(0) } onViewCreated(). However, for some reason, it is not executed immediately but instead runs after onDestroyView is called. => crash Do you have any way to fix it? Thanks

You're using handler.post {}, so it's possible for it to run after onDestroyView.

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