Skip to content

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

Description

@Axemasta

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:

  • App in light mode
  • Navigate to page with this behavior
  • Behaviors Color property is set to Colors.Blue
  • App in dark mode
  • Navigate to page with this behavior
  • Behaviors Color property is set to Colors.Red

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

  • App in light mode
  • Navigate to page with this behavior
  • Behaviors Color property is set to Colors.Blue
  • Change app to dark mode
  • Behaviors Color property is still set to Colors.Blue

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:

  • Take the following behvior:
#if IOS || MACOS || MACCATALYST
using PlatformView = UIKit.UIView;
#elif ANDROID
using AndroidX.ConstraintLayout.Motion.Widget;

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<VisualElement>
{
    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
<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_

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions