Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Toggling FlyoutLayoutBehavior on Android causes the app to crash #18161

Closed
mjo151 opened this issue Oct 19, 2023 · 10 comments · Fixed by #22453 · May be fixed by #24394
Closed

Toggling FlyoutLayoutBehavior on Android causes the app to crash #18161

mjo151 opened this issue Oct 19, 2023 · 10 comments · Fixed by #22453 · May be fixed by #24394

Comments

@mjo151
Copy link

mjo151 commented Oct 19, 2023

Description

Toggling the FlyoutLayoutBehavior property on a Flyout page on Android causes the app to crash. The problem occurs in .NET 8 SR1 and SR2, and this problem also existed in .NET 7.

Steps to Reproduce

  1. Clone the linked repository
  2. Run the app on an Android tablet
  3. Click the "Toggle Flyout Behavior" button
  4. Notice the flyout menu closes (as expected)
  5. Expand the flyout menu and click the "Toggle Flyout Behavior" button
  6. Notice the app crashes

Link to public reproduction project repository

https://github.com/mjo151/maui-flyout-behavior-bug

Version with bug

8.0.0-rc.1.9171

Is this a regression from previous behavior?

No, this is something new

Last version that worked well

Unknown/Other

Affected platforms

Android

Affected platform versions

Android 13 (did not test other Android versions)

Did you find any workaround?

I did not find a workaround for this issue.

Relevant log output

Java.Lang.ClassCastException: androidx.appcompat.widget.LinearLayoutCompat$LayoutParams cannot be cast to androidx.drawerlayout.widget.DrawerLayout$LayoutParams
  at java.lang.ClassCastException: androidx.appcompat.widget.LinearLayoutCompat$LayoutParams cannot be cast to androidx.drawerlayout.widget.DrawerLayout$LayoutParams
  at at androidx.drawerlayout.widget.DrawerLayout.getDrawerViewAbsoluteGravity(DrawerLayout.java:991)
  at at androidx.drawerlayout.widget.DrawerLayout.checkDrawerViewAbsoluteGravity(DrawerLayout.java:996)
  at at androidx.drawerlayout.widget.DrawerLayout$ViewDragCallback.onViewPositionChanged(DrawerLayout.java:2292)
  at at androidx.customview.widget.ViewDragHelper.continueSettling(ViewDragHelper.java:779)
  at at androidx.drawerlayout.widget.DrawerLayout.computeScroll(DrawerLayout.java:1375)
  at at android.view.View.updateDisplayListIfDirty(View.java:22044)
  at at android.view.View.draw(View.java:22925)
  at at android.view.ViewGroup.drawChild(ViewGroup.java:4529)
  at at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4290)
  at at android.view.View.updateDisplayListIfDirty(View.java:22052)
  at at android.view.View.draw(View.java:22925)
  at at android.view.ViewGroup.drawChild(ViewGroup.java:4529)
  at at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4290)
  at at android.view.View.updateDisplayListIfDirty(View.java:22052)
  at at android.view.View.draw(View.java:22925)
  at at android.view.ViewGroup.drawChild(ViewGroup.java:4529)
  at at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4290)
  at at android.view.View.updateDisplayListIfDirty(View.java:22052)
  at at android.view.View.draw(View.java:22925)
  at at android.view.ViewGroup.drawChild(ViewGroup.java:4529)
  at at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4290)
  at at android.view.View.updateDisplayListIfDirty(View.java:22052)
  at at android.view.View.draw(View.java:22925)
  at at android.view.ViewGroup.drawChild(ViewGroup.java:4529)
  at at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4290)
  at at android.view.View.updateDisplayListIfDirty(View.java:22052)
  at at android.view.View.draw(View.java:22925)
  at at android.view.ViewGroup.drawChild(ViewGroup.java:4529)
  at at android.view.ViewGroup.dispatchDraw(ViewGroup.java:4290)
  at at android.view.View.draw(View.java:23197)
  at at com.android.internal.policy.DecorView.draw(DecorView.java:821)
  at at android.view.View.updateDisplayListIfDirty(View.java:22061)
  at at android.view.ThreadedRenderer.updateViewTreeDisplayList(ThreadedRenderer.java:689)
  at at android.view.ThreadedRenderer.updateRootDisplayList(ThreadedRenderer.java:695)
  at at android.view.ThreadedRenderer.draw(ThreadedRenderer.java:793)
  at at android.view.ViewRootImpl.draw(ViewRootImpl.java:4670)
  at at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:4381)
  at at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:3600)
  at at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:2328)
  at at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:9087)
  at at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1231)
  at at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1239)
  at at android.view.Choreographer.doCallbacks(Choreographer.java:899)
  at at android.view.Choreographer.doFrame(Choreographer.java:832)
  at at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1214)
  at at android.os.Handler.handleCallback(Handler.java:942)
  at at android.os.Handler.dispatchMessage(Handler.java:99)
  at at android.os.Looper.loopOnce(Looper.java:201)
  at at android.os.Looper.loop(Looper.java:288)
  at at android.app.ActivityThread.main(ActivityThread.java:7872)
  at at java.lang.reflect.Method.invoke(Native Method)
  at at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
  at at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:936)
@mjo151 mjo151 added the t/bug Something isn't working label Oct 19, 2023
@mattleibow mattleibow added this to the Backlog milestone Oct 19, 2023
@ghost
Copy link

ghost commented Oct 19, 2023

