-
-
Save Zhuinden/ea3189198938bd16c03db628e084a4fa to your computer and use it in GitHub Desktop.
// 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) |
How about such a getValue
version?
override fun getValue(thisRef: Fragment, property: KProperty<*>): T {
return binding ?: run {
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.")
}
viewBindingFactory(thisRef.requireView()).also {
this.binding = it
}
}
}
to get rid of
val binding = binding
if (binding != null) {
return binding
}
I just had an idea while checking how findViewTreeLifecycleOwner
method works - it just tags root view with LifecycleOwner
.
I think that's an interesting alternative for this use as well:
class VBDelegate<T>(private val bindingFactory: (View) -> T) : ReadOnlyProperty<Fragment, T> {
companion object {
private val key = R.id.content
}
override fun getValue(thisRef: Fragment, property: KProperty<*>): T =
thisRef.requireView().run {
getTag(key)?.let { it as T } ?: bindingFactory(this).also { setTag(key, it) }
}
}
Now there are no listeners, no potential leaks (binding reference is cleared alongside view itself) and you can access it during onDestroyView
safely.
Only caveat is (if you can call it that) you need to use project specific resource ID for tagging purpose.
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)
}
@Drjacky 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.
@Zhuinden You mean just this:
override fun onViewCreated(view: View, savedInstanceState: Bundle) {
super.onViewCreated(view, savedInstanceState)
val binding = binding
}
But still using the binding
from the top variable in lines that are outside of the onViewCreated
:
class FragBlah : Fragment() {
private val binding by viewInflateBinding(FragBlahBinding::inflate)
...
private fun visibilityConnectingContainer() {
binding.txtTitle.visibilityOff() //this binding is the one from the top class variable
}
?
In that case, the problem is that you have some listener that is called even after onDestroyView.
That's not a bug in this code.
@Drjacky I see that you forgot to add the layout name in the class FragBlah : Fragment() . It should be class FragBlah : Fragment(R.layout.your_layout_name).
@flamesoft But we have setContentView(binding.root)
in onCreate
for Activity and = binding.root
in onCreateView
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
How would I use this in DialogFragment?
I just don't use this in a DialogFragment.
@Zhuinden Isn't it possible to add support for it, or have a similar solution for it?
@AndroidDeveloperLB There are two "flavors" of DialogFragment
, they have different view lifecycle. You can find examples for both cases here.
@gmk57 Oh these make sense. Thank you!
sounds like you are missing
val binding = binding
in youronViewCreated
OR you are actually accessing the view afteronDestroyView
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
@Zhuinden what is the real reason behind val binding = binding
in most cases we directly access binding. is this required ? .
@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.
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.
@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.
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
}
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
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
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.
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 inonDestroyView
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
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 inonDestroyView
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
.
You can see in fragment manager source how fragment view destruction is performed:
performDestroyView
- which is the place where lifecycle becomes destroyed andonDestroyView
is callednull
viewLifecycleOwnerLiveData
value is set tonull
I'd conclude that its impossible not to trigger the observer (as long as view itself was set) and say that Leakcanary is just wrong in this case as it's probably just hooking into lifecycle while being unaware of additional observer that'll clear the reference right after lifecycle is destroyed.