Question

Let us say I have this ReactiveUI view model structure, with Model being some arbitrary model type.

class ViewModel : ReactiveObject
{
    private readonly ReactiveList<Model> _models;
    public IReactiveList<Model> Models { get { return _models; } }

    public IReactiveCommand LoadModels { get; private set; }
    public bool LoadingModels { get; private set; } // Notifies;
}

And these models come from a task-based asynchronous API modeled by this interface:

interface ITaskApi
{
    Task<IEnumerable<Model>> GetModelsAsync();
}

After taking a look at how Octokit.net's reactive library was written, I wrote the following class to adapt the API to a reactive world:

class ObservableApi
{
    private readonly ITaskApi _taskApi;

    public IObservable<Model> GetModels() {
        return _taskApi.GetModelsAsync().ToObservable().SelectMany(c => c);
    }
}

And now, I have written the following ways to implement loading of models inside of the the LoadModels command, in the ViewModel() constructor:

// In both cases, we want the command to be disabled when loading:
LoadModels = new ReactiveCommand(this.WhenAny(x => x.LoadingModels, x => !x.Value));

// First method, with the Observable API;
LoadModels.Subscribe(_ =>
    {
        LoadingModels = true;
        _models.Clear();
        observableClient.GetModels().ObserveOnDispatcher()
            .Subscribe(
                m => _models.Add(m),
                onCompleted: () => { LoadingModels = false; });
    });

// Second method, with the task API;
LoadModels.Subscribe(async _ =>
    {
        LoadingModels = true;
        try {
            var loadedModels = await taskClient.GetModelsAsync();
            _models.Clear();
            _models.AddRange(loadedModels);
        } catch (Exception ex) {
            RxApp.DefaultExceptionHandler.OnNext(ex);
        } finally {
            LoadingModels = false;
        }
    });

While I think that both ways will do the job, I have the following misgivings:

  • In the first sample, should I dispose the inner subscription or will that be done when the inner observable completes or errors?
  • In the second sample, I know that exceptions raised in the GetModelsAsync method will be swallowed.

What is the "best", most idiomatic way to populate a ReactiveList<T> from an asynchronous enumeration (either IObservable<T> or Task<IEnumerable<T>> (or would IObservable<IEnumerable<T>> be better?))?

Was it helpful?

Solution

After taking a look at how Octokit.net's reactive library was written, I wrote the following class to adapt the API to a reactive world:

While you sometimes want to do this (i.e. flatten the collection), it's usually more convenient to just leave it as IEnumerable<T> unless you then plan to invoke an async method on each item in the list. Since we just want to stuff everything in a List, we don't want to do this. Just leave it as Task<T>

In the first sample, should I dispose the inner subscription or will that be done when the inner observable completes or errors?

Any time you have a Subscribe inside another Subscribe, you probably instead want the SelectMany operator. However, there is a better way to do what you're trying to do, you should check out this docs article for more info.

So, here's how I would write your code:

// In both cases, we want the command to be disabled when loading:
LoadModels = new ReactiveCommand();

LoadModels.RegisterAsyncTask(_ => taskClient.GetModelsAsync())
    .Subscribe(items => 
    {
        // This Using makes it so the UI only looks at the collection
        // once we're totally done updating it, since we're basically
        // changing it completely.
        using (_models.SuppressChangeNotifications())
        {
            _models.Clear();
            _models.AddRange(items);
        }
    });

LoadModels.ThrownExceptions
    .Subscribe(ex => Console.WriteLine("GetModelsAsync blew up: " + ex.ToString());

// NB: _loadingModels is an ObservableAsPropertyHelper<bool>
LoadModels.IsExecuting
    .ToProperty(this, x => x.LoadingModels, out _loadingModels);
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top