How to have [ObservableProperty] in the class which already implements INotifyPropertyChanged itself?

333 views Asked by At

I just simply want to use a [ObservableProperty] for corresponding fields/properties. However, as I am using MvvmCross (what's not so important), my ViewModel already inherited from a base class which does implement INotifyPropertyChanged itself. So, when using this, I am getting error:

//[INotifyPropertyChanged]
public partial class MyVewModel : MvxViewModel
{
    [ObservableProperty] private string name;
   
}

MVVMTK0019: Fields annotated with [ObservableProperty] must be contained in a type that inherits from ObservableObject or that is annotated with [ObservableObject] or [INotifyPropertyChanged] (including base types).

But I can't annotate my class with any of those as if I do I get another error:

MVVMTK0001: Cannot apply [INotifyPropertyChanged] to a type that already declares the INotifyPropertyChanged interface.

The whole situation looks kinda ridiculous. Does that mean that I can't use third-party classes that way then? What's the simplicity of CommunityToolkit.Mvvm in in that case?..

1

There are 1 answers

3
Julian On

You cannot and it doesn't make any sense to do it. Either use MvvmCross or CommunityToolkit.Mvvm, don't mix them within the same class.

You can only use one implementation of INotifyPropertyChanged, because there are conventions that the source generators from the MVVM Community Toolkit use which are not satisfied by other implementations. That's why the source generators rely on the implementation of the ObservableObject base class.

For example, the [ObservableProperty] source generator will generate a property setter that calls the base class's OnPropertyChanged() method. Since this method is not part of the interface, other implementations sometimes name it differently, e.g. RaisePropertyChanged() or NotifyPropertyChanged(), etc. and even implement it slightly differently, or not at all.

Therefore, the resulting auto-generated code wouldn't necessarily compile, because the expected OnPropertyChanged() method with the right signature may not exist. Hence, the errors you see. They ensure that the generator can deal with your code and generate the correct, additional parts of the partial class that are used to notify subscribers of changes to the generated properties.

Let's assume there are two different implementations of IPropertyChanged, let's call them ObservableObject and NotifyObject:

namespace CommunityToolkit.Mvvm;

public class ObservableObject : INotifyPropertyChanged
{
    public event EventHandler<PropertyChangedEventArgs> PropertyChanged;

    protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
    {
        PropertyChanged?.Invoke(new PropertyChangedEventArgs(this, propertyName);
    }
}
namespace MyMvvm;

public class NotifyObject : INotifyPropertyChanged
{
    public event EventHandler<PropertyChangedEventArgs> PropertyChanged;

    protected void RaisePropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(new PropertyChangedEventArgs(this, propertyName);
    }
}

The [ObservableProperty] code generator will always generate a property (in another partial class of the same name) similar to the following for a simple backing field that is decorated with it:

public partial class MyClass
{
    public bool MyBool
    {
        get => _myBool;
        set
        {
            if(_myBool == value) return;
            _myBool = value;
            OnPropertyChanged();
        }
    }
}

This is by convention, that's just how the code generator works. It expects the base class to provide the protected void OnPropertyChanged([CallerMemberName] string propertyName = "") method.

The ObservableObject implementation satisfies this requirement, the NotifyObject one doesn't, because the name and signature don't match. The source generator has no straightforward and surefire way to determine whether the correct method exists in the base class that is used unless it checks that a specific one is used.

Therefore, this works:

using CommunityToolkit.Mvvm;

public partial class MyClass : ObservableObject
{
    [ObservableProperty]
    private bool _myBool;
}

While this doesn't:

using MyMvvm;

// Emits compiler error
public partial class MyClass : NotifyObject
{
    [ObservableProperty]
    private bool _myBool;
}

In order to avoid issues, the code generator only works with partial classes that inherit from the toolkit's implementation of ObservableObject either via the [ObservableObject] or [INotifyPropertyChanged] attributes or by directly inheriting from it, which essentially results in the same thing.

This may become more clear from a blog post I wrote about this about year ago, specifically the Under the hood section.