DEV Community

Cover image for UI Command Pattern : Part I
Jorge Castro
Jorge Castro

Posted on • Updated on

UI Command Pattern : Part I

This post is intended to discuss different Command pattern approaches when developing UI user interactions through ICommand interface.

You can follow along with the final code here:
https://github.com/keozx/CleanRx/tree/master/src/samples/Commands

It is important to analyze each approach and the differences among them so when it is adopted within a team is easier to break barriers of adopting one framework or the other or small part of it. Most organizations will stick to one framework but that is seldom efficient when you need the best tool to get the job done. In my opinion, contrary to common belief, mixing up frameworks is actually a good thing, linking and proper dependency management should remove the clutter around having many tools in a single code base.

This post is divided in 3 main sections:

Command framework comparison

In the following table I compare different Command frameworks out there, most of them of course are part of a bigger framework but focusing on one small piece at a time helps in making the right choice on your current situation.
I chose to analyze what I think are the frameworks most commonly used, I'm missing MVVMCross for now as well as others, but I'll add them in following posts as soon as I have time to play around with them, in this first part I have included only these 4 approaches with the features I found relevant to consider, but this can change later and will update the post and code accordingly.

A better visibility version can be found in my GitHub Page

Feature XF Prism Async Reactive
Executes a simple bindable Action ✔️ ✔️ ✔️ ✔️ <T>
Create from Task<T> (not async void) ✔️ ✔️
Retrieve unhandled exceptions ✔️ ✔️
Observes boolean changes for "CanExecute" behavior through INPC (not RaiseCanExecute) ✔️ ✔️ *WhenAnyValue
Observes IObservable ticks for "CanExecute" behavior ✔️
Guards against double execution (double tap) ✔️
Returns a <T> result at end of execution ✔️
Accessible "CanExecute" state ✔️
Accessible "IsExecuting" state ✔️
Subscribe to Completion and executes handler for <T> result ✔️

Xamarin.Forms Command

Based on above comparison, is there any reason to use XF Command? well only for a quick POC or if all you are really going to do is clicking a button and do something simple that does not require any other feature. However there may be some memory save if you are having a lot of simple buttons in a screen. A benchmark comparison coming soon.
Xamarin Docs

Prism DelegateCommand

DelegateCommand is a better Command implementation because allows observing a boolean for 'CanExecute', but other than that, feels like a half-baked implementation of ICommand, not really intended in my opinion for use in complex apps. Prism Docs

AsyncAwaitBestPractices AsyncCommand

AsyncCommand comes from package Async Await Best Practices which apart from giving an implementation of ICommand with error handling, prevents the use of async void. Also the library provides for other good stuff like SafeFireAndForget() and WeakEventManager so is a handy package for Task based programming.

Unfortunately it does not provide CanExecute observer pattern like DelegateCommand, but is capable of catching exceptions if you provide an Error Handler parameter when creating the Command.

ReactiveUI ReactiveCommand

From all, from what you can see in the table ReactiveCommand is your swiss army knife implementation of ICommand, fully featured and rock solid, you can't go wrong with it, the flexibility of IObservable as consumer and implementer gives us ultimate adaptability.

ReactiveCommand is also the only one capable of providing a generic typed result, additional to the default parameter in ICommand, to allow to retrieve the execution output, this is useful for Unit Testing because often you would want to assert the outcome of an operation starting with the click of a button end to end.

One thing that shines from the rest is also that it has built-in execution blocking so it won't allow double tap or double execution if already fired, whether is a Task or an Action. Also, you can retrieve CanExecute and IsExecuting states to have better awareness for other components that may need to know execution state.

*While not directly capable of observing a boolean property, what RxUI Command offers is accepting the more robust IObservable type parameter for CanExecute behavior. You have to take an extra step to use WhenAnyValue() extension to wrap the INPC events from a property into an observable but in a real world application you rarely just watch for a single source, you could chain different observables here without having to notify of Can Execute has changed, so this is handy when having to watch multiple states at once and calculate whether or not the Command should be enabled or disabled.

