Skip to content

Instantly share code, notes, and snippets.

@raver99
Created November 17, 2021 12:17
Show Gist options
  • Save raver99/5a819a559c2ee062def172537964f2b3 to your computer and use it in GitHub Desktop.
Save raver99/5a819a559c2ee062def172537964f2b3 to your computer and use it in GitHub Desktop.
ExecuteOnceAsyncCommand
//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