dotnet / maui

.NET MAUI is the .NET Multi-platform App UI, a framework for building native device applications spanning mobile, tablet, and desktop.
https://dot.net/maui
MIT License
22.04k stars 1.73k forks source link

[BUG] StackLayout Orientation binding is hectic on Andoid platform #20545

Open RsZoli opened 7 months ago

RsZoli commented 7 months ago

Description

I would like to switch the StackLayout's orientation according to device orientation, so in portrait mode, the orientation would be "Vertical", in landscape mode the orientation would be "Horizontal"!

I use this code in XAML:

    <StackLayout Orientation="{Binding StackLayoutOrientation}">

    <Label Text="1"
           BackgroundColor="DarkRed"
           HeightRequest="100"
           WidthRequest="100" />

    <Label Text="2"
           BackgroundColor="DarkGreen"
           HeightRequest="100"
           WidthRequest="100" />

    </StackLayout>

And in the codebehind:

    private StackOrientation _stackLayoutOrientation;
    public StackOrientation StackLayoutOrientation
    {
        get { return _stackLayoutOrientation; }
        set
        {
            if(_stackLayoutOrientation != value )
            {
                _stackLayoutOrientation = value;
                OnPropertyChanged();
            }
        }
    }
    public MainPage()
    {
        DeviceDisplay.Current.MainDisplayInfoChanged += Current_MainDisplayInfoChanged;

        InitializeComponent();

        BindingContext = this;
    }
    private void Current_MainDisplayInfoChanged(object? sender, DisplayInfoChangedEventArgs e)
    {
        if(e.DisplayInfo.Orientation == DisplayOrientation.Landscape)
        {
            StackLayoutOrientation = StackOrientation.Horizontal;
        }
        else
        {
            StackLayoutOrientation = StackOrientation.Vertical;
        }
    }

The behaviour is hectic at best!

(I have removed previous large screenshots from here for the sake of readablity, please refer to the video attached in a comment below!)

Steps to Reproduce

Create a new .NET MAUI application and use my code!

Link to public reproduction project repository

https://github.com/RsZoli/GitHubRepros

Version with bug

8.0.3

Is this a regression from previous behavior?

Not sure, did not test other versions

Last version that worked well

Unknown/Other

Affected platforms

iOS, Android

Affected platform versions

No response

Did you find any workaround?

No response

Relevant log output

No response

drasticactions commented 7 months ago

@RsZoli This has nothing to do with the binding. Your issue is MainDisplayInfoChanged and I've updated your example to show this:

https://github.com/drasticactions/MauiRepros/tree/main/MauiStackResize

If you invoke the binding directly by setting it via a click handler, it updates fine. The issue is the rotation and when that event fires. If you have Auto-Rotation set in the emulator, the event for rotation fires as the screen is redrawn. This happens every time you rotate, so your code works correctly (At least in my emulator).

However, if you turn it off, when you rotate it to landscape, you see a button appear in Android letting you rotate the view. If you click it then, you'll see that the event for MainDisplayInfoChanged doesn't fire. When you rotate it back to Portrait, it should then fire the event but it will then give "Landscape", and that's when everything gets screwed up.

It's not the binding, it's that event that's the issue. Also FWIW I tested it on iOS and it worked correctly for me.

drasticactions commented 7 months ago

Also, I just mentioned it here (https://github.com/dotnet/maui/issues/20305#issuecomment-1943187147) and I think it can also apply to your issue:

IMO, if you're basing UI decisions around screen rotations, I would suggest using something like Visual States instead, as they should be calculated when your app is brought back. Also, as Android apps can run inside of their own windows (On Chrome OS and Windows Subsystem for Android, among others), basing UI around full screens and device rotations would probably limit what you want to do.

There are other options for detailing with responsive UI that may work better for what you want to do.

RsZoli commented 7 months ago

@drasticactions Hey! Thank you for your time on this and your input!

Unfortunately, the values I get back after auto rotation is always correct, I have printed them out to the console, so the value of the binded StackOrientation property is always correct as well, but the UI is not always updating accordingly, however sometimes it does, as you can see on my screenshots, out to four I got two right! So the event behaves just fine, at least for me!

I also tried using VisualStates but I got the same result, sometimes the UI just won't update!

What I did not try though is iOS, I will today and I get back with the results!

drasticactions commented 7 months ago

Can you record a video running my sample with what you're seeing then? I can reproduce these conditions with auto-rotate turned off but not with it on.

And when you say you used Visual States, did you use it while using MainDisplayInfoChanged? If it's not, could you make a new issue for it? If so, I think it's the same issue as it is with your binding, MainDisplayInfoChanged is the issue.

RsZoli commented 7 months ago

@drasticactions Yes, i'm using MainDisplayInfoChanged in every scenario!

(I have removed previous large screenshots from here for the sake of readablity, please refer to the video attached in the comment below!)

RsZoli commented 7 months ago

@drasticactions Maybe this will be more self-explanatory, here is a video recording from a Galaxy S21:

https://github.com/dotnet/maui/assets/28480037/c084e60c-b04d-4e1b-bb01-bb50e1a8bb04

RsZoli commented 7 months ago

@drasticactions Dear Friend, i have uploaded a repro presenting this issue! Would you be so kind to look at it: https://github.com/RsZoli/GitHubRepros Also, on iOS, it is working without issue!

Thank you very much!

drasticactions commented 7 months ago

Running your repo project and looking at the video, I think it basically confirms what I said at the start: The issue is MainDisplayInfoChanged and when that event fires, not your binding. The binding itself is correct if you set it yourself, but the MainDisplayInfoChanged event doesn't fire consistently when you expect it to. That may be a bug in MAUI Essentials with that API, or Android itself firing that event. Using it with VisualStates then would also be problematic because it's MainDisplayInfoChanged that's calling it to update, which seems to be the problem here. That's also why I suggest detecting size changes based on widths and heights rather than screen rotation, since even if this API worked consistently, there are other ways to do responsive UI that are more flexible and would probably work better in the end.

Also to be clear, I'm not on the MAUI team. I'm in .NET and I'm helping out when I can. So I may not be fixing this issue myself, but hopefully going into these issues will help those who can or want to.

RsZoli commented 7 months ago

@drasticactions Thank you for trying to help! The only thing I do not understand is that as you can see on the video, the text values are also binded to the UI, and every time the MainDisplayInfoChanged event fires have the correct values!

When it says "Horizontal", than that is the value binded to the StackLayout's orientation as well, and yet 2 out of 10 tries, it stays "Vertical"!

jaosnz-rep commented 6 months ago

Verified on 8.0.14 with sample project, it works fine on the iOS platform, but the error can repro on the Android platform.