In the following sections I go through a demo setup to analyze each one in deep.

Setting up a meaningful use case 👨‍🏫

Let's say we have a minimum requirement when we click a button in our app, simple but still a realistic requirement:

  • We should be able to click a button in screen to execute an Important Task
  • User can control whether or not the button should be enabled
  • There may be an event that would make the button not available to click, when the event for disabling it happens we disable the button, then enable it with the following event
  • Button should be enabled only when the user has manually enabled it and the event for enabling it occurs.
  • Oh but also the Important Task shall not be executing while another Task is happening, this is an important Task!
  • Important Task may error with an Exception, handle it
  • Important Task takes a parameter from the Command execution.
  • After Important Task finishes we may do some operation on the result, this handler may also cause an unexpected exception, handle it but this is an unrecoverable state.

Now, let's explore our options for a minimum implementation of this, shall we?

Defining a simple View

Here I'm using C# Markup extensions as of latest XF 4.8 (I don't think XAML is suited for UI code for reasons I'll explain in another post)

Notice I declare the Parameter and Result types to pass a parameter and return a Result from the Task to execute.

The view is simply a Button and a Switch to enable manually the the button, the command is bound to the View Model we define later here, the VM will handle results, errors and executability of the Command.

public MainPage()
        {
            var formsVm = new FormsCmdViewModel();
            var delegateVm = new DelegateCmdViewModel();
            var asyncVm = new AsyncCmdViewModel();
            var rxVm = new ReactiveCmdViewModel();
            Content = new StackLayout
            {
                Spacing = 10,
                Children = 
                { 
                    new ContentView
                    { 
                        BindingContext = formsVm,
                        Content = new StackLayout
                        {
                            BindingContext = formsVm,
                            Children =
                            {
                                new Button { Text = "Forms Command" }
                                    .Width(50)
                                    .Height(50)
                                    .BindCommand(
                                        nameof(formsVm.FormsCommand),
                                        formsVm,
                                        parameterSource: new Parameter
                                        {
                                            Type = "Forms",
                                        }),
                                new Label { Text = "Enable Command?" },
                                new Switch()
                                    .Bind(nameof(formsVm.Enabled)),
                            },
                        },
                    },
                    new ContentView
                    { 
                        BindingContext = delegateVm,
                        Content = new StackLayout
                        {
                            BindingContext = delegateVm,
                            Children =
                            {
                                new Button { Text = "Delegate Command" }
                                    .Width(50)
                                    .Height(50)
                                    .BindCommand(
                                        nameof(delegateVm.DelegateCommand),
                                        delegateVm,
                                        parameterSource: new Parameter
                                        {
                                            Type = "DelegateCommand",
                                        }),
                                new Label { Text = "Enable Command?" },
                                new Switch()
                                    .Bind(nameof(delegateVm.Enabled)),
                            },
                        },
                    },
                    new ContentView
                    { 
                        BindingContext = asyncVm,
                        Content = new StackLayout
                        {
                            BindingContext = asyncVm,
                            Children =
                            {
                                new Button { Text = "Async Command" }
                                    .Width(50)
                                    .Height(50)
                                    .BindCommand(
                                        nameof(asyncVm.AsyncCommand),
                                        asyncVm,
                                        parameterSource: new Parameter
                                        {
                                            Type = "AsyncCommand",
                                        }),
                                new Label { Text = "Enable Command?" },
                                new Switch()
                                    .Bind(nameof(asyncVm.Enabled)),
                            },
                        },
                    },
                    new ContentView
                    { 
                        BindingContext = rxVm,
                        Content = new StackLayout
                        {
                            BindingContext = rxVm,
                            Children =
                            {
                                new Button { Text = "Reactive Command" }
                                    .Width(50)
                                    .Height(50)
                                    .BindCommand(
                                        nameof(rxVm.RxCommand),
                                        rxVm,
                                        parameterSource: new Parameter
                                        {
                                            Type = "ReactiveCommand",
                                        }),
                                new Label { Text = "Enable Command?" },
                                new Switch()
                                    .Bind(nameof(rxVm.Enabled)),
                            },
                        },
                    },
                },
            };
        }
    public class Parameter
    {
        public string Type { get; set; }
    }

    public class Result
    {
        public string Type { get; set; }
    }
