3

I am trying to hook a stream (IObservable) to be controlled through ToggleSwitch in a UWP project. The expectation is that I start the streaming when the switch is in On state and stop when it is in Off state.

So the thought is to 1. Create two commands, one to start the stream and another to stop the stream. 2. Create two Observables that monitors the switch state and InvokeCommand when the condition is right.

ViewModel

public class MainPageViewModel : ViewModelBase
{
    public ReactiveCommand<Unit, (long, float)> StreamCommand { get; }
    public ReactiveCommand<Unit, Unit> StopCommand { get; }
    public IObservable<(long, float)> FlowStream { get; set; }

    private bool _isStreamOn;

    public bool IsStreamOn
    {
        get => _isStreamOn;
        set => this.RaiseAndSetIfChanged(ref _isStreamOn, value);
    }

    public MainPageViewModel()
    {
        var stream = GetStream();

        var canSwitchOn = this.WhenAnyValue(x => x.IsStreamOn);
        var canSwitchOff = this.WhenAnyValue(x => x.IsStreamOn, isOn => isOn != true);

        FlowStream = StreamCommand = ReactiveCommand.CreateFromObservable(
            () =>
                {
                    stream.Start();
                    return Observable.FromEventPattern<StreamDataEventArgs<(long, INumeric, INumeric, INumeric)>>(
                            h => stream.DataAvailable += h,
                            h => stream.DataAvailable -= h)
                        .SelectMany(e => e.EventArgs.Data)
                        .Select(item => item));
                }, canSwitchOn);

        StopCommand = ReactiveCommand.Create(
            () =>
            {
                stream.Stop();
                IsStreamOn = false;
            }, canSwitchOff);

        canSwitchOff.InvokeCommand(StopCommand);
        canSwitchOn.InvokeCommand(StreamCommand);
    }

}

View

public sealed partial class MainPage : Page, IViewFor<MainPageViewModel>
{
    public MainPage()
    {
        InitializeComponent();
        NavigationCacheMode = Windows.UI.Xaml.Navigation.NavigationCacheMode.Enabled;
        ViewModel = new MainPageViewModel();

        this.WhenActivated(subscription =>
        {
            subscription(this.OneWayBind(this.ViewModel,
                vm => vm.StreamCommand,
                v => v.chart.SeriesCollection[0].Stream)); // Chart take care of displaying data

            subscription(this.Bind(this.ViewModel,
                vm => vm.IsStreamOn,
                v => v.streamToggle.IsOn));
        });
    }

    object IViewFor.ViewModel
    {
        get { return ViewModel; }
        set { ViewModel = (MainPageViewModel)value; }
    }

    public MainPageViewModel ViewModel
    {
        get { return (MainPageViewModel)GetValue(ViewModelProperty); }
        set { SetValue(ViewModelProperty, value); }
    }

    public static readonly DependencyProperty ViewModelProperty =
        DependencyProperty.Register("ViewModel", typeof(MainPageViewModel), typeof(MainPage), null);
}

However, the InvokeCommand fails, as it requires the ReactiveCommands to take the bool, instead of Unit. Any idea how I can invoke a command when certain conditions are met?

resp78
  • 1,414
  • 16
  • 37

2 Answers2

3

If you want to turn a stream (IObservable<(long, float)> FlowStream) on and off based on a IObservable<bool> IsStreamOn observable then you can do this:

IObservable<(long, float)> outputStream =
    IsStreamOn
        .Select(flag => flag ? FlowStream : Observable.Never<(long, float)>())
        .Switch();

So each time IsStreamOn produces a true you start getting values from FlowStream, otherwise the values stop.

This assumes that FlowStream is hot. If not, do this:

IObservable<(long, float)> outputStream =
    FlowStream
        .Publish(fs =>
            IsStreamOn
                .Select(flag => flag ? fs : Observable.Never<(long, float)>())
                .Switch());

Here's a simple test:

void Main()
{
    IObservable<long> outputStream =
        FlowStream
            .Publish(fs =>
                IsStreamOn
                    .Select(flag => flag ? fs : Observable.Never<long>())
                    .Switch());

    using (outputStream.Subscribe(Console.WriteLine))
    {
        IsStreamOn.OnNext(true);
        Thread.Sleep(TimeSpan.FromSeconds(2.5));
        IsStreamOn.OnNext(false);
        Thread.Sleep(TimeSpan.FromSeconds(3.0));
        IsStreamOn.OnNext(true);
        Thread.Sleep(TimeSpan.FromSeconds(3.0));
    }

}

