We have one app that downloads data as user moves through pages. There were few observations as I went through fixing issues there.
General gyan about adding support for network in the app can be found here; it is worth a skim; not all may apply for every app.
In our app, I need to handle mainly these things:
- download failure error handling – if the network connection is not available, or the http client async api calls throws or return any error.
- async download task cancellation – when the user moves between pages in the app, download async task for the previous page need to be canceled.
Network connection not available
The app want to display a clean error message to the user – “Internet connection is not available.” after determining conclusively that internet access is not there. The code snippet below does the job.
using Windows.Networking.Connectivity; private bool CheckInternetAccess() { ConnectionProfile profile = NetworkInformation.GetInternetConnectionProfile(); var level = profile != null ? profile.GetNetworkConnectivityLevel() : NetworkConnectivityLevel.None; if (level >= NetworkConnectivityLevel.InternetAccess) { // We have Internet, all is golden return true; } return false; } bool isMessageBoxActive; using Windows.UI.Popups; public async Task ShowMessage(string message) { if (this.isMessageBoxActive) return; this.isMessageBoxActive = true; MessageDialog dialog = new MessageDialog(message); await dialog.ShowAsync(); this.isMessageBoxActive = false; } bool internetOk = this.CheckInternetAccess(); if (!internetOk) this.showMessage("Internet connection is not available");
This works for displaying one time error message to the user. Since in my case, there was no need to keep a check if the connectivity goes down/up, I have not used NetworkStatusChanged event.
HttpClient.GetAsync api error handling
Next the api call to download the data GetAsync need to be cancelable and it’s exception & errors need to be handled. To cancel, api overload taking a cancellationToken can be used. CancellationTokenSource (cts) provides the cancellation token which can be passed to the api. To cancel the task, cts.Cancel() needs to be called. To understand – How to cancel async task – this msdn topic will help.
The code below handles exception and errors.
public async Task LoadData(CancellationToken cancellationToken) { if (this.data != null) return; bool internetOk = this.CheckInternetAccess(); if (!internetOk) { var message = Singletons.ResourceLoader.GetString("InternetNotAvailable"); throw new DataLoadException(message); } HttpClient client = new HttpClient(); using (client) { HttpResponseMessage response = null; try { response = await client.GetAsync(this.dataUrl, cancellationToken); } catch (TaskCanceledException) { throw; } catch (Exception e) { var m = Singletons.ResourceLoader.GetString("DataDownloadException"); var fm = string.Format(m, this.Title, e.Message); throw new DataLoadException(fm); } if (!response.IsSuccessStatusCode) { var message = Singletons.ResourceLoader.GetString("DataDownloadFailed"); var formattedMessage = string.Format(message, this.Title, (int)response.StatusCode); throw new DataLoadException(formattedMessage); } try { var content = await response.Content.ReadAsStringAsync(); this.data = DataParser.Parse(content); } catch (Exception) { var m = Singletons.ResourceLoader.GetString("DataParsingFailed"); var fm = string.Format(m, this.Title); throw new DataLoadException(fm); } } }
The calling code provides the cancellation token and calls cancel during code paths where the async task needs to be canceled.
private CancellationTokenSource dataLoadOperationCts; private void Unload_handler() { if (this.dataLoadOperationCts != null) { this.dataLoadOperationCts.Cancel(); this.dataLoadOperationCts = null; } } private async void initialize() { this.dataLoadOperationCts = new CancellationTokenSource(); // need to trigger download of data. ... Exception error = null; try { await item.LoadData(this.dataLoadOperationCts.Token); } catch (DataLoadException err) { error = err; } catch (TaskCanceledException) { // do nothing } if (error != null) await this.ShowMessage(error.Message); }
The LoadData helper method raises DataLoadException with app friendly error message which are then caught and handled by the UI code. DataLoadException and Singletons class are not included in the code listing but they are kind of obvious.
Few more things:
- Unit testing was important here. There are code paths that can not be reached easily. I have to exercise the code path by throwing exception by hand in code or using slow network where download took time & canceling midway when the GetAsync call did not yet complete or disconnecting the network. For slow network, I have used the slow 2g mobile internet by sharing it using “internet sharing” feature on mobile.
- Using Xxxasync methods on http client is key. Don’t try to read the stream returned using GetStreamAsync and then use sync methods like TextReader.ReadToEnd. That will result in sync read and responsiveness issues in app.
- Need to handle all exception thrown by async api or unhandled exception will cause app to crash.
- I did a brief search for System.Net.WebClient vs System.Net.Http.HttpClient after finishing the work. It looked like HttpClient is newer api entry.
Every app has different set of network access needs. HTH. Share your learnings through comments.