When working on an app that relies on getting data from an API, it is crucial to make the waiting and loading experience as seamless as possible for the user – like Facebook, Amazon, LinkedIn apps do. Unresponsive app will most likely be uninstalled by the user and you can say goodbye to your customers, revenue, user base, whatever it is you made the app for.
One of the biggest issue API reliant apps face is unreliable network connectivity. As users move around with their mobile devices, they are bound to run into poor or dropped network connectivity issues. We must have a strategy around transient faults and network issues, and that’s where Polly comes to the rescue.
Polly has been around for a while and is a great tool for making your app more responsive. It helps in developing strategies around transient faults i.e. dropped network connection, weak transmission resulting in errors, etc. In this post, I will demonstrate how to simplify the use of Polly in our Xamarin.Forms MVVM app. You can build from there and make the strategies as complex as needed.
Polly works with any .NET application. Which means that you can apply these resilience policies to your .NET API layer as well.
Scenario
Let’s build an app that pulls a list of countries from a REST API. If the API is ever down or not responsive for a period, we still want to get the data without throwing up a bunch of alert messages. To do this, we employ a policy to retry a given number of times if a call fails at first. And in some cases, wait before retrying to give the API time to recover and cater to our requests.
Architecture
We structure our code in several layers/nodes/tiers however you want to call them. This will keep the code decoupled based on behavior. We will have the following layers,
- Models – for all our model objects
- Common – resources and anything else used app wide
- Services – services to make API calls
- Core – Shared MVVM code
- iOS – iOS platform specific code
- Android – Android specific code
In my opinion, the best OSS framework currently available in the
market to write MVVM Xamarin.Forms app is Prism.Forms. It provides services to handle navigation, dependency injection, events, etc. So we will be using Prism.Forms for this app.
Components
These are the main components of the app that make use of Polly as a stand-alone service that can be used optionally.
IClient
We will use the mono managed HttpClient
for this demo. But since we can swap the implementation of the client for numerous reasons, we will keep our client independent using IClient
abstraction.
INetworkService
This service will handle our Polly policies. We want to keep all my policies in one place so that we can easily manage them and test them.
IApiService
This service will handle all API calls. We can choose to use Polly policies (via INetworkService
) or not in this service. We, as developers, have the freedom to call APIs as we need since not all calls need a policy e.g. POST that can cause duplication or errors will not be retried. All our functional services will inject IApiService
to make API calls.
The main component to pay attention to here is the INetworkService
. We inject this service in the ApiService
and use for selective calls. This allows us to mock and test our services and gives us the option to replace Polly with any other implementation in the future without changing code in all services, just like our IClient
… de-coupled 😉.
Resilience Policies
We can have a policy for any type of exception we want to handle. For this app, however, we will implement Retry
and WaitAndRetry
policies based on the catch all Exception
since we do not have any special flow for different exceptions. We use these as a starting point and build on top to get more complex policies for our app. Check Polly’s documentation to learn about all its powers.
Let’s start with the INetworkService
with Polly policies.
Retry
Our Retry
method will take a Task
to execute on retry, a retryCount
, and a Func
to execute on each retry.
WaitAndRetry
Much like Retry
, our WaitAndRetry
will add a wait time between each retry giving the API server to recover (and show a status to the user while they wait, log, etc.)
Here is how our completed INetworkService
will look like,
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public interface INetworkService | |
{ | |
Task<T> Retry<T>(Func<Task<T>> func, int retryCount, Func<Exception, int, Task> onRetry); | |
Task<T> WaitAndRetry<T>(Func<Task<T>> func, Func<int, TimeSpan> sleepDurationProvider, int retryCount, | |
Func<Exception, TimeSpan, Task> onRetryAsync); | |
} |
The implementation for Retry
will be as such,
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
internal async Task<T> RetryInner<T>(Func<Task<T>> func, | |
int retryCount, | |
Func<Exception, int, Task> onRetry) | |
{ | |
return await Policy | |
.Handle<Exception>() | |
.RetryAsync(retryCount, onRetry) | |
.ExecuteAsync<T>(func); | |
} |
and our WaitAndRetry
will be implemented like,
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
internal async Task<T> WaitAndRetryInner<T>(Func<Task<T>> func, Func<int, TimeSpan> sleepDurationProvider, | |
int retryCount, | |
Func<Exception, TimeSpan, Task> onRetryAsync) | |
{ | |
return await Policy | |
.Handle<Exception>() | |
.WaitAndRetryAsync(retryCount, sleepDurationProvider, onRetryAsync) | |
.ExecuteAsync<T>(func); | |
} |
As mentioned before, we are only handling exceptions of type System.Exception
, but we can handle other exceptions e.g. .Handle<SqlException>
and retry database connection, .Handle<HttpResponseException>
and handle response issues, etc. We can also combine these as .Handle<SqlException>().Or<HttpResponseException>()
.
Usage
Now that we have our policies in place, we will simply inject INetworkService
into our IApiService
and use its policies.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class ApiService : IApiService | |
{ | |
readonly IClient _client; | |
readonly INetworkService _networkService; | |
public ApiService(IClient client, INetworkService networkService) | |
{ | |
_client = client; | |
_networkService = networkService; | |
} | |
// No policies | |
public async Task<T> GetAsync<T>(Uri uri) where T : class | |
{ | |
return await ProcessGetRequest<T>(uri); | |
} | |
// With policies | |
public async Task<T> GetAndRetry<T>(Uri uri, int retryCount, Func<Exception, int, Task> onRetry = null) where T : class | |
{ | |
var func = new Func<Task<T>>(() => ProcessGetRequest<T>(uri)); | |
return await _networkService.Retry<T>(func, retryCount, onRetry, cancelToken); | |
} | |
public async Task<T> GetWaitAndTry<T>(Uri uri, Func<int, TimeSpan> sleepDurationProvider, int retryCount, Func<Exception, TimeSpan, Task> onWaitAndRetry = null) where T : class | |
{ | |
var func = new Func<Task<T>>(() => ProcessGetRequest<T>(uri)); | |
return await _networkService.WaitAndRetry<T>(func, sleepDurationProvider, retryCount, onWaitAndRetry, cancellationToken); | |
} | |
} |
Finally, we will call the IApiService
from our functional service,
public class SomeService { IApiService apiService; public SomeService(IApiService apiService) { _apiService = apiService; } }
Retry,
var response = await _apiService.GetAndRetry(host, DEFAULT_COUNT, OnRetry);
Wait and retry,
var response = await _apiService.GetWaitAndRetry(host, GetSleepDuration, DEFAULT_COUNT, OnWaitAndRetry);
That’s it. Now we can customize the behavior in OnRetry
and/or OnWaitAndRetry
and control the UI flow.
Making Sense Of It All…
So, what did we just do. We introduced a new service that holds all our resilience policies. Why keep it in a service in itself? For one, it gives us the ability to test our services with and/or without resilience policies. It let’s us swap Polly with any other implementation when the time comes by simply swapping out one service. Finally, it let’s us introduce resilience policies later in the project with minimum effort.
Fancy Bonus
How about we fancy our app a bit and let the user know that we are working hard on their request. What I would like to do is to change my Loading...
text to Still loading...
on my 2nd retry. To do so, we may want to bring our retry delegates into the ViewModel to have better control. But in this app, the service will simply publish an event that it is retrying along with the retry count,
_eventAggregator.GetEvent<WaitAndRetryEvent>().Publish(retryCount);
and the ViewModel will update the text as it is subscribed to this event.
void OnRetrying(int retryCount) { // if retrying for the second time, let the user know politely if (retryCount > 1) { LoadingText = "Still loading..."; } }
Doing so, we politely notify the user that their data is being loaded.


What’s Next
If you are interested in learning more about other Polly features such as,
- PolicyRegistry
- Combined policies
- Circuit Breaker
- Cache
- Fallback
visit Polly to learn more.
Let me know in the comments if this post was interesting to you. Enjoy 😊
Resources
Full sample source: https://github.com/hnabbasi/polly-mvvm
Cloud icon made by Freepik from www.flaticon.com
Really good stuff, i’m about to write something like this my self but now may trying polly.
Absolutely. Give Polly a try, it may save you a whole bunch of time.
Hey Hussain, great post! So I recently implemented this myself after reading this, but found out that many of the actual transient network errors are platform-specific (mostly Android/Mono I believe).
Things like Java.Net socket closed, operation cancelled, unknown host name, connection abort, etc.
Because our API service codes are typically inside a .NET standard project that has no dependency on the native Android or iOS project, we can’t quite categorize this type of errors cleanly via Polly’s Handle policy. A catch-all “Exception” is obviously problematic too as we don’t really want to retry non-retryable issues.
What I ended up doing is that in addition to the standard HttpClient exceptions, I added a check for exception messages containing some of those keywords I encountered. This is obviously kind of hacky. I wonder if you have any other ideas.