Search code examples
c#xamarinxamarin.formsreactiveuiicommand

React to updates on ObservableCollection


I have the following piece of code:

ReactiveCommand
    .CreateFromTask(_ => GetPerson())
    .Execute()
    .Subscribe(x => person = x);

var canNext = person?.Friends
                .ToObservableChangeSet()
                .AutoRefresh(x => x.Selected)
                .ToCollection()
                .Select(x => x.Any(y => y.Selected));

MyCommand = ReactiveCommand.CreateFromTask(async _ => Debug.Write("Hello"), canNext);

How can I get canNext to update in order to disable/enable my command?

If I hard code a person with friends the canNext works fine when selecting and deselecting friends. However when assigning person to an async call GetPerson() it breaks any updates running to canNext.

I've created a dummy project to help demo the problem. https://github.com/grierson/updateButton


Solution

  • How to fix?

    To get the behavior you want you could first initialize person, and then when you call GetPerson(), just update the person you already have, as follows

    person = new Person() { Friends = new ObservableCollection<Friend>() };
    
    ReactiveCommand
        .CreateFromTask(_ => GetPerson())
        .Execute()
        .Subscribe(x =>
        {
            foreach (var friend in x.Friends)
            {
                person.Friends.Add(friend);
            }
        });
    
    var canNext = person?.Friends
        .ToObservableChangeSet()
        .AutoRefresh(x => x.Selected)
        .ToCollection()
        .Select(x => x.Any(y => y.Selected));
    
    MyCommand = ReactiveCommand.CreateFromTask(async _ => Debug.Write("Hello"), canNext);
    

    Now some words about what you are doing wrong:

    Why button is not changing?

    The reason for this behavior is that when you run

    ReactiveCommand
        .CreateFromTask(_ => GetPerson())
        .Execute()
        .Subscribe(x => person = x);
    
    var canNext = person?.Friends
        .ToObservableChangeSet()
        .AutoRefresh(x => x.Selected)
        .ToCollection()
        .Select(x => x.Any(y => y.Selected));
    
    MyCommand = ReactiveCommand.CreateFromTask(async _ => Debug.Write("Hello"), canNext);
    

    at the moment you reach the line that sets MyCommand, the value of canNext is null: this happens because person is null at that point of time, and so you are passing a null value to the canExecute parameter of CreateFromTask, i.e. you are really setting

    MyCommand = ReactiveCommand.CreateFromTask(async _ => Debug.Write("Hello"), null);
    

    or what is the same

    MyCommand = ReactiveCommand.CreateFromTask(async _ => Debug.Write("Hello"));
    

    that is, you are not really passing any information to canExecute, and the Button is simply always active, as you can see when you run the code above.

    P.S.: Thanks for the simple sample. It was key on being able to find out the root of the problem.