We've added this issue to our backlog, and we will work to address it as time and resources allow. If you have any additional information or questions about this issue, please leave a comment. For additional info about issue management, please read our Triage Process.

@prabhavmehra
Copy link

any updates on this? Facing same issue with a very simple flyout behaviour.

@guyvaio
Copy link

guyvaio commented Aug 17, 2024

Error probably not fixed as written just above.

  • MAUI nugets forced to 8.0.80.
  • Bin and obj folder removed.
  • App remove from tablet.
  • Rebuild.
  • Redeploy.

After changing the orientation of the tablet, exactly the same error occurs (same stack but with different line numbers)

@guyvaio
Copy link

guyvaio commented Aug 19, 2024

I've created a minimal MAUI 8.0.80 app with a Flyoutpage. All test are done on a physical tablet.
To reproduce the problem, just switch from landscape to portrait, and from portrait to landscape. And repeat until the crash occurs. Sometime the crash occurs immediately, sometime after a dozen of rotations.
It seems related to the speed of the rotation.
Sometime, in landscape mode, the split mode does not appear. And at the next rotation back from portrait, it will appear again.
The crash occurs when rotating from portrait to landscape. It seems to be consistent with the cast exception. The panel layout is already a linear panel and the android native code seems to handle at this time the popover panel.

When considering the FlyoutPageRenderer of Xamarin (was working perfectly) https://github.com/xamarin/Xamarin.Forms/blob/5.0.0/Xamarin.Forms.Platform.Android/AppCompat/FlyoutPageRenderer.cs I notice in the code 2 hacks with await Task.Delay(100);

There is no such code in the MAUI FlyoutViewHandler https://github.com/dotnet/maui/blob/main/src/Core/src/Handlers/FlyoutView/FlyoutViewHandler.Android.cs

I don't know if it is related.

The reality however is that it is impossible to release an app on a tablet that crashes when you change the orientation.

@guyvaio
Copy link

guyvaio commented Aug 19, 2024

After investigations, it appears the root cause of this problem is in Maui-main\src\Essentials\src\DeviceDisplay\DeviceDisplay.android.cs

public override async void OnOrientationChanged(int orientation)
{
	await Task.Delay(500);
	onChanged();
}

The code is awaiting half a second (why so long?) but if in the interval another rotation occurs, the FlyoutPage enter in race conditions.

This is not an acceptable solution but if I replace OnOrientationChanged by this code, the error does not occur anymore. And it solves other errors like split mode visible in portrait.

private bool changeIsBusy;
public override async void OnOrientationChanged(int orientation)
{
	if (!changeIsBusy)
	{
		try
		{
			changeIsBusy = true;
			await Task.Delay(500);
			onChanged();
		}
		finally
		{
			changeIsBusy = false;
		}
	}
}

Hoping this will help someone to provide a real solution.

guyvaio added a commit to guyvaio/maui that referenced this issue Aug 20, 2024
Switch from landscape to portrait, and from portrait to landscape repeatedly produces this error: androidx.appcompat.widget.LinearLayoutCompat$LayoutParams cannot be cast to androidx.drawerlayout.widget.DrawerLayout$LayoutParams

The root cause is in the OnOrientationChanged implementation in Android that awaits 0.5 second and creates race conditions if the orientation changes again during this interval.

dotnet#18161
@guyvaio
Copy link

guyvaio commented Aug 20, 2024

Fix proposed: main...guyvaio:maui:patch-1

guyvaio added a commit to guyvaio/maui that referenced this issue Aug 23, 2024
On an Android tablet, switching from landscape to portrait, and from portrait to landscape repeatedly produces this error: androidx.appcompat.widget.LinearLayoutCompat$LayoutParams cannot be cast to androidx.drawerlayout.widget.DrawerLayout$LayoutParams

This error can be easily reproduced on a physical tablet by quickly changing orientation.

dotnet#18161

The change of orientation triggers method UpdateFlyoutBehavior of src/Core/src/Handlers/FlyoutView/FlyoutViewHandler.Android.cs, that calls method LayoutViews to determine if the views are to be created in “side by side” mode or “as flyout”. The problem is after the rotation, DrawerLayout.GetPlatformViewBounds() called (for investigation) in this method shows that the Android view still has the height/width before the rotation. The MAUI code is ahead of the android reality. Waiting 100ms is enough to let Android adapt its interface.

A similar issue had been addressed by Xamarin developers: the comment on line 324 of https://github.com/xamarin/Xamarin.Forms/blob/5.0.0/Xamarin.Forms.Platform.Android/AppCompat/FlyoutPageRenderer.cs is 
//hack : when the orientation changes and we try to close the Flyout on Android		
//sometimes Android picks the width of the screen previous to the rotation
@Brosten
Copy link

Brosten commented Sep 5, 2024

Will this make it to SR9?

@guyvaio
Copy link

guyvaio commented Sep 6, 2024

Temporary fix https://gist.github.com/guyvaio/5ca11a6aa373c1fe486cc9e25137aeec
Hope this helps others.

@Brosten
Copy link

Brosten commented Sep 12, 2024

Why is this issue closed? I see no open issue addressing it. The crash is rather easy to reproduce with latest MAUI.
The workaround by @guyvaio sadly didn't fix it for us either.. :(

@mfazucchi
Copy link

I am also still experiencing this issue on an Android tablet using the latest .NET 8 and 8.0.82 maui compatibility packages.

@github-actions github-actions bot locked and limited conversation to collaborators Oct 14, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.