Created
November 17, 2021 12:17
-
-
Save raver99/5a819a559c2ee062def172537964f2b3 to your computer and use it in GitHub Desktop.
ExecuteOnceAsyncCommand
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//based on AsyncCommand -> https://github.com/jamesmontemagno/mvvm-helpers/blob/master/MvvmHelpers/Commands/AsyncCommand.cs | |
public class ExecuteOnceAsyncCommand : IAsyncCommand | |
{ | |
private bool isBusy = false; | |
readonly Func<Task> execute; | |
readonly Func<object, bool>? canExecute; | |
readonly Action<Exception>? onException; | |
readonly bool continueOnCapturedContext; | |
readonly WeakEventManager weakEventManager = new WeakEventManager(); | |
/// <summary> | |
/// Create a new AsyncCommand | |
/// </summary> | |
/// <param name="execute">Function to execute</param> | |
/// <param name="canExecute">Function to call to determine if it can be executed</param> | |
/// <param name="onException">Action callback when an exception occurs</param> | |
/// <param name="continueOnCapturedContext">If the context should be captured on exception</param> | |
public ExecuteOnceAsyncCommand(Func<Task> execute, | |
Func<object, bool>? canExecute = null, | |
Action<Exception>? onException = null, | |
bool continueOnCapturedContext = false) | |
{ | |
this.execute = execute ?? throw new ArgumentNullException(nameof(execute)); | |
this.canExecute = canExecute; | |
this.onException = onException; | |
this.continueOnCapturedContext = continueOnCapturedContext; | |
} | |
/// <summary> | |
/// Event triggered when Can Excecute changes. | |
/// </summary> | |
public event EventHandler CanExecuteChanged | |
{ | |
add { weakEventManager.AddEventHandler(value); } | |
remove { weakEventManager.RemoveEventHandler(value); } | |
} | |
/// <summary> | |
/// Invoke the CanExecute method and return if it can be executed. | |
/// </summary> | |
/// <param name="parameter">Parameter to pass to CanExecute.</param> | |
/// <returns>If it can be executed.</returns> | |
public bool CanExecute(object parameter) => canExecute?.Invoke(parameter) ?? !isBusy; | |
/// <summary> | |
/// Execute the command async. | |
/// </summary> | |
/// <returns>Task of action being executed that can be awaited.</returns> | |
public Task ExecuteAsync() | |
{ | |
isBusy = true; | |
RaiseCanExecuteChanged(); | |
var Task = execute().ContinueWith((Task t) => | |
{ | |
Device.BeginInvokeOnMainThread(() => | |
{ | |
isBusy = false; | |
RaiseCanExecuteChanged(); | |
}); | |
}); | |
return Task; | |
} | |
/// <summary> | |
/// Raise a CanExecute change event. | |
/// </summary> | |
public void RaiseCanExecuteChanged() => weakEventManager.HandleEvent(this, EventArgs.Empty, nameof(CanExecuteChanged)); | |
#region Explicit implementations | |
void ICommand.Execute(object parameter) | |
{ | |
ExecuteAsync().SafeFireAndForget(onException, continueOnCapturedContext); | |
} | |
#endregion | |
} | |
/// <summary> | |
/// Implementation of a generic Async Command | |
/// </summary> | |
public class ExecuteOnceAsyncCommand<T> : IAsyncCommand<T> | |
{ | |
private bool isBusy = true; | |
readonly Func<T, Task> execute; | |
readonly Func<object, bool>? canExecute; | |
readonly Action<Exception>? onException; | |
readonly bool continueOnCapturedContext; | |
readonly WeakEventManager weakEventManager = new WeakEventManager(); | |
/// <summary> | |
/// Create a new AsyncCommand | |
/// </summary> | |
/// <param name="execute">Function to execute</param> | |
/// <param name="canExecute">Function to call to determine if it can be executed</param> | |
/// <param name="onException">Action callback when an exception occurs</param> | |
/// <param name="continueOnCapturedContext">If the context should be captured on exception</param> | |
public ExecuteOnceAsyncCommand(Func<T, Task> execute, | |
Func<object, bool>? canExecute = null, | |
Action<Exception>? onException = null, | |
bool continueOnCapturedContext = false) | |
{ | |
this.execute = execute ?? throw new ArgumentNullException(nameof(execute)); | |
this.canExecute = canExecute; | |
this.onException = onException; | |
this.continueOnCapturedContext = continueOnCapturedContext; | |
} | |
/// <summary> | |
/// Event triggered when Can Excecute changes. | |
/// </summary> | |
public event EventHandler CanExecuteChanged | |
{ | |
add { weakEventManager.AddEventHandler(value); } | |
remove { weakEventManager.RemoveEventHandler(value); } | |
} | |
/// <summary> | |
/// Invoke the CanExecute method and return if it can be executed. | |
/// </summary> | |
/// <param name="parameter">Parameter to pass to CanExecute.</param> | |
/// <returns>If it can be executed</returns> | |
public bool CanExecute(object parameter) => canExecute?.Invoke(parameter) ?? isBusy; | |
/// <summary> | |
/// Execute the command async. | |
/// </summary> | |
/// <returns>Task that is executing and can be awaited.</returns> | |
public Task ExecuteAsync(T parameter) | |
{ | |
isBusy = false; | |
RaiseCanExecuteChanged(); | |
var Task = execute(parameter).ContinueWith((Task t) => | |
{ | |
Device.BeginInvokeOnMainThread(() => | |
{ | |
isBusy = true; | |
RaiseCanExecuteChanged(); | |
}); | |
}); | |
return Task; | |
} | |
/// <summary> | |
/// Raise a CanExecute change event. | |
/// </summary> | |
public void RaiseCanExecuteChanged() => weakEventManager.HandleEvent(this, EventArgs.Empty, nameof(CanExecuteChanged)); | |
#region Explicit implementations | |
void ICommand.Execute(object parameter) | |
{ | |
if (CommandUtils.IsValidCommandParameter<T>(parameter)) | |
ExecuteAsync((T)parameter).SafeFireAndForget(onException, continueOnCapturedContext); | |
} | |
#endregion | |
} | |
internal static class CommandUtils | |
{ | |
internal static bool IsValidCommandParameter<T>(object o) | |
{ | |
bool valid; | |
if (o != null) | |
{ | |
// The parameter isn't null, so we don't have to worry whether null is a valid option | |
valid = o is T; | |
if (!valid) | |
throw new InvalidCommandParameterException(typeof(T), o.GetType()); | |
return valid; | |
} | |
var t = typeof(T); | |
// The parameter is null. Is T Nullable? | |
if (Nullable.GetUnderlyingType(t) != null) | |
{ | |
return true; | |
} | |
// Not a Nullable, if it's a value type then null is not valid | |
valid = !t.GetTypeInfo().IsValueType; | |
if (!valid) | |
throw new InvalidCommandParameterException(typeof(T)); | |
return valid; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment