11
\$\begingroup\$

Most of the Rx Compelling Examples™ load and store data locally, which I don't find that Compelling.

I spent some time coming up with how to best perform the following actions but want to make sure I am not Doing It Wrong™.

Here are the cases I am trying to solve:

  1. Load data from cache and remote server when view model is instantiated
  2. Save data to server when UI interaction happens (some button press)
  3. Handle error cases (network down)

Some items I am trying to avoid:

  1. Errors being swallowed (async in ctor for example)
  2. Incorrect use of Rx patterns (is there a more 'correct' way)
  3. Loss of data either through out of sync lists, not using cache correct, other.

public class CommonViewModel : ReactiveObject, IRoutableViewModel
{
    private readonly RemoteHttpService _remoteClient;

    private ReactiveList<Item> _items;
    public ReactiveList<Item> ItemsFromServer
    {
        get { return _items; }
        set { this.RaiseAndSetIfChanged(ref _items, value); }
    }

    public ReactiveCommand<List<Item>> LoadItems{ get; protected set; }
    public ReactiveCommand<object> SendItemsToServerCommand { get; protected set; }

    public string UrlPathSegment
    {
       get { return "CommonViewModel View"; }
    }

    public IScreen HostScreen { get; private set; }

    public CommonViewModel(IScreen screen)
    {
        HostScreen = screen;
        ItemsFromServer = new ReactiveList<Item>();
        _remoteClient = new RemoteHttpService(); //using refit and polly behind the scenes.

        SendItemsToServerCommand = ReactiveCommand.CreateAsyncTask((_, ctx) => InternalSendItemsToServer());
        SendItemsToServerCommand.ThrownExceptions.Subscribe(ex => UserError.Throw("Could not send data to server", ex));

        LoadItems = ReactiveCommand.CreateAsyncObservable(_ =>     this.InternalGetItemsFromCacheAndServer());
        LoadItems.Subscribe(list =>
        {
             _items.Clear();
            foreach (var item in list){
                 //using AddRange throw Presentation Exception
                _items.Add(item);
            }
         });
        LoadItems.ThrownExceptions.Subscribe(ex => UserError.Throw("Could not get data from server"));
        LoadItems.ExecuteAsyncTask();
    }

    private IObservable<List<Item>> InternalGetItemsFromCacheAndServer()
    {
        return BlobCache.LocalMachine.GetAndFetchLatest("items",
        InternalGetRemoteItems,
        dt => true,
        RxApp.MainThreadScheduler.Now + TimeSpan.FromMinutes(5));
    }

    private async Task<List<Item>> InternalGetRemoteItems()
    {
        var conferences = await _remoteClient.GetItems().ConfigureAwait(false);
        return conferences;
    }

    private async Task<object> InternalSendItemsToServer()
    {
        var itemsToSendToServer = ItemsFromServer.SomeLinq().Maybe();
        return await _remoteClient.SendDataToServer(itemsToSendToServer);
    }
}
\$\endgroup\$
  • \$\begingroup\$ I added the language tag to your question. Please correct it if I got it wrong. \$\endgroup\$ – RubberDuck Dec 23 '14 at 16:37
9
\$\begingroup\$

Sorry to disappoint, but you did everything right! This is a quite good example of using RxUI, Akavache, and Refit together.

The only thing I would change, is to not immediately call LoadItems.ExecuteAsyncTask in the ViewModel constructor. Invoking this in the VM constructor means that your VM class becomes more difficult to test, because you always have to mock out the effects of calling LoadItems, even if the thing you are testing is unrelated.

Instead, I always call these commands in the View constructor, something like:

this.WhenAnyValue(x => x.ViewModel.LoadItems)
    .SelectMany(x => x.ExecuteAsync())
    .Subscribe();

This means that any time we get a new ViewModel, we execute LoadItems, which is what we want when the app is running, but not what we want in every unit test.

\$\endgroup\$
  • 1
    \$\begingroup\$ Is there any pros/cons to putting the 'WhenAnyValue' code in the view? \$\endgroup\$ – detroitpro Dec 29 '14 at 19:07
  • 1
    \$\begingroup\$ Yes, on WPF you need to use the WhenActivated method to avoid leaking memory \$\endgroup\$ – Ana Betts Dec 29 '14 at 19:23
  • \$\begingroup\$ I would really try to return IObservable<Item> from your load items method in order to use subscription to add one item to the _items list as they arrive (or use Buffer to get chunks). Your current implementation will not block the UI but will wait until you retrieve all items from the server before it shows something. \$\endgroup\$ – Alexey Zimarev Mar 16 '15 at 20:48
  • 1
    \$\begingroup\$ I disagree with Alexey - unless your API itself is streaming items on an ongoing basis, your UI will not be any more usable if dozens of items per second are flying into a list. Showing a great "in progress" visual as well as a cancel button is the way to go here. \$\endgroup\$ – Ana Betts Mar 17 '15 at 1:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.