Sitemap

Mastering Error Handling in Flutter with Dio

6 min readOct 13, 2023

--

Best way to handle error with explain exception

Introduction:
In mobile app development, handling errors gracefully is crucial to provide a smooth and user-friendly experience. When working with Flutter, Dio, a powerful HTTP client library, offers robust mechanisms for handling errors during network requests. In this article, we will explore how to effectively handle errors in Flutter using Dio, ensuring that your app remains reliable and user-friendly.

1. Introduction to Dio:
Dio is a versatile package that simplifies making HTTP requests in Flutter applications. It provides features like cancellation, interceptors, request/response transformation, and error handling. Error handling is a critical aspect of network communication, as it allows your app to handle scenarios like no internet connection, server errors, and more.

2. Dio Configuration and Instance Creation:
Before we dive into error handling, let’s set up Dio by creating an instance with appropriate configurations. We’ll define timeouts, base URLs, and other settings to tailor Dio to our app’s needs.3. Global Error Handling with Interceptors:

const String applicationJson = "application/json";
const String contentType = "content-type";
const String accept = "accept";
const int apiTimeOut = 60000;

class DioFactory {
Future<Dio> getDio() async {
Dio dio = Dio();

Map<String, String> headers = {
contentType: applicationJson,
accept: applicationJson,
};

dio.options = BaseOptions(
baseUrl: Constants.baseUrl,
headers: headers,
receiveTimeout: const Duration(milliseconds: apiTimeOut),
sendTimeout: const Duration(milliseconds: apiTimeOut),
connectTimeout: const Duration(milliseconds: apiTimeOut),
);

if (!kReleaseMode) {
dio.interceptors.add(PrettyDioLogger(
requestHeader: true,
requestBody: true,
responseHeader: true,
));
}

return dio;
}
}

3. DataSource enum:
This is an enumeration that defines various data sources, each associated with a specific type of failure. It to be used for mapping error types to failure responses.

enum DataSource {
success,
noContent,
badRequest,
forbidden,
unauthorised,
notFound,
internetServerError,
connectTimeout,
connectionError,
cancel,
receiveTimeout,
sendTimeout,
cacheError,
noInternetConnection,
defaultError
}

4. DataSourceExtension:
This extension adds a method called getFailure to the DataSource enum. This method returns a Failure object based on the value of the enum.


extension DataSourceExtension on DataSource {
Failure getFailure() {
final context = navigatorKey!.currentState!.context;
switch (this) {
case DataSource.success:
return Failure(ResponseCode.success, ResponseMessage.success.tr(context));
case DataSource.noContent:
return Failure(ResponseCode.noContent, ResponseMessage.noContent.tr(context));
case DataSource.badRequest:
return Failure(ResponseCode.badRequest, ResponseMessage.badRequest.tr(context));
case DataSource.forbidden:
return Failure(ResponseCode.forbidden, ResponseMessage.forbidden.tr(context));
case DataSource.unauthorised:
return Failure(ResponseCode.unauthorised, ResponseMessage.unauthorised.tr(context));
case DataSource.notFound:
return Failure(ResponseCode.notFound, ResponseMessage.notFound.tr(context));
case DataSource.internetServerError:
return Failure(ResponseCode.internalServerError, ResponseMessage.internalServerError.tr(context));
case DataSource.connectTimeout:
return Failure(ResponseCode.connectTimeout, ResponseMessage.connectTimeout.tr(context));
case DataSource.connectionError:
return Failure(ResponseCode.connectionError, ResponseMessage.connectionError.tr(context));
case DataSource.cancel:
return Failure(ResponseCode.cancel, ResponseMessage.cancel.tr(context));
case DataSource.receiveTimeout:
return Failure(ResponseCode.receiveTimeout, ResponseMessage.receiveTimeout.tr(context));
case DataSource.sendTimeout:
return Failure(ResponseCode.sendTimeout, ResponseMessage.sendTimeout.tr(context));
case DataSource.cacheError:
return Failure(ResponseCode.cacheError, ResponseMessage.cacheError.tr(context));
case DataSource.noInternetConnection:
return Failure(ResponseCode.noInternetConnection, ResponseMessage.noInternetConnection.tr(context));
case DataSource.defaultError:
return Failure(ResponseCode.defaultError, ResponseMessage.defaultError.tr(context));
}
}
}

