Execute a set of commands in order, sequentially with ReactiveUI

2.3k views Asked by At

I have a ViewModel with some ReactiveCommands that user can invoke manually.

CommandA
CommandB
CommandC

So the user can invoke only B, or A first, and then C.

The problem is that there is that I want to create another command that runs them in order, from A to C. They shouldn't run in parallel, but sequentially.

How can I do a command of the given characteristics?

3

There are 3 answers

2
Taylor Buchanan On BEST ANSWER

Since you want them executed sequentially, ReactiveCommand.CreateCombined isn't really a good fit. However, you could create something similar to CombinedReactiveCommand to work for your situation.

Reactive commands can be executed manually by calling ReactiveCommand<TParam,TResult>.Execute, which returns an IObservable<TResult> that returns a single value. Therefore, a very basic implementation could just chain it to other execute calls with SelectMany:

ReactiveCommand.CreateFromObservable(() =>
    CommandA.Execute()
        .SelectMany(_ => CommandB.Execute())
        .SelectMany(_ => CommandC.Execute()));

If all of your commands have the same type, it could be made more generic:

var commands = new[] { CommandA, CommandB, CommandC };
ReactiveCommand.CreateFromObservable(() =>
    commands.Aggregate(
        Observable.Return(Unit.Default),
        (acc, cur) => acc.SelectMany(_ => cur.Execute())));

However, this does not take into consideration the other things CombinedReactiveCommand handles, such as CanExecute and ThrownExceptions. If you need to handle those, then you might consider rolling your own CombinedReactiveCommand.

0
Adrián Romero On

I am asuming, that your commands are of type ReactiveCommand<Unit,Unit> so, you can do this:

public class MyCoolViewModel
{
   bool runSequentially = false;

   public ReactiveCommand<Unit, Unit> Command { get; set; }
   public ReactiveCommand<Unit, Unit> Command2 { get; set; }
   public ReactiveCommand<Unit, Unit> Command3 { get; set; }
   public ReactiveCommand<Unit, Unit> CommandwhoInvokesOtherCommands { get; set; }


   public MyCoolViewModel()
   {

       Command = ReactiveCommand.CreateFromTask<Unit, Unit>(async _ =>
       {    
        runSequentially= false;
        Console.WriteLine("Start 1");
        await Task.Delay(1000);
        Console.WriteLine("End 1");
        return Unit.Default;
       });
      Command2 = ReactiveCommand.CreateFromTask<Unit, Unit>(async _ =>
      {
        runSequentially = false;
        Console.WriteLine("Start 2");
        await Task.Delay(1000);
        Console.WriteLine("End 2");
        return Unit.Default;
      });

    Command3 = ReactiveCommand.CreateFromTask<Unit, Unit>(async _ =>
    {
        Console.WriteLine("Start 3");
        await Task.Delay(1000);
        Console.WriteLine("End 3");
        return Unit.Default;
    });

    CommandwhoInvokesOtherCommands = ReactiveCommand.CreateFromTask<Unit, Unit>(async _ =>
      {
          Console.WriteLine("Invoking other commands");
          runSequentially = true;
          await Task.Delay(1000);
          Console.WriteLine("End");
          return Unit.Default;
      });

            /*Command 1, 2 and 3 only will run if flag is set to true*/
    CommandwhoInvokesOtherCommands.Where(_ => runSequentially).InvokeCommand(Command);
    Command.Where(_ => runSequentially).InvokeCommand(Command2);
    Command2.Where(_ => runSequentially).InvokeCommand(Command3);

    //Observable.Return(Unit.Default).InvokeCommand(CommandwhoInvokesOtherCommands);//for test purposes

}
}

The utility method InvokeCommand will call another command when a command returns, so this method plus a where clause will do the trick.

I hope this help you Regards

1
Luis On

Import System.Reactive.Linq, now you can:

{
    await CommandA.Execute();
    await CommandB.Execute();
    await CommandC.Execute();
}

System.Reactive.Linq makes IObservable as awaitable.