Appearance
Handling Timeout and Connectivity State Changes in Flutter with Dio
One of the most popular Flutter packages for dealing with http data is dio. In real world usage we have to deal with less than perfect internet connections. Fortunately dio supports (Interceptors) which allows us (amount other things) to retry failed requests.
One package I came across recently for retrying requests is dio_retry. This package is pretty simple--just add it in to the list of dio interceptors, set the maximum number of retry requests and let it go.
Matt Rešetár wrote a great article on handling connectivity state changes with yet another interceptor: Flutter Tutorial - Dio Connectivity Retry Interceptor
One thing not discussed was how to combine network timeout issues with connectivity state changes. For example, if we don't have an internet connection it doesn't make sense to retry the dio request. Likewise, if we have a poor internet connection and are experiencing timeouts on our requests it may make sense to retry the request (up to a specified limit). The code below shows how to handle both pretty simply.
Our shouldRetry function is below which checks to see if the error type is valid as well as to display a message to the user.
dart
static FutureOr<bool> shouldRetry(DioError error) async {
// Don't retry if cancelled or if response error (e.g. 404, etc.)
var hasRetry = error.type != DioErrorType.CANCEL &&
error.type != DioErrorType.RESPONSE;
// show a message to the user via flushbar
if (hasRetry) {
// clientConnectionType is set by a listener on the connectivity object
var msg = clientConnectiontype == 'None' ? 'Please check your internet connection' :
error.type == DioErrorType.DEFAULT ? 'An error has occurred. Retrying...' :
'A network timeout has occurred. Retrying...';
// display toast to the user via flushbar https://pub.dev/packages/flushbar
unawaited(Flushbar(
message: msg,
duration: Duration(seconds: 1),
title: 'Connection Error')
.show(context));
}
return hasRetry;
}
Our onError handler checks the connection type first. If there is no internet connection we schedule a retry request which will run once the connection state changes. Otherwise we will retry the previous request just so long as we haven't exceeded a maximum number of requests.
The relevant code for our onError handler is below:
dart
try {
if (clientconnectiontype == 'None') {
// for an example implementation of scheduleRequestRetry see https://resocoder.com/2020/03/23/dio-connectivity-retry-interceptor-flutter-tutorial/
return scheduleRequestRetry(err.request);
}
else {
// retry with the updated options
return await dio.request(
err.request.path,
cancelToken: err.request.cancelToken,
data: err.request.data,
onReceiveProgress: err.request.onReceiveProgress,
onSendProgress: err.request.onSendProgress,
queryParameters: err.request.queryParameters,
options: err.request,
);
}
} catch (e) {
return e;
}
With these simple changes you can handle both retry logic and connectivity state changes. For testing purposes you can set dio timeout values to artificially small values and also use a fictitious endpoint on your requests that is guaranteed to timeout.
dart
dio.options.connectTimeout = 5000; // 5 seconds
dio.options.receiveTimeout = 5000; // 5 seconds
For testing connectivity state changes I suggest using the Android emulator since you can easily toggle airplane mode on and off. Lastly, to simulate poor networks use the Network Link Conditioner.