Enter fullscreen mode Exit fullscreen mode

Declaring the Important Task

We simply define a Task with a Delay and randomly throwing an exception, a simple Result instance is returned from this.

        private static async Task<Result> ImportantTask(Parameter parameter)
        {
            Debug.WriteLine($"Command executed from {parameter?.Type}");
            await Task.Delay(3000);
            var rnd = new Random().Next(10);
            if (rnd > 5)
            {
                Debug.WriteLine("Exception! Because it can happen");
                throw new InvalidOperationException();
            }
            return new Result{ Type = parameter?.Type };
        }
Enter fullscreen mode Exit fullscreen mode

Commands Implementation

Let's now explore our options to implement the Command execution and determining when is enabled or not, and also how to catch exceptions in each one.

Option 1: Xamarin Forms Command 🤦‍♂️

Ok so let's firstly declare our command in the constructor of the FormsCmdViewModel:

    public FormsCmdViewModel()
    {
        _device = new DeviceService();
        _device.StartTimer(TimeSpan.FromSeconds(3), OnEvent);
        FormsCommand = new Command<Parameter>(OnClickAsyncVoid, CanExecute);
    }
// ...
private bool OnEvent()
{
    EnabledFromEvent = !_enabledFromEvent;
    Debug.WriteLine($"Event triggered: {_enabledFromEvent}");
    return true;
}
Enter fullscreen mode Exit fullscreen mode

Notice we use a timer to set EnabledFromEvent property that will be a conditional to enable the Command and hence, the button.

Now let's define the OnClickAsyncVoid() method :

private async void OnClickAsyncVoid(Parameter obj)
{
    // We have to await the Task and try/catch this async void,
    // otherwise we would not catch exceptions on the ImportantTask handler, 
    // like when we don't change Enabled in the Main Thread, then our app wouldn't make a noise!
    try
    {
        Enabled = false;
        try
        {
            var result = await ImportantTask(obj).ConfigureAwait(false);
            HandleResult(result);
        }
        catch (InvalidOperationException ex)
        {
            Debug.WriteLine($"Expected Exception handled! {ex}");
        }
        finally
        {
            // If you did this you would run into threading exception,
            // shows our point above.
            // Enabled = true;
            _device.BeginInvokeOnMainThread(() => Enabled = true);
        }
    }
    catch (Exception ex)
    {
        Debug.WriteLine($"Exception from handler! {ex}");
        // This is an unrecoverable state of the app!
        throw;
    }
}
Enter fullscreen mode Exit fullscreen mode

So as you can see is a very simple method that basically does the following:

  • Executes our ImportantTask
  • Calls the result handler through HandleResult() method and catches the expected InvalidOperationException, we simply log to console these for simplicity.
  • Also. We catch unexpected exceptions happening inside the execution with the outer try catch, notice that setting Enabled = true out of Main Thread would cause the exception, while you can remove it later after the code is safe, most of times you will want to keep a handler in cases an unexpected exception happens in more complex tasks, notice also that we crash the app as we don't know the state of the app if we ever get here!
  • Lastly, it will disable the Command at beginning of execution and at the end setting Enabled property

This method completes our requirement, there is one thing we have to do so that our Button disables or enables as we operate the switch or a timed event happens, in our properties we need to call also RaiseCanExecuteChanged() so the Command evaluates CanExecute:

