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.21k stars 1.75k forks source link

AppThemeBindings don't update value when used on a PlatformBehavior #24444

Open Axemasta opened 2 months ago

Axemasta commented 2 months ago

Description

This is another issue with the TouchBehavior which is affecting users.

I have been able to replicate the issue outside of the TouchBehavior so I will report it seperately.

Given the following PlatformBehavior:

public partial class ColorBehavior : PlatformBehavior<VisualElement>
{
    public static readonly BindableProperty ColorProperty = BindableProperty.Create(
        nameof(Color),
        typeof(Color),
        typeof(ColorBehavior));

    public Color? Color
    {
        get => (Color?)GetValue(ColorProperty);
        set => SetValue(ColorProperty, value);
    }
}

If we use an app theme binding to set the Color property:

<ContentView HeightRequest="300" WidthRequest="300">
    <ContentView.Behaviors>
        <behaviors:ColorBehavior Color="{AppThemeBinding Light=Blue, Dark=Red}"/>
    </ContentView.Behaviors>
</ContentView>

When we navigate to a page that uses this behavior, the correct color will be set based on the app theme:

This doesn't work if the app theme changes at runtime. The old value will be used:

Hooking into the theme changed api, we can respond to changes in theme however the value of the property does not change. Its also worth noting that the property changed events that handle changing the color do not fire when the theme is switched, it only happens if you manually hook the theme changes.

Since there are no warnings or exceptions available, its hard to know why this isn't happening.

Steps to Reproduce

Either run the repro provided or:

using PlatformView = Android.Views.View;

elif WINDOWS

using PlatformView = Microsoft.UI.Xaml.FrameworkElement;

elif TIZEN

using PlatformView = Tizen.NUI.BaseComponents.View;

elif NET6_0_OR_GREATER || (NETSTANDARD || !PLATFORM)

using PlatformView = System.Object;

endif

using System.Diagnostics;

namespace TouchBehaviorRelativeBinding.Behaviors;

public partial class ColorBehavior : PlatformBehavior { public static readonly BindableProperty ColorProperty = BindableProperty.Create( nameof(Color), typeof(Color), typeof(ColorBehavior), null , propertyChanged: UpdateColor);

public Color? Color
{
    get => (Color?)GetValue(ColorProperty);
    set => SetValue(ColorProperty, value);
}

private static void UpdateColor(BindableObject bindable, object oldvalue, object newvalue)
{
    if (bindable is ColorBehavior colorBehavior)
    {
        colorBehavior.UpdateColor();
    }
}

private void UpdateColor()
{
    if (View is null)
    {
        return;
    }

    View.BackgroundColor = Color;

    AnnounceColors();
}

protected VisualElement? View { get; set; }

protected override void OnAttachedTo(VisualElement bindable, PlatformView platformView)
{
    base.OnAttachedTo(bindable, platformView);

    View = bindable;
    UpdateColor();

    Application.Current.RequestedThemeChanged += OnAppThemeChanged;
}

private void OnAppThemeChanged(object? sender, AppThemeChangedEventArgs e)
{
    UpdateColor();
}

private void AnnounceColors()
{
    var isRed = Color == Colors.Red; // Dark
    var isBlue = Color == Colors.Blue; // Light

    if (isRed)
    {
        Debug.WriteLine($"The color was red (dark) when app theme is {Application.Current.RequestedTheme}");
    }
    else if (isBlue)
    {
        Debug.WriteLine($"The color was blue (light) when app theme is {Application.Current.RequestedTheme}");
    }
    else
    {
        throw new InvalidOperationException("The color was not set to red or blue...");
    }
}

}

- Apply it to a view:
```xml
```xml
<ContentView HeightRequest="300" WidthRequest="300">
    <ContentView.Behaviors>
        <behaviors:ColorBehavior Color="{AppThemeBinding Light=Blue, Dark=Red}"/>
    </ContentView.Behaviors>
</ContentView>

- Load the page the view is on and observe the correct color based on theme
- Manually change the theme of your device (ie light to dark)
- Visit the app again
- Observe the color has not changed on this view, but other views have changed (if they exist)

### Link to public reproduction project repository

https://github.com/Axemasta/TouchBehaviorRelativeSource/tree/appthemebinding

### Version with bug

8.0.80 SR8

### 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, Windows, macOS

### Affected platform versions

All

### Did you find any workaround?

No

### Relevant log output

_No response_
github-actions[bot] commented 2 months ago

Hi I'm an AI powered bot that finds similar issues based off the issue title.

Please view the issues below to see if they solve your problem, and if the issue describes your problem please consider closing this one and thumbs upping the other issue to help us prioritize it. Thank you!

Open similar issues:

Closed similar issues:

Note: You can give me feedback by thumbs upping or thumbs downing this comment.

jaosnz-rep commented 2 months ago

I can repro this issue at Windows platform on the latest 17.12.0 Preview 1.0(8.0.80 & 8.0.72). Sample project: https://github.com/Axemasta/TouchBehaviorRelativeSource/tree/appthemebinding

Axemasta commented 2 months ago

I've looked into this running maui locally. It looks like the OnAppThemeChanged listener is never invoked for the behavior (only, everything else works). If I manually add a call to RequestedThemeChanged, this starts working again so I'll investigate further as to why it doesn't apply to the behavior!

public AppThemeProxy(Element parent, AppThemeBinding binding)
{
    _parent = parent;
    Binding = binding;
    this.SetDynamicResource(AppThemeProperty, AppThemeResource);
    ((IElementDefinition)parent)?.AddResourcesChangedListener(OnParentResourcesChanged);

    if (Application.Current is not null)
    {
        Application.Current.RequestedThemeChanged += (s, e) =>
        {
            OnAppThemeChanged();
        };
    }
}