Last active
February 11, 2025 09:25
-
-
Save Zhuinden/ea3189198938bd16c03db628e084a4fa to your computer and use it in GitHub Desktop.
Fragment view binding delegate
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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) |
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
.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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