0
votes

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.

1
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 minimal reproducible example. - Charles Mager
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
@CharlesMager I have updated original post - MistyK
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
I think you'd need to provide a minimal reproducible example, 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

1 Answers

1
votes

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());
}