0

I am using ReactiveUI for a UWP app and have two commands CommandA and CommandB. CommandA when invoked attempts to make changes to the hardware. CommandB when invoked reads the hardware and provides the latest value.

  1. I would like to invoke the CommandA (with the parameter as the CombBox value) when a ComboBox value is changed.
  2. After the execution of CommandA I would like to invoke CommandB repeatedly till I get the value that is same as the one selected in ComboBox or if a timeout occurs. On timeout an error should be displayed.

To check that the CommandA is finished executing, I wrote the following code for [1]

this.WhenAnyValue(x => x.ComboBoxAValue)
    .InvokeCommand(CommandA);

CommandA.IsExecuting
    .Buffer(2,1)
    .Where(t => t[0] == true && t[1] == false)
    .Select( x=> Unit.Default)
    .InvokeCommand(CommandB) // This statement would attempt to invoke CommandB only once

I am not sure how to do [2].

resp78
  • 1,414
  • 16
  • 37
  • You would need to mix it with a Observable.Timer. You could use a Observable.Concat(CommandA, Observable.Timer(TimeSpan.Zero, TimeSpan.FromSeconds(1)) Where you'd get the value for the cancel condition you can mix in with a CombineLatest. – Glenn Watson Jun 20 '18 at 02:43

2 Answers2

0

I'm conviced that exists a better solution, but here you have an approach:

public class MyCoolViewModel : ReactiveObject
{
    private readonly Subject<Unit> _ticksToInvokeB;

    private readonly ObservableAsPropertyHelper<bool> _commandAExecutedCorrectly;
    public bool CommandAExecutedCorrectly => _commandAExecutedCorrectly.Value;

    public ReactiveCommand<Unit, bool> CommandA { get; set; }
    public ReactiveCommand<Unit, bool> CommandB { get; set; }

    private string _comboBoxValue;
    public string ComboBoxValue
    {
        get => _comboBoxValue;
        set => this.RaiseAndSetIfChanged(ref _comboBoxValue, value);
    }

    public MyCoolViewModel()
    {
        //Subject implements IObservable<T> and IObserver<T>, also alow us to tick values to its observable
        _ticksToInvokeB = new Subject<Unit>(); 

        CommandA = ReactiveCommand.Create<Unit,bool>( _ =>
        {
            Console.WriteLine("Command A");
            return true;
        });

        CommandB = ReactiveCommand.CreateFromTask<Unit,bool>( async _ =>
        {
            await Task.Delay(3000);
            var isTheSame = DateTime.Now.Second % 2 == 0;
            Console.WriteLine($"CommandB: is the same: {isTheSame}");
            if(!isTheSame)//if the value is not the same, tick a new unit, since we ticked a new value, CommandA will be executed
                _ticksToInvokeB.OnNext(Unit.Default);


            return isTheSame;
        });

        CommandA//We just send commandA execution to an OAPH
            .ToProperty(this, x => x.CommandAExecutedCorrectly, out _commandAExecutedCorrectly);

        this.WhenAnyValue(x => x.ComboBoxValue)
        .Skip(1) //this is because ComboBoxValue has a initial value (null) so we ignore it 
        .Select(_ => Unit.Default) //When it changes simply project an Unit
        .InvokeCommand(CommandA);//Inke CommandA

        this.WhenAnyValue(x => x.CommandAExecutedCorrectly)//When changes maded CommandA will set ChangesMaded to true
        .Where(b => b) // only when Command A gets executed correctly
        .Do(b => TickUnit()) // Tick a new unit
        .Subscribe();

        _ticksToInvokeB
            .Throttle(TimeSpan.FromMilliseconds(200))//delay a little bit the next value
            .InvokeCommand(CommandB);//invokes CommandB

    }

    private void TickUnit()
    {
        Console.WriteLine("Ticking new value");
        _ticksToInvokeB.OnNext(Unit.Default);
    }

}

Let me know if it helps you.

Regards.

Adrián Romero
  • 620
  • 7
  • 17
  • As you mentioned it could be improved. Once concern is spilling the code `TickUnit` in the viewmodel. Sorry, I got distracted, will try your solution soon and let you know how it went. – resp78 Jun 29 '18 at 12:05
-1

First, instead of the buffer technique you used for detecting a completed command, I would do something like this. And instead of worrying about creating a command for CommandB, I would just execute it as a method. You can still use a command if you want, but I'll just be using an async call in this example. I'm using ExecuteUntilItYieldsTheSelectedComboBoxValue to continuously execute your CommandB logic in a loop until a matching value is found. It utilizes Observable.Create so you can control when OnNext is triggered. And you can tag on a Timeout to it and handle it in the Subscribe extension.

CommandA.IsExecuting
    // IsExecuting has an initial value of false.  We can skip that first value
    .Skip(1)
    // Filter until the executing state becomes false.
    .Where(isExecuting => !isExecuting)
    // Start an inner observable for your "CommandB" logic.
    .Select(_ => ExecuteUntilItYieldsTheSelectedComboBoxValue())
    // Whenever CommandA is invoked, dispose of the last inner observable subscription and resubscribe.
    .Switch()
    .Subscribe(
        _ => Console.WriteLine("OnNext"),
        ex => [display error message]);

...

private IObservable<Unit> ExecuteUntilItYieldsTheSelectedComboBoxValue()
{
    return Observable
        .Create<Unit>(
            async o =>
            {
                int randNum = -1;
                do
                {
                    randNum = await GetRandomNumberAsync();
                } while(randNum != ComboBoxValue);

                o.OnNext(Unit.Default);
                o.OnCompleted();

                return Disposable.Empty;
            })
        .Timeout(TimeSpan.FromSeconds(3));
}

Update

Based on what Enigmativity pointed out, about needing to return something better than Disposable.Empty (to cancel any in-progress async task and break out of the loop), I'm changing the ExecuteUntilItYieldsTheSelectedComboBoxValue method to be the following:

private IObservable<Unit> ExecuteUntilItYieldsTheSelectedComboBoxValue()
{
    return Observable
        // Call our async method and pass in a cancellation token.
        .FromAsync(ct => GetRandomNumberAsync(ct))
        // Keep generating random numbers.
        .Repeat()
        // Until we find one that matches the ComboBoxValue.
        .Where(x => x == ComboBoxValue)
        // Just take the first one so this inner observable will complete.
        .Take(1)
        .Select(_ => Unit.Default)
        .Timeout(TimeSpan.FromSeconds(3));
}

Note that it's still possible to make the Observable.Create method work correctly, but this edited solution is cleaner and less error prone. Let me know if you have any questions.

Colt Bauman
  • 564
  • 3
  • 9
  • When you call `return Disposable.Empty;` as the return for an `Observable.Create` method you are doing something wrong. It creates an observable that is impossible to unsubscribe from until it naturally completes - and if it doesn't naturally complete you can't safely stop your code. – Enigmativity Jun 20 '18 at 13:50
  • @Enigmativity Thanks for pointing out my oversight. I edited my answer with a different solution. Returning Disposable.Empty as the return for an Observable.Create method, _usually_ means you are doing something wrong (as in my case), but not always. There's not always something to clean up or cancel. – Colt Bauman Jun 21 '18 at 02:41
  • When there's not something to clean up or cancel then you can probably use another operator to create the observable. An inhumane effort to remove `return Disposable.Empty;` should always be done before giving up. – Enigmativity Jun 21 '18 at 06:44
  • Sorry, I was distracted, will try your solution soon and let you know how it went. – resp78 Jun 29 '18 at 12:02