Đorđe Milanović
Đorđe Milanović

Reputation: 379

NET MAUI: Updating properties and enabling commands that depend on observable object fields

As we know, RelayCommand and getter-only properties need to be notified when their dependencies change. That is usually done with NotifyCanExecuteChangedForAttribute and NotifyPropertyChangedForAttribute.

But what needs to be done in cases when we access nested object fields, and not primitive types? I know that those classes also need to implement INotifyPropertyChanged. But that is not enough here, because that event isn't propagated to cause reevaluation of CanExecute method.

Here is an example:

There is an object of Bill.Item class that implements INotifyPropertyChanged (I have tried to use both ObservableObjectand Fody.PropertyChange, but there is no difference).

[ObservableProperty]
[NotifyCanExecuteChangedFor(nameof(SaveCommand))]
[NotifyPropertyChangedFor(nameof(NotEnoughDrinks))]
private Bill.Item billItem;

Its properties are updated through UI with XAML two-way bindings:

<Entry Placeholder="Title" Text="{Binding BillItem.Title}" />
...
<HorizontalStackLayout>
    <Entry Placeholder="Quantity" Text="{Binding BillItem.Quantity}" IsReadOnly="True" />
    <Stepper Value="{Binding BillItem.Quantity}" Minimum="1" Increment="1" />
</HorizontalStackLayout>

SaveCommand and NotEnoughDrinks property depend on fields inside BillItem.

public bool NotEnoughDrinks => ChosenDrink?.Quantity < BillItem.Quantity;
[RelayCommand(CanExecute = nameof(IsSaveable))]
private void Save() {
    OnClose?.Invoke(BillItem);
}

private bool IsSaveable() {
    if (string.IsNullOrWhiteSpace(BillItem.Title)) return false;

    return true;
}

I have found a workaround, by subscribing to PropertyChanged event and manually invoking OnPropertyChanged and NotifyCanExecuteChanged. Unfortunately that solution produces COMException and TargetInvocationException from time to time, and I am unable to find the root cause, or any message related to those exceptions. And except from that issue, I assume that this solution is not idiomatic enough because it makes code more complex.

partial void OnBillItemChanged(Bill.Item? oldValue, Bill.Item newValue) 
{
    void OnBillItemPropertyChanged(object? sender, PropertyChangedEventArgs args) 
    {
        try 
        {
            OnPropertyChanged(nameof(NotEnoughDrinks));
            SaveCommand.NotifyCanExecuteChanged();
        } 
        catch (Exception) {}
    }

    if (oldValue is not null)
        oldValue.PropertyChanged -= OnBillItemPropertyChanged;
    if (newValue is not null)
        newValue.PropertyChanged += OnBillItemPropertyChanged;
}

Is there anything that I am missing, for example an attribute, that is useful for these cases when I need to handle updates of nested ObservableObject fields? Or I really need to stick to workarounds like the one shown above?

Upvotes: 0

Views: 115

Answers (0)

Related Questions