private bool _enabled;
public bool Enabled
{
    get => _enabled;
    set
    {
        SetProperty(ref _enabled, value);
        (FormsCommand as Command)?.ChangeCanExecute();
    }
}

private bool _enabledFromEvent;

private bool EnabledFromEvent
{
    get => _enabledFromEvent;
    set
    {
        SetProperty(ref _enabledFromEvent, value);
        (FormsCommand as Command)?.ChangeCanExecute();
    }
}
Enter fullscreen mode Exit fullscreen mode

This method will be also used for DelegateCommand, for the CanExecute() method we simply define that both booleans should be true:

private bool CanExecute(Parameter obj)
{
    return Enabled && EnabledFromEvent;
}
Enter fullscreen mode Exit fullscreen mode

Option 2: Prism DelegateCommand 💆‍♂️

Well, I was thinking that

ObservesCanExecute(Func<bool> canExecute);
Enter fullscreen mode Exit fullscreen mode

would accept the 2 conditions we have set up at once, but it will not, so only you can observe properties with ObservesProperty(), however you can chain together several so in this case we would not need to call RaiseCanExecuteChanged() which is analogue to XF Command's ChangeCanExecute() like this:

DelegateCommand = new DelegateCommand<Parameter>(OnForms, CanExecute)
    .ObservesProperty(() => Enabled)
    .ObservesProperty(() => EnabledFromEvent);
Enter fullscreen mode Exit fullscreen mode

Here, ObservesProperty() method, allows us to save some code delegating the knowledge of when to call RaiseCanExecuteChanged automatically, to the Command itself, instead of imperatively calling it in the properties! We sort of pull the state, not push it, which is one essential aspect of Reactive paradigm, but this is not using Reactive Extensions at all. Notice that this will tell when to call CanExecute(), not really evaluating the properties altogether, so you can still have arbitrary logic in the CanExecute() method.

and that's it... the OnClickAsyncVoid defined before is exactly the same, let's move on!

Option 3: AsyncCommand 🧰

The most interesting use of AsyncCommand is that it can handle passing Tasks for execution and a way to handling errors out of the box, that way we have the following declaration:

AsyncCommand = new AsyncCommand<Parameter>(
    OnClickAsyncTask,
    CanExecute,
    OnError);
Enter fullscreen mode Exit fullscreen mode

This allows to convert our OnClickAsyncVoid() to an OnClickAsyncTask() to be simplified a bit:

        private async Task OnClickAsyncTask(Parameter obj)
        {
            Enabled = false;
            try
            {
                // Ideally, we would return the Task instead of awaiting here,
                // but we would not be able to retrieve the Result at Command level
                var result = await ImportantTask(obj).ConfigureAwait(false);
                HandleResult(result);
            }
            catch (InvalidOperationException ex)
            {
                Debug.WriteLine($"Expected Exception handled! {ex}");
            }
            finally
            {
                // If you did this you would run into threading exception,
                // shows our point above.
                // Enabled = true;
                _device.BeginInvokeOnMainThread(() => Enabled = true);
            }
        }
Enter fullscreen mode Exit fullscreen mode

This will remove some code from the method and remove the async void!

However, we have to still call explicitly when we want it to evaluate the conditions for CanExecute behavior contrary to DelegateCommand:

(AsyncCommand as AsyncCommand<Parameter>)?.RaiseCanExecuteChanged();
Enter fullscreen mode Exit fullscreen mode

And also explicitly enable and disable the button as all previous examples. But this can be done better!

Option 4: ReactiveCommand 👨‍🔬

Let's declare our ReactiveCommand functionality as follows:

RxCommand = ReactiveCommand
    .CreateFromTask<Parameter, Result>(
        ImportantTask,
        CanExecute());
RxCommand
    .Subscribe(HandleResult);
RxCommand
    .ThrownExceptions
    .Subscribe(OnError);
Enter fullscreen mode Exit fullscreen mode

