CommunityToolkit / dotnet

.NET Community Toolkit is a collection of helpers and APIs that work for all .NET developers and are agnostic of any specific UI platform. The toolkit is maintained and published by Microsoft, and part of the .NET Foundation.
https://docs.microsoft.com/dotnet/communitytoolkit/?WT.mc_id=dotnet-0000-bramin
Other
3.05k stars 300 forks source link

RelayCommand and AsyncRelayCommand - CanExecuteChanged and PropertyChanged events on main thread #777

Open KrzysztofFryzlewicz opened 1 year ago

KrzysztofFryzlewicz commented 1 year ago

Overview

In Xamarin bindings to UI elements have to be invoked on main thread no matter what context the changes were invoked on. Proposed solution is to add virtual methods that encapsulate CanExecuteChanged (both AsyncRelayCommand and RelayCommand) and PropertyChanged (AsyncRelayCommand) invocation, similarly to ObservableObject.OnPropertyChanged or ObservableObject.OnPropertyChanging. Currently there is NotifyCanExecuteChanged method, but not virtual and it is only used once AsyncRelayCommand (should be for all usages of this event). Also both classes should not be sealed to enable inheritance. Similarly for RelayCommand and AsyncRelayCommand.

API breakdown

namespace CommunityToolkit.Mvvm.Input;

public class RelayCommand : IRelayCommand
{
    public virtual void NotifyCanExecuteChanged()
    {
        CanExecuteChanged?.Invoke(this, EventArgs.Empty);
    }
}

public class AsyncRelayCommand : IAsyncRelayCommand, ICancellationAwareCommand
{
    public virtual void NotifyCanExecuteChanged()
    {
        CanExecuteChanged?.Invoke(this, EventArgs.Empty);
    }

    protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        ArgumentNullException.ThrowIfNull(e);

        PropertyChanged?.Invoke(this, e);
    }
}

Usage example

public class DerivedRelayCommand : RelayCommand
{
    public override NotifyCanExecuteChanged()
    {
        SomeMainThreadDispatcher.InvokeOnMainThread(() => base.NotifyCanExecuteChanged());
    }
}

public class DerivedAsyncRelayCommand : AsyncRelayCommand
{
    public override NotifyCanExecuteChanged()
    {
        SomeMainThreadDispatcher.InvokeOnMainThread(() => base.NotifyCanExecuteChanged());
    }

    public void OnPropertyChanged(PropertyChangedEventArgs e)
    {
        SomeMainThreadDispatcher.InvokeOnMainThread(() => base.OnPropertyChanged(e));
    }
}

Breaking change?

No

Alternatives

Introducing static field with dispatcher resolved by Ioc:

public interface IDispatcher
{
    void InvokeOnMainThread(Action action);
}

namespace CommunityToolkit.Mvvm.Input;

public sealed class RelayCommand : IRelayCommand
{
    private static readonly Lazy<IDispatcher> _dispatcher = new Lazy<IDispatcher>(() => Ioc.Default.GetService<IDispatcher>());

    public void NotifyCanExecuteChanged()
    {
        if (_dispatcher.Value is IDispatcher dispatcher)
        {
            dispatcher.InvokeOnMainThread(() => CanExecuteChanged?.Invoke(this, EventArgs.Empty));
            return;
        } 

        CanExecuteChanged?.Invoke(this, EventArgs.Empty);
    }
}

Additional context

No response

Help us help you

Yes, but only if others can assist

Rand-Random commented 9 months ago

seems duplicate with: https://github.com/CommunityToolkit/dotnet/issues/536