Status Update
Comments
sy...@google.com <sy...@google.com> #2
class FixDrawerLayout @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
) : DrawerLayout(context, attrs) {
fun registerToFragmentLifecycle(fragment: Fragment) {
fragment.viewLifecycleOwner.launchOnLifecycleDestroy {
super.onDetachedFromWindow()
}
}
}
[Deleted User] <[Deleted User]> #3
With markdown the same:
I have a project on SingleActivity. And in manifest android:enableOnBackInvokedCallback="true". I have a Root Fragment with a toolbar to which I attach a DrawerLayout.
<!-- XML Root Fragment -->
<androidx.drawerlayout.widget.DrawerLayout>
<androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.google.android.material.appbar.AppBarLayout>
<com.google.android.material.appbar.MaterialToolbar />
</com.google.android.material.appbar.AppBarLayout>
<androidx.fragment.app.FragmentContainerView />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
<androidx.core.widget.NestedScrollView
android:layout_gravity="start">
</androidx.drawerlayout.widget.DrawerLayout>
On button click in DrawerLayout I change RootFragment and lose all View in this Xml after onDestroyView. I don't call close() or closeDrawer() because I need the old state when I return to this fragment. But!
The DrawerLayout calls onDetachedFromWindow() which calls updateBackInvokedCallbackState() where ViewCompat.isAttachedToWindow(this) returns true and all the following condition is also true.
boolean shouldBeRegistered = visibleDrawer != null
&& currentDispatcher != null
&& getDrawerLockMode(visibleDrawer) == LOCK_MODE_UNLOCKED
&& ViewCompat.isAttachedToWindow(this);
As a consequence, the following code cannot be executed:
} else if (!shouldBeRegistered && mBackInvokedDispatcher != null) {
Api33Impl.tryUnregisterOnBackInvokedCallback(
mBackInvokedDispatcher, mBackInvokedCallback);
mBackInvokedDispatcher = null;
}
Where mBackInvokedDispatcher != null is true, and shouldBeRegistered is true (but !shouldBeRegistered is false).
Consequently, my mechanical back button or slide (back gesture) is blocked and not called on a new Root fragment when the DrawerLayout is Destroyed.
<!-- new Root fragment -->
<androidx.coordinatorlayout.widget.CoordinatorLayout>
<com.google.android.material.appbar.AppBarLayout>
<com.google.android.material.appbar.MaterialToolbar />
</com.google.android.material.appbar.AppBarLayout>
<androidx.fragment.app.FragmentContainerView />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
There may be a workaround, but that's not all. The fact is that LeakCanary reacts to this error, which claims that after registerOnBackInvokedCallback and not called tryUnregisterOnBackInvokedCallback, a memory leak occurs.
fast fix: android:enableOnBackInvokedCallback="false" or downgrade DrawerLayout to 1.1.1 or
sy...@google.com <sy...@google.com>
sy...@google.com <sy...@google.com> #4
What I've seen is that when I show a new fragment as a result of a drawer layout navigation view being selected, back gestures break with `enableOnBackInvokedCallback` on stop working - and I saw the same else if block you mentioned above _not_ get called and result in stale/stuck back handlers consuming the gestures in this scenario.
I was able to delay showing the fragment until after the drawer layout closed, and the issue did not exist there.
The odd thing is - when I try out older drawer layout versions, the bug still exists. So maybe it's something deeper? I'm working to get a sample app setup to demo this, will circle back soon but wanted to add my current observations.
ca...@instantbits.com <ca...@instantbits.com> #5
after the drawer layout closed
According to my requirements, it should not close, since there is a screen animation where you can see that it is open. So the hot fix only helps through custom view.
fun registerToFragmentLifecycle(fragment: Fragment) {
fragment.viewLifecycleOwner.launchOnLifecycleDestroy {
super.onDetachedFromWindow()
}
}
sy...@google.com <sy...@google.com> #6
related to `launchOnLifecycleDestroy` - what is that method? I don't see it anywhere in my repo/online. Is that a custom API? Would be curious what that does!
sy...@google.com <sy...@google.com> #7
Here's my sample app btw -
Again, I'm not sure if our issues are exactly the same, but I get "stuck" not being able to swipe back between fragments after navigating to a fragment starting from a menu item being selected in a Drawer Layout - so it sounds similar...
ca...@instantbits.com <ca...@instantbits.com> #8
launchOnLifecycleDestroy ->
fun(block: suspend CoroutineScope.(Lifecycle.Event) -> Unit) {
coroutineScope.launch {
val observer = LifecycleEventObserver { _, event -> launch { if (Event.ON_DESTROY == event) block(event) } }
addObserver(observer)
try {
awaitCancellation()
} catch (_: CancellationException) {
removeObserver(observer)
}
}
}
sy...@google.com <sy...@google.com> #9
I found that if the drawer layout is part of a fragment, which is in turn replaced with another fragment as part of a navigation action, the bug occurs (back actions are consumed and not forwarded to the app as expected).
However, if you place the drawer layout as part of the parent activity, no bug occurs when you navigate from the drawer layout to another fragment/back/etc.
I assume that's because the drawer layout, when part of the activity, never goes destroyed/removed from the view, so it does not encounter the bug in question.
This is beginning to look like a separate issue related to navigation logic with drawer layouts in fragments/non-activity lifecycles.
ca...@instantbits.com <ca...@instantbits.com> #10
I agree with you completely.
sy...@google.com <sy...@google.com> #11
re...@gmail.com <re...@gmail.com> #12
bu...@gmail.com <bu...@gmail.com> #13
Sent from my Galaxy Tab® A
Description
Which version of the Cast SDK are you using? Cast SDK 3 - I'm targeting Google Play Services 15
Fatal Exception: java.lang.IllegalStateException: GoogleApiClient is not connected yet.
at com.google.android.gms.common.api.internal.zzaj.execute(Unknown Source)
at com.google.android.gms.common.api.internal.zzbd.execute(Unknown Source)
at com.google.android.gms.common.api.internal.zzav.execute(Unknown Source)
at com.google.android.gms.cast.Cast$CastApi$zza.sendMessage(Unknown Source)
at com.google.android.gms.cast.framework.media.RemoteMediaClient$zza.zza(Unknown Source)
at com.google.android.gms.internal.cast.zzcm.zza(Unknown Source)
at com.google.android.gms.internal.cast.zzdh.zza(Unknown Source)
at com.google.android.gms.internal.cast.zzdh.zzda(Unknown Source)
at com.google.android.gms.internal.cast.zzdh.zza(Unknown Source)
at com.google.android.gms.internal.cast.zzdi.zza(Unknown Source)
at com.google.android.gms.internal.cast.zzdn.zza(Unknown Source)
at com.google.android.gms.internal.cast.zzdn.zza(Unknown Source)
at com.google.android.gms.internal.cast.zzdn.zzdc(Unknown Source)
at com.google.android.gms.internal.cast.zzdo.run(Unknown Source)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:241)
at android.app.ActivityThread.main(ActivityThread.java:6274)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)