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.18k stars 1.74k forks source link

Implicit styles in App.xaml are applied too early, before the class's constructor #10162

Open ivan-todorov-progress opened 2 years ago

ivan-todorov-progress commented 2 years ago

Description

This undesired behavior is a major source of problems, which is preventing the usage of implicit styles in many cases.

For example, let's declare following custom component:

public class CustomComponent : ContentView
{
    public static readonly BindableProperty FirstColorProperty = BindableProperty.Create(nameof(FirstColor),
        typeof(Color), typeof(CustomComponent), propertyChanged: OnFirstColorChanged);

    public static readonly BindableProperty SecondColorProperty = BindableProperty.Create(nameof(SecondColor),
        typeof(Color), typeof(CustomComponent), propertyChanged: OnSecondColorChanged);

    public CustomComponent()
    {
        Debug.WriteLine("CustomComponent constructor is called.");
    }

    public Color FirstColor
    {
        get => (Color)this.GetValue(FirstColorProperty);
        set => this.SetValue(FirstColorProperty, value); 
    }

    public Color SecondColor
    {
        get => (Color)this.GetValue(SecondColorProperty);
        set => this.SetValue(SecondColorProperty, value);
    }

    private static void OnFirstColorChanged(BindableObject bindable, object oldValue, object newValue)
    {
        Debug.WriteLine("FirstColor property is set.");
    }

    private static void OnSecondColorChanged(BindableObject bindable, object oldValue, object newValue)
    {
        Debug.WriteLine("SecondColor property is set.");
    }
}

The sole purpose of the above code is to trace the time when the properties are set in respect to the class's constructor.

If we apply an implicit style to this component in the page's resources, everything works as expected, e.g.:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:TestApp"
             x:Class="TestApp.MainPage">
    <ContentPage.Resources>
        <Style TargetType="local:CustomComponent">
            <Setter Property="FirstColor" Value="Red" />
        </Style>
    </ContentPage.Resources>
    <local:CustomComponent />
</ContentPage>

The above XAML code snippet generates the following debug output:

CustomComponent constructor is called.
FirstColor property is set.

First the class's constructor is called, then the FirstColor property is set by the implicit style. This is the normal behavior, every developer would normally expect.

However, if we apply an implicit style to the component in App.xaml, everything runs backwards, .e.g.:

<Application xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:TestApp"
             x:Class="TestApp.App">
    <Application.Resources>
        <Style TargetType="local:CustomComponent">
            <Setter Property="SecondColor" Value="Blue" />
        </Style>
    </Application.Resources>
</Application>

The previous XAML code snippet generates the following debug output:

SecondColor property is set.
CustomComponent constructor is called.

The property SecondColor is set first, and then the class's constructor is called. If the CustomComponent class has some non-trivial initialization logic there, that would break things, because the class is not initialized yet, but we try to alter its state.

The expected behavior is to apply all implicit styles after the class's constructor is called, otherwise it is too difficult or even impossible to cope with a partially initialized class.

Steps to Reproduce

Run the sample project from the provided link and observe the debug output:

SecondColor property is set.
CustomComponent constructor is called.
FirstColor property is set.
  1. The SecondColor property of the CustomComponent class is set before the CustomComponent constructor is called.
  2. The CustomComponent constructor is called.
  3. The FirstColor property of the CustomComponent class is set after the CustomComponent constructor is called.

Link to public reproduction project repository

https://github.com/telerik/ms-samples/tree/main/Maui/AppImplicitStyle

Version with bug

6.0.486 (current)

Last version that worked well

Unknown/Other

Affected platforms

iOS, Android, Windows, macOS

Affected platform versions

N/A

Did you find any workaround?

The only workaround I have found so far is to avoid implicit styles declared in App.xaml entirely. Explicit named styles should be used everywhere instead.

Relevant log output

No response

ghost commented 2 years ago

We've moved this issue to the Backlog milestone. This means that it is not going to be worked on for the coming release. We will reassess the backlog following the current release and consider this item at that time. To learn more about our issue management process and to have better expectation regarding different types of issues you can read our Triage Process.

arivoir commented 1 year ago

I had already reported this issue https://github.com/dotnet/maui/issues/9050. I ended up not using implicit styles

ivan-todorov-progress commented 1 year ago

@arivoir: The issue https://github.com/dotnet/maui/issues/9050 is already closed. This explains why I have not found anything while searching for similar bugs. I completely disagree with Microsoft that implicit styles should call the property setters of the inherited class before the constructor. This is a well-known anti-pattern and there are numerous ways to avoid that. In their current state, implicit styles should be avoided. For this reason, I would prefer to leave this bug as open. If you agree with me, please, add your vote.

homeyf commented 1 year ago

Verified this issue with Visual Studio Enterprise 17.7.0 Preview 2.0. Can repro on windows platform with sample project. https://github.com/ivan-todorov-progress/maui-app-implicit-style-bug image

marchev-prgs commented 1 year ago

This causes a bunch of NREs over here : /

jmichas commented 2 months ago

Completely ridiculous that this is still open and unaddressed. I have been converting a large xam forms project and at every turn I find MAUI stuff that isn't even at base parity with the xam forms it replaced. Chasing down all these little quirks is stupid and a waste of time. If Maui wasn't ready it shouldn't have been released. Ready would be 100% analogous features and performance. I purposely waited as long as I could to convert but now mobile SDKs have forced me to. I thought by now all this would work. So unbelievably disappointed. I wish microsoft never bought Xamarin.