Thinking & Doing


Feed
BY  Joe Meyer  ON  11-29-2016 3:26 PM

Over the years, I’ve gotten to write a few mobile apps both on my own time and for work. Inevitably, each iteration tackles many of the same problems as its predecessor. How should we handle extra clicks? What happens if the network goes out? How should we refresh the data shown to the user and will it be current? Each time my implementation differs slightly (hopefully) converging on an altogether "good" solution - in this post, I'd like to discuss one tool that I stumbled upon in my academic wanderings that helps developers address such problems.

A Primer on Observables

If you're reading this, you're probably already familiar with the concept of observables, and have likely already used them somewhere in your development, mobile or otherwise. Put simply, they are a means of subscribing to and consuming different types of dynamic data in our code. With that in mind, it's easy to see that observables lend themselves especially nicely to mobile apps; our programs should respond to new data as it arrives and react to events as they occur without direct intervention from the user. That’s where Reactive Extensions come in to make our job of handling these events easier, simpler, and more clear. In my exploration of Reactive Extensions, I found ReactiveUI to be especially good at allowing me to describe how events should be handled in a concise, declarative manner and in well-defined layers with clear separation of concerns – an MVVM developer’s dream! So, combining my fledgling knowledge of ReactiveUI with my continuing love affair with Xamarin.Forms was a match made in heaven and I set out to try on some of the coolest capabilities ReactiveUI had to offer.

A Compelling Example

Let's looks at some ways we can use some of the tools provided by ReactiveUI in a practical scenario. In any mobile app, network connectivity can be a tricky thing. Sure, it's easy enough to check for a connection on launch, but how should the app behave if the user switches from WiFi to cellular? Or if the user loses connectivity entirely? In most cases, we can assume an app should inform the user that they are offline. Sometimes, it may make sense to check for connectivity before performing an network operation, but that can add some extra overhead and we may be tempted to sprinkle our checks in a few different places around our code. Instead, we can use ReactiveUI to determine when network connectivity has been lost and instead disable any functionality the user would need the network for - that way, we needn't check for connectivity on every call, we can simply prevent the call from happening in the first place!

In this example, let's assume the user is checking the arrival status of various train routes at a nearby station. As trains are arriving and departing, we want to refresh the UI to display the latest arrival information periodically, but also allow the user to force a refresh manually. In the case where network connectivity is lost, we want to ensure both background refresh and manual refresh are disabled as well as inform the user that they the information displayed may not be current. Most of these goodies are stashed in the ViewModel, so let's start there. First, we'll need to define our search function as a ReactiveCommand that returns some object, like so:

        private ReactiveCommand<List<Arrival>> _background;

        public ReactiveCommand<List<Arrival>> Background

        {

            get { return _background; }

            private set { this.RaiseAndSetIfChanged(ref _background, value); }

        }

 

The initialization of this is trivial in our simple example, so I won't bore you with the details, but keep in mind that we would also need to setup some additional bindings and define a few properties to make this all tick. Refer to the GitHub source for more info. Instead, let's turn to something a little more interesting; we start by defining the conditions under which our search function may execute declaratively: 

 

            var searchQueryNotEmpty = this.WhenAnyValue(vm => vm.SearchQuery).Select(query => !string.IsNullOrEmpty(query)).DistinctUntilChanged();

 

In this first statement, we ensure that the user has selected a station. For the sake of simplicity, we are fetching status updates for a single station, regardless of what the user types, but you can imagine a type-ahead suggestion, drop-down list, or location search function tie-in in a "real" application to hydrate these values.

 

            var searchNotExecuting = this.WhenAnyObservable(x => x.Search.IsExecuting).DistinctUntilChanged();

Here, we ensure that the search is not already executing before we allow the user to search again. This is a form of throttling that prevents a user from accidentally spamming the execution of the search, such as in the case of an accidental double-tap. ReactiveUI also provides the means to throttle by a time delay - that is, we can wait a set amount of time before performing execution, which can be especially useful when performing instant searches to prevent each character typed by the user from executing a search (Presumably, we wouldn't want to perform the search until we have some certainty that the app has captured the user's intent, rather than waste IO and resources on an incomplete idea as it is typed).

For the sake of demonstration, let's also include the ability to manually disable refresh entirely from the UI:

            var refreshEnabled = this.WhenAnyValue(x => x.IsRefreshEnabled).DistinctUntilChanged();

Using ReactiveUI, we can combine these three statements together to form a single, cohesive idea about under what conditions we should allow refresh:

            canSearch = Observable.CombineLatest(searchQueryNotEmpty, searchNotExecuting, refreshEnabled,

                        (isSearchQuery, isExecuting, allowRefresh) => isSearchQuery && allowRefresh && !isExecuting)

                    .Do(cps => Debug.WriteLine($"Log some relevant info here"))

                    .DistinctUntilChanged(); 

 

Notice that we can also introduce some instrumentation in our combined “can search” statement, which in this case allows us to log some very relevant info anytime the observable value of canSearch changes. This could be anything you need, from logging the value of each sub-statement, to timestamps or debugging info.


But wait! We still need to affordances in the event of an error - specifically, let’s look at how we can react when connectivity fails:

 

            // ThrownExceptions is any exception thrown from the CreateFromObservable piped

            // to this observable. Subscribing to this allows us to handle errors on the UI thread

            Search.ThrownExceptions

                .Subscribe(async ex =>

                {

                    if(Debugger.IsAttached) Debugger.Break();

 

                    // Show a dialog to the user - UserError is renamed to UserInteraction in v7

                    var result = await UserError.Throw("Network Error", ex);

 

                    // here, we can take different actions depending on the user input

                    if (result == RecoveryOptionResult.RetryOperation && Search.CanExecute(null))

                        Search.Execute(null);

                });


Notice here that we are creating a binding to ANY thrown exception during execution. Were we giving this due diligence, we'd certainly be more rigorous in determining what action to take, rather than assuming a network error. For simplicity's sake however, let's assume connectivity loss and prompt the user with a few recovery options. Should the user wish to try the search again immediately, they may indicate such from the UI, which we've constructed on the view page:

 

            // User error allows us to interact with our users and get feedback on how to handle an exception

            UserError

                .RegisterHandler(async (UserError arg) => {

                    var result = await this.DisplayAlert("Failed to get latest schedule", $"{arg.ErrorMessage}{Environment.NewLine}Retry search?", "Yes", "No");

                    return result ? RecoveryOptionResult.RetryOperation : RecoveryOptionResult.CancelOperation;

                }).DisposeWith(_bindingsDisposable);


In practice, we can easily demonstrate this via Visual Studio’s handy Android Simulator by simply changing the network type to Cellular > No Network. And voila! Our dialog box appears with the expected options:

 

By re-enabling our network connection, we can then successfully refresh the data by clicking “Yes”. Let’s look at all the elements working together:

It’s that easy! Notice how the refresh button automatically responds to the executing command while the refresh toggle is set to “off” or we are already executing. For the full details on my implementation, take a look at the source code.

ReactiveUI + Xamarin

Looking to get started on Reactive extensions yourself? Here are a few extra links I found handy:

SYNDICATION

Archives