What's new in ReactiveUI 6: ReactiveCommand<T>
One of the largest breaking changes in ReactiveUI 6.0 is the new ReactiveCommand. Since this change is the most likely one to mess with your existing code, I wanted to make sure to get the blog post about it as soon as possible.
What was wrong with the old ReactiveCommand?
There are a few parts of the existing ReactiveCommand design that made certain scenarios particularly difficult to deal with. The original scenario that motivated this rewrite was the following (simplified for effect):
"In GitHub for Windows, the Sync Command is an invocation of the Pull Command followed by the Push Command - how can I create a Command whose CanExecute turns off until the Pull then the Push completes?"
The only one who received the completion that the background method for Pull and Push completed was the person who set it up (i.e. the one who called RegisterAsyncXYZ
). Other callers had to do something really hacky:
someCommand.IsExecuting.Where(x => x == false).Skip(1).Take(1);
The even more common scenario was, "Write a test that invokes a Command with a background method, then wait for the result".
With the new ReactiveCommand and the ExecuteAsync
method, the GitHub for Windows scenario becomes very easy:
var canSync = Observable.CombineLatest(
Push.CanExecuteObservable, Pull.CanExecuteObservable,
(pl,pu) => pl && pu);
Sync = ReactiveCommand.CreateAsyncTask(canSync, async _ => {
await Pull.ExecuteAsync();
await Push.ExecuteAsync();
});
The test scenario similarly becomes much more straightforward:
var fixture = new CoolViewModel();
var result = await fixture.ExecuteAsync();
Assert.NotNull(result);
Creating ReactiveCommands
ReactiveCommand is now a Generic class - before, Subscribing to ReactiveCommand would give you the (relatively useless, Worst Practice) CommandParameter. Now, Subscribing to ReactiveCommand gives you the result of your background method after it finishes.
To facilitate this, the correct way to create ReactiveCommands is now via a static ReactiveCommand.CreateXYZ
family of methods. Create lets you provide both the canExecute
as well as providing the method you used to provide to RegisterAsync
.
Dual-Mode Zen Nature: ExecuteAsync vs. Subscribe
ReactiveCommand now has two complimentary ways to get the result of your background method:
-
Subscribing to the Command itself gives you a stream of all results - this is very convenient to connect to a property on the ViewModel via
ToProperty
-
ExecuteAsync
returns a single invocation of the background method. This is useful for invoking a Command as part of a larger workflow, or for testing the background method of a Command. Both the awaiter and any Subscribers to the Command will receive the results.
Some Before => Afters
Before:
// Create a simple command
var someCmd = new ReactiveCommand();
someCmd.Subscribe(_ => Console.WriteLine("Cool it was invoked!"));
// Create an async command
var asyncCmd = new ReactiveCommand(myCanExecute);
asyncCmd.RegisterAsyncTask(x => doAThingAsync())
.ToProperty(this, x => x.OutputText, out outputText);
After:
// Create a simple command
var someCmd = ReactiveCommand.Create();
someCmd.Subscribe(_ => Console.WriteLine("Cool it was invoked!"));
// Create an async command
var asyncCmd = ReactiveCommand.CreateAsyncTask(myCanExecute, x => doAThingAsync());
asyncCmd.ToProperty(this, x => x.OutputText, out outputText);
Previous post: I Am Not Web Scale: Wunderlist Edition
Next post: Welcome to ReactiveUI 6.0