5. ResponseCode class:
This class defines static integer constants representing various HTTP status codes, both standard HTTP status codes and custom ones for local status codes.

class ResponseCode {
static const int success = 200; // success with data
static const int noContent = 201; // success with no data (no content)
static const int badRequest = 400; // failure, API rejected request
static const int unauthorised = 401; // failure, user is not authorised
static const int forbidden = 403; // failure, API rejected request
static const int internalServerError = 500; // failure, crash in server side
static const int notFound = 404; // failure, not found
static const int invalidData = 422; // failure, not found

// local status code
static const int connectTimeout = -1;
static const int cancel = -2;
static const int receiveTimeout = -3;
static const int sendTimeout = -4;
static const int cacheError = -5;
static const int noInternetConnection = -6;
static const int locationDenied = -7;
static const int defaultError = -8;
static const int connectionError = -9;
}

6. ResponseMessage class:
This class defines static string constants representing response messages for different HTTP status codes. These messages to be internationalized (using localization).

class ResponseMessage {
static const String success = AppStrings.strSuccess; // success with data
static const String noContent = AppStrings.strNoContent; // success with no data (no content)
static const String badRequest = AppStrings.strBadRequestError; // failure, API rejected request
static const String unauthorised = AppStrings.strUnauthorizedError; // failure, user is not authorised
static const String forbidden = AppStrings.strForbiddenError; // failure, API rejected request
static const String internalServerError = AppStrings.strInternalServerError; // failure, crash in server side
static const String notFound = AppStrings.strNotFoundError; // failure, crash in server side

// local status code
static const String connectTimeout = AppStrings.strTimeoutError;
static const String cancel = AppStrings.strDefaultError;
static const String receiveTimeout = AppStrings.strTimeoutError;
static const String sendTimeout = AppStrings.strTimeoutError;
static const String cacheError = AppStrings.strCacheError;
static const String noInternetConnection = AppStrings.strNoInternetError;
static const String defaultError = AppStrings.strDefaultError;
static const String connectionError = AppStrings.strDefaultError;
}

7. HandleError function:
This private function takes a DioException as a parameter and returns a Failure object.It switches on the type of the DioException and maps different types of DioException to corresponding Failure values based on a set of enumerated values defined in the DataSource enum.

  Failure _handleError(DioException error) {
switch (error.type) {
case DioExceptionType.connectionTimeout:
return DataSource.connectTimeout.getFailure();
case DioExceptionType.sendTimeout:
return DataSource.sendTimeout.getFailure();
case DioExceptionType.receiveTimeout:
return DataSource.receiveTimeout.getFailure();
case DioExceptionType.badResponse:
return _handleBadResponse(error);
case DioExceptionType.cancel:
return DataSource.cancel.getFailure();
case DioExceptionType.connectionError:
return DataSource.connectionError.getFailure();
default:
return _handleDefaultError(error);
}
}

_handleBadResponse(DioException error) {
try {
final code = error.response?.statusCode ?? ResponseCode.defaultError;
String message = '';
switch (code) {
case ResponseCode.unauthorised:
return DataSource.unauthorised.getFailure();
case ResponseCode.forbidden:
return DataSource.forbidden.getFailure();
case ResponseCode.notFound:
return DataSource.notFound.getFailure();
default:
message = _extractErrorMessage(error.response?.data);
return Failure(code, message);
}
} catch (e) {
return DataSource.defaultError.getFailure();
}
}

