0

I'm migrating from reactive 4.5 to 6.5.0 and I encounter some issues. I have a WPF app with button bound to ReactiveCommand. Previously I was using ReactiveCommand constructor like this:

 _runProcessCommand = new ReactiveCommand(CanRunProcess(null));
 _runProcessCommand.Subscribe(RunImpl);

 public IObservable<bool> CanRunProcess(object arg)
{
    return this.WhenAny( ... )
}

Now I've changed it to be:

_runProcessCommand = ReactiveCommand.Create(CanRunProcess(null));
_runProcessCommand..Subscribe(RunImpl);

So I expected that the behaviour should be exactly the same but it isn't. My button is disabled until I change something from WhenAny in CanRunProcess bound which are basically properties from UI. It happens in many places in the project so there is no mistake. Is anything different between these two ways of creating ReactiveCommand? How to achieve the same result? The funny thing is when I subscribe to CanExecuteObservable it works as expected:

_runProcessCommand.CanExecuteObservable.Subscribe(x =>
            {
               Debug.WriteLine(x);
            });

and it's the same when I Invoke CanExecute explicitly:

  var c = _runProcessCommand.CanExecute(null);

I suppose it may be related with lazyness somewhere but I don't understand why it would be the case because button should invoke CanExecute to get the current initial value.

When I subscribe to CanRunProcess I get a lot of falses followed by a lot of trues and the last value is true which I suspect should enable the command.

  CanRunProcess(null).Subscribe(x =>
            {
                Debug.WriteLine(x);
            });

EDIT: I've downloaded ReactiveUI sources and I've noticed that there is no subscription to canExecute but instead Dofunction is being used:

 this.canExecute = canExecute.CombineLatest(isExecuting.StartWith(false), (ce, ie) => ce && !ie)
                .Catch<bool, Exception>(ex => {
                    exceptions.OnNext(ex);
                    return Observable.Return(false);
                })
                .Do(x => {
                    var fireCanExecuteChanged = (canExecuteLatest != x);
                    canExecuteLatest = x;

                    if (fireCanExecuteChanged) {
                        this.raiseCanExecuteChanged(EventArgs.Empty);
                    }
                })
                .Publish();

It looks like something needs to instantiate it - something needs to call

either CanExecuteObservable or CanExecute to instantiate canExecute object. Why isn't it created when you bind it to the button?

After debugging ReactiveUI sources I know exactly what happens. Do is lazy function so until connect function is invoked handler won't be executed. It means that canExecuteLatest will be false when command is being bound to the button and when calling CanExecute function, so the button stays disabled.

Reproducable example (note it works when I do the same example with WhenAny):

 public class MainViewModel : ReactiveObject
    {
        private ReactiveCommand<object> _saveCommand;
        private string _testProperty;
        private ReactiveList<string> _ReactiveList;

        public ReactiveCommand<object> SaveCommand
        {
            get
            {
                return _saveCommand;
            }
            set { this.RaiseAndSetIfChanged(ref _saveCommand, value); }
        }


        public ReactiveList<string> ReactiveList
        {
            get
            {
                return _ReactiveList;
            }
            set { this.RaiseAndSetIfChanged(ref _ReactiveList, value); }
        }


        public MainViewModel()
        {
            ReactiveList = new ReactiveList<string>();
            ReactiveList.ChangeTrackingEnabled = true;

            SaveCommand = ReactiveCommand.Create(CanRunSave(null));
            SaveCommand.Subscribe(Hello);

            // SaveCommand.CanExecute(null); adding this line will invoke connect so the next line will run CanSave and enable the button. 

            ReactiveList.Add("sad");
        }

        public void Hello(object obj)
        {

        }

        private IObservable<bool> CanRunSave(object arg)
        {
            return ReactiveList.Changed.Select(x => CanSave());
        }

        private bool CanSave()
        {
            return ReactiveList.Any();
        }
    }



<Window x:Class="WpfApplication8.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Button Content="test" Command="{Binding SaveCommand}" />
    </Grid>
</Window>


public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = new MainViewModel();
        }
    }

Button is still disabled even though I add something to ReactiveList. The problem is that updates between creating the command and binding it to the button are ignored because connect wasn't called so the changes are simply not reflected.

