MVVM Command disabling scope for asynchronous operations
Introduction
One of the must-have features of all modern applications is constant UI responsiveness. The user interface should never freeze and stay responsive no matter what service calls or other background operations are being executed. So, when there is an operation running in the background, the user should only be able to take certain actions that do not interfere with the running operation. Two common approaches to tackling this problem would be:
- Blocking the UI completely while displaying a loading animation with a message.
- Disabling only the actions (buttons for example) that the user is not allowed to take while the operation is in progress.
In this post, we will go through a possible implementation for the second case. You can also find a full working sample of the implementation that follows on Github. We will start by defining the disabling scope of an operation.
Disabling Scope
Let’s suppose that we have a contacts list (like the one you can see below) in which we can add a new or update/remove the selected contact (in red). When someone adds a contact by clicking the add button, a time consuming background task is executed (service call). While this task is being executed, the Update and Remove buttons should be disabled. Therefore, these two buttons would be the disabling scope of the Add Button action.
Implementation
If you are familiar with the MVVM pattern, then you have definitely already made use of Commands and the ICommand
Interface. There are various implementations of this interface. A popular one, suitable for asynchronous operations is the RelayCommandAsync.
The constructor of this implementation looks like this:
The Initial Approach
We can define the Command Handler and set the conditions under which the Command should be enabled. An initial approach could look like this:
The RemoveCommand
can execute when the selected contact is not null and the AddCommand
is not executing. The AddCommandHandler
changes accordingly the boolean IsAddCommandExecuting
which enables or disables the RemoveCommand
.
The ViewModel condition SelectedContact!= null
is a responsibility of the Command itself. The same could happen with the Disabling scope as well though. When looking at the AddCommand
declaration, you cannot directly see the disabling scope of the Command.
One step further
What if we introduced a new IDisableable
interface, a new DisablingScope
class and finally an IDisableableCommand
interface:
After these last changes, the IsAddCommandExecuting
is not necessary anymore and the AddCommandHandler
would now look like this:
Polishing up
Now, we can directly see which buttons belong to the disabling scope of the Command by looking at the Command Handler. What if we could improve this a little bit more and make it more clear. We could perhaps skip the using
part every time a handler is called. What if we encapsulated this functionality in the RelayCommandAsync
class? Then, we have the following IDisableableCommand
implementation:
After these last changes, theAddCommand
declaration would be:
As you can see, it becomes absolutely clear which Commands are being disabled while the AddCommand
is executing and all that without any affecting the AddCommandHandler
.