_handleDefaultError(DioException error) {
if (error.response?.statusCode == ResponseCode.noInternetConnection) {
return DataSource.noInternetConnection.getFailure();
} else {
return DataSource.defaultError.getFailure();
}
}

String _extractErrorMessage(dynamic data) {
if (data is String) return data;
String message = '';
if (data is Map) {
data.forEach((key, value) {
if (value is List) {
message += value.join('\n');
} else if (value is String) {
message += value;
} else {
message += value.toString();
}
});
}
return message;
}

7. ErrorHandler class:
This class implements the Exception interface, indicating that it’s intended for handling exceptions.
It has a late field named failure of type Failure, which is not initialized immediately.
The ErrorHandler class has a constructor named handle, which takes a dynamic error parameter. It handles different types of exceptions by calling the _handleError function based on the type of the error.
If the error is of type DioException, it calls the _handleError function to determine the failure.
If the error is not a DioException, it sets the failure to a default value obtained from a data source called DataSource.

class ErrorHandler implements Exception {
late Failure failure;

ErrorHandler.handle(dynamic error) {
if (error is DioException) {
failure = _handleError(error);
} else {
failure = DataSource.defaultError.getFailure();
}
}
}

8. Handling Errors in Specific Requests:
While global error handling is essential, you can also handle errors on a per-request basis. Use try-catch blocks around Dio requests to capture errors and respond accordingly.

  Future<Either<Failure, List<ArticleResponseDto>>> getArticles() async {
try {
....
...
..
.
return Right(response);
} catch (error) {
return Left(ErrorHandler.handle(error).failure);
}
}

9. Displaying User-Friendly Error Messages:
To ensure a positive user experience, convert technical error messages into user-friendly messages. You can use a helper function to map error codes to human-readable messages that guide users on how to proceed.

///English Message
"success": "success",
"bad_request_error": "bad request. try again later",
"no_content": "success with not content",
"forbidden_error": "forbidden request. try again later",
"unauthorized_error": "user unauthorized, try again later",
"not_found_error": "url not found, try again later",
"conflict_error": "conflict found, try again later",
"internal_server_error": "some thing went wrong, try again later",
"unknown_error": "some thing went wrong, try again later",
"timeout_error": "time out, try again late",
"default_error": "some thing went wrong, try again later",
"cache_error": "cache error, try again later",
"no_internet_error": "Please check your internet connection"

//Arabic Message
"success": "تم بنجاح",
"bad_request_error": "طلب غير صالح. حاول مرة أخرى لاحقًا",
"no_content": "success with not content",
"forbidden_error": "طلب محظور. حاول مرة أخرى لاحقًا",
"unauthorized_error": "user unauthorized, try again later",
"not_found_error": "url غير موجود , حاول مرة أخرى لاحقًا",
"conflict_error": "تم العثور على تعارض , حاول مرة أخرى لاحقًا",
"internal_server_error": "حدث خطأ ما , حاول مرة أخرى لاحقًا",
"unknown_error": "حدث خطأ ما , حاول مرة أخرى لاحقًا",
"timeout_error": "انتهت المهلة , حاول مرة أخرى متأخرًا",
"default_error": "حدث خطأ ما , حاول مرة أخرى لاحقًا",
"cache_error": "خطأ في ذاكرة التخزين المؤقت , حاول مرة أخرى لاحقًا",
"no_internet_error": "يُرجى التحقق من اتصالك بالإنترنت"

10. Conclusion:
Effective error handling is essential for delivering a robust and reliable Flutter app. Dio’s comprehensive error handling mechanisms, combined with user-friendly error messages, ensure that users remain informed even in challenging network scenarios. By implementing these strategies, you can elevate your app’s reliability and enhance the overall user experience.

# Github example

--

--

MohAmmad Joumani
MohAmmad Joumani

Written by MohAmmad Joumani

Mobile Application Developer (Flutter & Native)

Responses (7)