MistyK
  • 6,055
  • 2
  • 42
  • 76
  • You're correct that the subscription to your 'can execute' observable is lazy, but an observable created using `this.WhenAny` should always return an initial value, so it shouldn't be important. I think you need to give some more detail, preferably a [mcve]. – Charles Mager May 10 '16 at 08:22
  • thanks Charles for you answer. I can't reproduce it for smaller example. It's interesting for me that when I subscribe to canExecute observable and the last value is true then still it doesn't enable the command. I am confused with the way it works. – MistyK May 10 '16 at 08:42
  • @CharlesMager I have updated original post – MistyK May 10 '16 at 11:45
  • I'm not sure your analysis is correct - on calling `CanExecute`, the observable is connected. If the first result from your observable is `true`, then it will be different to `canExecuteLatest` and it will be updated / the event will be raised / `true` will be returned. You've verified this yourself by calling `CanExecute` and it returning `true`. All 'binding' does it call that method to set the enabled state. – Charles Mager May 10 '16 at 11:50
  • I think you'd need to provide a [mcve], as I suggested before. I wouldn't expect an expression using `WhenAny` to return 'a lot of falses followed by a lot of trues' - it's likely something in the way this observable is defined that explains the issue. – Charles Mager May 10 '16 at 11:52
  • if you look into ReactiveUI sources you may notice that canExecute is not subscribed but instead they use 'Do' function (in the ReactiveCommand constructor) and after that they use publish which creates an instance of connectable observable. Until you call 'connect' on that object then all messages to 'Do' function will be ignored so canExecuteLatest wil stay false. – MistyK May 10 '16 at 11:56
  • The act of connecting subscribes to the underlying observable - it's not subscribed until then, so no messages will be 'ignored' unless your observable is 'hot' (i.e. it produces values even when no one is subscribed). This is hard to tell, as you've given no code for it, but that wouldn't be expected from a simple `WhenAny`. – Charles Mager May 10 '16 at 12:07
  • It's not really simple WhenAny - it's more complex. I will try to provide an example – MistyK May 10 '16 at 12:10
  • @CharlesMager Please see edit - btw thanks for you help, I know that I should've provided that example earlier, sorry for that – MistyK May 10 '16 at 12:18
  • Yep, now you can spot the problem immediately! Answer incoming... – Charles Mager May 10 '16 at 12:24

1 Answers1

1

The issue in your example is that the Changed event on ReactiveList<T> is essentially a hot observable. It is producing changes that occur even if no observer is subscribed. When an observer does subscribe, any previous changes will have been missed.

The result of this is that a subscriber to CanRunSavewill not get any initial value. The first value received will be the result of the first change to the ReactiveList (e.g. the next addition/removal) after subscription.

As a result of the laziness in ReactiveCommand, any change to the list before CanExecute is called (which is when the observable is subscribed to) will be missed. On subscription, there will be no initial value, so command's 'can execute' state will be the default of false until the list is changed.

The fix is surprisingly simple - make sure there is an initial value on subscription. You can do this using StartWith:

private IObservable<bool> CanRunSave(object arg)
{
    return ReactiveList.Changed.Select(_ => Unit.Default)
        .StartWith(Unit.Default)
        .Select(_ => CanSave());
}
Charles Mager
  • 25,735
  • 2
  • 35
  • 45
  • Thanks mate, I wasn't aware of hot and cold subscriptions. As I understand thanks to StartWith method, when something starts subscribing to the observable object it will receive initial value so the condition will be reevaluated, is that correct? – MistyK May 10 '16 at 12:45
  • Yes - the change is essentially to create a sequence that always has an initial value and then any changes from the list. This then calls `CanSave` to produce a boolean value. The result is that on subscription, you will immediately get a value from the result of `CanSave` followed by a new result each time the list changes. – Charles Mager May 10 '16 at 12:47
  • 1
    `StartWith` is pretty similar to `Observable.Return(initial).Concat(changes)` - take a look [here](http://www.introtorx.com/content/v1.0.10621.0/12_CombiningSequences.html#StartWith). – Charles Mager May 10 '16 at 12:49
  • Brilliant, that's really simple, I should have read the documentation earlier :) Thanks! – MistyK May 10 '16 at 12:53