A lot is going on here! So let's demystify it. The first declaration is similar to others, we use the static CreateFromTask() method to pass in this case not the OnClickAsyncTask anymore, but the ImportantTask directly.

This saves us a lot of code because as you may remember, this command can take a Task but also the Command enables and disables when the Task starts and completes, respectively.

Also, we don't need to wrap the Task calling into an async method because we can simply Subscribe() to the execution completion of the ReactiveCommand, which allows to retrieve the result and pass it to our HandleResult method or do something else as the command is an IObservable<T> itself.

Lastly, ReactiveCommand provides a way to catch any thrown Exceptions through ThrownExceptions observable. We pass the OnError handler but as we don't have a way to filter through the exceptions, we simply type sniff the exception as follows:

private void OnError(Exception ex)
{
    if (ex is InvalidOperationException)
    {
        Debug.WriteLine($"Expected Exception handled! {ex}");
    }
    else
    {
        Debug.WriteLine($"Exception from handler! {ex}");
        // This is an unrecoverable state of the app!
        RxApp.DefaultExceptionHandler.OnNext(ex);
    }
}
Enter fullscreen mode Exit fullscreen mode

This may not be ideal for more complex scenarios, in which case we can still keep a very slim version of OnClickAsyncTask() while also keeping the unexpected exception handler:

private async Task<Result> OnClickAsyncTask(Parameter obj)
{
    try
    {
        return await ImportantTask(obj).ConfigureAwait(false);
    }
    catch (InvalidOperationException ex)
    {
        Debug.WriteLine($"Expected Exception handled! {ex}");
        return default;
    }
}
Enter fullscreen mode Exit fullscreen mode

We now don't have the side effects as in the previous method version, notice that even if is an async Task, it is a generic Task<TResult> which the ReactiveCommand can easily make available to us, something you simply can't do with any other Command, that however comes with a bit of a price, ICommand is still an interface ReactiveCommand implements but to handle result operations it also implements IObservable<TResult>, so you will need to declare the command property differently:

public ReactiveCommand<Parameter, Result> RxCommand { get;}
Enter fullscreen mode Exit fullscreen mode

All in all, this has allowed us to have a Single Responsibility method, such as execute our Task and handle any errors coming from it, no need to care about the execution state of the Command or if there is another error down the road of execution that we can't handle.

Lastly, let's explore the CanExecute part, in order to mimic what DelegateCommand's ObservesProperty() can do we have to pass an IObservable<bool> to the command declaration, so we have to modify CanExecute() as follows:

private IObservable<bool> CanExecute()
{
    return this.WhenAnyValue(
        vm => vm.Enabled,
        vm => vm.EnabledFromEvent,
        (enabled, enabledEvent) => enabled && enabledEvent);
}
Enter fullscreen mode Exit fullscreen mode

We use the WhenAnyValue extension method from ReactiveUI to observe the INPC events easily and at once for both properties Enabled and EnabledFromEvent, the third argument is a selector, so you can pass a custom logic to evaluate the two properties observed, in this case we simply want both to be true, but again, this is a powerful tool to enable complex logic as you can see.

Note also, that even though we had to make this method return IObsevable<bool>, it is now in a whole different league than just observing boolean properties, meaning you can chain other observables together to create complex pipelines. Plus we don't need to tell the command to evaluate imperatively this condition on each property setter, as it should be!

Again, we don't need to tell the Command when it should enable through the properties, a function is responsible to evaluate it when something changes, we react.

Bonus code! 🎁

You may notice that for simple cases like ours, the IObservable<bool> for CanExecute parameter may be a bit hard to read specially since you are only evaluating 2 booleans. So what I did is try to mimic the way DelegateCommand is able to observe properties with ObservesCanExecute() in a compact way.

While I'm a total newbie dealing with C# Expressions, I was able to come up with a quick and dirty extension method that will create our IObservable<bool> for us, given 1 or 2 INPC enabled boolean properties. I give you 'WhenAnyValueMixin' class:

public static class WhenAnyValueMixin
{
    public static IObservable<bool> ObservesProperty<TSource>(
        this TSource target,
        Expression<Func<bool>> property1)
    {
        return target.WhenAnyValue(
            GetExpression<TSource>(property1));
    }

    public static IObservable<bool> ObservesProperty<TSource>(
        this TSource target,
        Expression<Func<bool>> property1,
        Expression<Func<bool>> property2)
    {
        return target.WhenAnyValue( 
            GetExpression<TSource>(property1),
            GetExpression<TSource>(property2), 
            (p1, p2) => p1 && p2);
    }

    private static Expression<Func<TSource, bool>> GetExpression<TSource>(
        Expression<Func<bool>> property1)
    {
        // This expression is our goal for the ObservableForProperty():
        // Expression<Func<ReactiveCmdViewModel, bool>> target;
        // And this parameter is a MemberExpression (=> Enabled) (because is a member?)
        if (!(property1.Body is MemberExpression exp)) 
        {    
            // Let user know if is not MemberExpression
            throw new NotSupportedException("MemberExpression expected!");
        }

        // Expression for the Parameter type we need in the goal above, assuming we have a member on TSource.
        var parameter = Expression.Parameter(typeof(TSource));
        // Create Member Access Expression, as in vm => vm.Enabled from the Member Info from the above of course.
        var memberAccess = Expression.MakeMemberAccess(parameter, exp.GetMemberInfo());
        // Create target expression from the member access and parameter expressions so we get vm => vm.Enabled to get it's observable below
        var lambda = Expression.Lambda<Func<TSource, bool>>(memberAccess, parameter);

        return lambda;
        // Fpr reference, started with this
        // return this.ObservableForProperty(lambda,  false, false)
        //     .Select((change, i) => change.Value);
    }
}
Enter fullscreen mode Exit fullscreen mode

As this is kind of out of scope for this post, I'll leave it to you to explore the code, which is not much, but this allows to declare our CanExecute() this way!

private IObservable<bool> CanExecute()
{
    // Mimicking DelegateCommand with WhenAnyValue here:
    return this.ObservesProperty(
        () => Enabled,
        () => EnabledFromEvent);
}
Enter fullscreen mode Exit fullscreen mode

Notice also you only need to pass CanExecute() to the Command, not the ObservesProperty in the Command declaration which is required for DelegateCommand, this is doing both evaluating CanExecute and Observing the properties for both being true in a single shot. Also, you can still attach a selector so you can do arbitrary logic on the 'ticked' values from the Observable.

Pretty neat!

A winner? 🏆

I have made up my mind, after trying out all of them now, to me there is not better implementation of ICommand + IObservable than Reactive UI, in my opinion we don't need any other implementation.

Consider also how much we reduced our code footprint in each iteration!

Factor XF Command DelegateCommand AsyncCommand ReactiveCommand
Enabled/EnabledFromEvent property setters lines 4 2 4 2
OnClickAsyncVoid/OnClickAsyncTask executors lines 25 25 17 0-12
OnClick* cognitive complexity 2 2 1 0-1
Readability weight (1-5) 1 3 2 4

The last one may be subjective but I think the ReactiveCommand version of the ViewModel is more readable, with less side effects and more scalable. This comes more evident as your complexity increases and you realize reactive programming is a natural fit for some advanced scenarios. Again, all this was all a simple example but is a good starting point for Reactive Extensions and ReactiveUI, one command to rule them all 😁

I find it unfortunate that the Reactive Paradigm is just gaining momentum even though has more than a decade of existence, and that's why few frameworks are leveraging Reactive Extensions, using Rx super charges a tool and makes it more versatile to use; Shiny is a good example of providing the flexibility of Reactive in a library and hope that's a trend.

I also have started a project that tries to close the gap on examples (still being cooked!) using Reactive and also be useful as an Architecture guidance, CleanRx

Latest comments (0)