IObservable<long> FlowStream = Observable.Interval(TimeSpan.FromSeconds(1.0));
Subject<bool> IsStreamOn = new Subject<bool>();

This produces:

0
1
5
6
7

Given the comments re actually calling .Start() and .Stop() then try something like this:

IObservable<(long, float)> outputStream =
    Observable
        .Create<(long, float)>(o =>
        {
            var stream = GetStream();
            return 
                FlowStream
                    .Publish(fs =>
                        IsStreamOn
                            .Do(flag => { if (flag) stream.Start(); else stream.Stop(); })
                            .Select(flag => flag ? fs : Observable.Never<(long, float)>())
                            .Switch())
                    .Subscribe(o);
        });
Enigmativity
  • 113,464
  • 11
  • 89
  • 172
  • Maybe I did not put it clearly. I want to know how do I send a **command** to start and stop a stream. I have stream API `Start` and `Stop` to control the underlying event pump. – resp78 Sep 25 '17 at 06:58
  • @resp78 - Still not clear enough as that's what I think I've done. Can you perhaps be even clearer? – Enigmativity Sep 25 '17 at 07:19
  • @resp78 - Oh, so you want to send the `stream.Start()` and `stream.Stop()` calls according to the values produced from `IsStreamOn`? That's fairly simple. But you really should post the full code for `GetStream()` as there's probably a much cleaner way to do it that the approach I'm going to suggest. – Enigmativity Sep 25 '17 at 07:23
  • @resp78 - I've added a suggestion to the end of my answer. – Enigmativity Sep 25 '17 at 07:31
  • GetStream is a mundane method. The interesting bit is that when the `Start` is called it starts firing `DataAvailable` event with the args `StreamDataEventArgs`. Will try your suggestion soon. – resp78 Sep 25 '17 at 07:35
  • For me both the answer worked. Your answer also made me aware about some of the practical usage of switch operator. I am now evolving my design and likely to dump the event and create observable directly to be consumed in the VM. Whilst undergoing this thinking I realised that I might have to dispose the observable completely on stop and recreate it again when I restart. How may I achieve that? (maybe it should be a separate question on SO, but thought we have the context here for others) – resp78 Sep 26 '17 at 13:24
  • @resp78 - You actually rarely need to stop and restart an observable. There are usually ways to make that part of the query. Doing `var s = new Subject(); var q = (from _ in s select source).Switch();` is a way to resubscribe to the `source` by just sending a new value to the subject. – Enigmativity Sep 26 '17 at 23:01
1

In these scenarios with your observables I tend to do

var canSwitchOn = this.WhenAnyValue(x => x.IsStreamOn).Select(_ => Unit.Default);

That will allow you not to have the bool passed along to the command.

oh also you may want a where() clause in this cause if you want to trigger a command in the right condition.

eg.

var switchOn = this.WhenAnyValue(x => x.IsStreamOn).Where(x => x).Select(_ => Unit.Default);
var switchOff = this.WhenAnyValue(x => x.IsStreamOn).Where(x => !x).Select(_ => Unit.Default);
Glenn Watson
  • 2,758
  • 1
  • 20
  • 30
  • I seen some people make a extension method like IgnoreResult() or something that will do the Select(_ => Unit.Default> on a IObservable – Glenn Watson Sep 25 '17 at 07:07
  • This works the first time only. Though when I toggle the switch, with a breakpoint in the select operator, the breakpoint is indeed hit. Not sure why it is not invoking the commands second time . – resp78 Sep 25 '17 at 07:32
  • Two possibilities, one is the CanExecute observable. You may not want the canExecute in this case, second, it could be that the Stream.Start() is still executing, and Stream.Stop(), check the command's IsExecuting property to see. – Glenn Watson Sep 25 '17 at 07:52
  • Especially if you use the approach I suggested of doing .Where() statements checking the value of bool to split into two observables the canExecute wouldn't be needed. – Glenn Watson Sep 25 '17 at 07:54