14
\$\begingroup\$

Most of the Rx Compelling ExamplesTM 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 WrongTM.

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);
 }
}
asked Dec 23, 2014 at 16:30
\$\endgroup\$
1
  • \$\begingroup\$ I added the language tag to your question. Please correct it if I got it wrong. \$\endgroup\$ Commented Dec 23, 2014 at 16:37

1 Answer 1

11
\$\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.

answered Dec 24, 2014 at 18:08
\$\endgroup\$
4
  • 1
    \$\begingroup\$ Is there any pros/cons to putting the 'WhenAnyValue' code in the view? \$\endgroup\$ Commented Dec 29, 2014 at 19:07
  • 1
    \$\begingroup\$ Yes, on WPF you need to use the WhenActivated method to avoid leaking memory \$\endgroup\$ Commented Dec 29, 2014 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\$ Commented Mar 16, 2015 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\$ Commented Mar 17, 2015 at 1:33

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.