使用 Dio 处理 Flutter 中的请求错误

3,591 阅读5分钟

简介:
在移动应用程序开发中,优雅地处理错误对于提供流畅且用户友好的体验至关重要。使用 Flutter 时,Dio 这个 HTTP 请求库提供了有效的机制来处理网络请求期间的错误。在本文中,我们将探讨如何使用 Dio 有效处理 Flutter 中的错误,确保您的应用程序保持可靠且用户友好。

  1. Dio 简介:
    Dio 是一个可以简化 Flutter 应用程序中的 HTTP 请求。它提供取消、拦截器、请求/响应转换和错误处理等功能,类似前端开发中的Axios请求库。错误处理是网络通信的一个重要方面,因为它允许应用程序处理无互联网连接、服务器错误等情况。
  2. Dio 配置和实例创建:
    在我们深入讨论错误处理之前,让我们通过创建具有适当配置的实例来设置 Dio。我们将根据需求定义超时、基本 URL 和其他设置。使用拦截器进行全局错误处理:
const String APPLICATION_JSON = "application/json";  
const String CONTENT_TYPE = "content-type";  
const String ACCEPT = "accept";  
const String AUTHORIZATION = "authorization";  
const String DEFAULT_LANGUAGE = "en";  
const String TOKEN = "token";  
const String BASE_URL = "https://api.example.com";  
  
class DioFactory {  
  
    Future<Dio> getDio() async {  
    Dio dio = Dio();  

        Map<String, String> headers = {  
            CONTENT_TYPE: APPLICATION_JSON,  
            ACCEPT: APPLICATION_JSON,  
            AUTHORIZATION: TOKEN,  
            DEFAULT_LANGUAGE: DEFAULT_LANGUAGE  
        };  

        dio.options = BaseOptions(  
            baseUrl: BASE_URL,  
            headers: headers,  
            receiveTimeout: Constants.apiTimeOut,  
            sendTimeout: Constants.apiTimeOut,  
        );  

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

        return dio;  
    }  
}
  1. DataSource 枚举:
    这是一个定义各种数据源的枚举,每个数据源都与特定类型的故障相关。它用于将错误类型映射到失败响应。
enum DataSource {  
    SUCCESS,  
    NO_CONTENT,  
    BAD_REQUEST,  
    FORBIDDEN,  
    UNAUTORISED,  
    NOT_FOUND,  
    INTERNAL_SERVER_ERROR,  
    CONNECT_TIMEOUT,  
    CANCEL,  
    RECIEVE_TIMEOUT,  
    SEND_TIMEOUT,  
    CACHE_ERROR,  
    NO_INTERNET_CONNECTION,  
    DEFAULT  
}
  1. DataSourceExtension:
    此扩展向 DataSource 枚举添加了一个名为 getFailure 的方法。此方法根据枚举值返回一个 Failure 对象。
extension DataSourceExtension on DataSource {  
    Failure getFailure() {  
        var mContext = navigatorKey!.currentState!.context;  
        switch (this) {  
            case DataSource.SUCCESS:  
            return Failure(ResponseCode.SUCCESS, ResponseMessage.SUCCESS.tr(mContext));  
            case DataSource.NO_CONTENT:  
                return Failure(ResponseCode.NO_CONTENT, ResponseMessage.NO_CONTENT.tr(mContext));  
            case DataSource.BAD_REQUEST:  
                return Failure(ResponseCode.BAD_REQUEST, ResponseMessage.BAD_REQUEST.tr(mContext));  
            case DataSource.FORBIDDEN:  
                return Failure(ResponseCode.FORBIDDEN, ResponseMessage.FORBIDDEN.tr(mContext));  
            case DataSource.UNAUTORISED:  
                return Failure(ResponseCode.UNAUTORISED, ResponseMessage.UNAUTORISED.tr(mContext));  
            case DataSource.NOT_FOUND:  
                return Failure(ResponseCode.NOT_FOUND, ResponseMessage.NOT_FOUND.tr(mContext));  
            case DataSource.INTERNAL_SERVER_ERROR:  
                return Failure(ResponseCode.INTERNAL_SERVER_ERROR,  
            ResponseMessage.INTERNAL_SERVER_ERROR.tr(mContext));  
            case DataSource.CONNECT_TIMEOUT:  
                return Failure(  
            ResponseCode.CONNECT_TIMEOUT, ResponseMessage.CONNECT_TIMEOUT.tr(mContext));  
            case DataSource.CANCEL:  
                return Failure(ResponseCode.CANCEL, ResponseMessage.CANCEL.tr(mContext));  
            case DataSource.RECIEVE_TIMEOUT:  
                return Failure(  
            ResponseCode.RECIEVE_TIMEOUT, ResponseMessage.RECIEVE_TIMEOUT.tr(mContext));  
            case DataSource.SEND_TIMEOUT:  
                return Failure(ResponseCode.SEND_TIMEOUT, ResponseMessage.SEND_TIMEOUT.tr(mContext));  
            case DataSource.CACHE_ERROR:  
                return Failure(ResponseCode.CACHE_ERROR, ResponseMessage.CACHE_ERROR.tr(mContext));  
            case DataSource.NO_INTERNET_CONNECTION:  
                return Failure(ResponseCode.NO_INTERNET_CONNECTION,  
            ResponseMessage.NO_INTERNET_CONNECTION.tr(mContext));  
            case DataSource.DEFAULT:  
                return Failure(ResponseCode.DEFAULT, ResponseMessage.DEFAULT.tr(mContext));  
        }  
    }  
}
  1. ResponseCode 类:
    该类定义了表示各种 HTTP 状态码的静态整型常量,包括标准 HTTP 状态码和本地状态码的自定义状态码。
class ResponseCode {  
    static const int SUCCESS = 200; // success with data  
    static const int NO_CONTENT = 201; // success with no data (no content)  
    static const int BAD_REQUEST = 400; // failure, API rejected request  
    static const int UNAUTORISED = 401; // failure, user is not authorised  
    static const int FORBIDDEN = 403; // failure, API rejected request  
    static const int INTERNAL_SERVER_ERROR = 500; // failure, crash in server side  
    static const int NOT_FOUND = 404; // failure, not found  

    // local status code  
    static const int CONNECT_TIMEOUT = -1;  
    static const int CANCEL = -2;  
    static const int RECIEVE_TIMEOUT = -3;  
    static const int SEND_TIMEOUT = -4;  
    static const int CACHE_ERROR = -5;  
    static const int NO_INTERNET_CONNECTION = -6;  
    static const int DEFAULT = -7;  
}
  1. ResponseMessage类:
    该类定义了表示不同HTTP状态码的响应消息的静态字符串常量。这些消息将被国际化(使用本地化)。
class ResponseMessage {  
    static const String SUCCESS = AppStrings.success; // success with data  
    static const String NO_CONTENT = AppStrings.success; // success with no data (no content)  
    static const String BAD_REQUEST = AppStrings.strBadRequestError; // failure, API rejected request  
    static const String UNAUTORISED = AppStrings.strUnauthorizedError; // failure, user is not authorised  
    static const String FORBIDDEN = AppStrings.strForbiddenError; // failure, API rejected request  
    static const String INTERNAL_SERVER_ERROR = AppStrings.strInternalServerError; // failure, crash in server side  
    static const String NOT_FOUND = AppStrings.strNotFoundError; // failure, crash in server side  

    // local status code  
    static const String CONNECT_TIMEOUT = AppStrings.strTimeoutError;  
    static const String CANCEL = AppStrings.strDefaultError;  
    static const String RECIEVE_TIMEOUT = AppStrings.strTimeoutError;  
    static const String SEND_TIMEOUT = AppStrings.strTimeoutError;  
    static const String CACHE_ERROR = AppStrings.strCacheError;  
    static const String NO_INTERNET_CONNECTION = AppStrings.strNoInternetError;  
    static const String DEFAULT = AppStrings.strDefaultError;  
}
  1. HandleError函数:
    该私有函数以DioException为参数,返回一个Failure对象。它切换DioException的类型,并根据DataSource枚举中定义的一组枚举值将不同类型的DioException映射到相应的Failure值。
Failure _handleError(DioException error) {  
    switch (error.type) {  
        case DioExceptionType.connectionTimeout:  
            return DataSource.CONNECT_TIMEOUT.getFailure();  
        case DioExceptionType.sendTimeout:  
            return DataSource.SEND_TIMEOUT.getFailure();  
        case DioExceptionType.receiveTimeout:  
            return DataSource.RECIEVE_TIMEOUT.getFailure();  
        case DioExceptionType.badResponse:  
            if (error.response != null &&  
                error.response?.statusCode != null &&  
                error.response?.statusMessage != null) {  
                    return Failure(error.response?.statusCode ?? 0,  
                error.response?.statusMessage ?? "");  
            } else {  
                return DataSource.DEFAULT.getFailure();  
            }  
        case DioExceptionType.cancel:  
            return DataSource.CANCEL.getFailure();  
        default:  
            return DataSource.DEFAULT.getFailure();  
    }  
}

8.ErrorHandler类:
该类实现了Exception接口,表明它用于处理异常。
它有一个名为 failure、类型为 Failure 的 Late 字段,该字段不会立即初始化。
ErrorHandler 类有一个名为handle 的构造函数,它采用动态错误参数。它根据错误的类型调用_handleError函数来处理不同类型的异常。
如果错误类型为 DioException,则调用 _handleError 函数来确定失败。
如果错误不是 DioException,则会将失败设置为从称为 DataSource 的数据源获取的默认值。

class ErrorHandler implements Exception {  
    late Failure failure;  

    ErrorHandler.handle(dynamic error) {  
        if (error is DioException) {  
            // dio error so its an error from response of the API or from dio itself  
            failure = _handleError(error);  
        } else {  
            // default error  
            failure = DataSource.DEFAULT.getFailure();  
        }  
    }  
}
  1. 处理特定请求中的错误:
    虽然全局错误处理很重要,但您也可以基于每个请求处理错误。使用try-catch来捕获错误并做出相应的响应。
Future<Either<Failure, ResponseDto>> getResponse(RequestDto requestDto) async {  
    if (await _networkInfo.isConnected) {  
    try {  
        ...  
        .  
        .  
        return Right(response);  
    } catch (error) {  
        return Left(ErrorHandler.handle(error).failure);  
    }  
    } else {  
        return Left(DataSource.NO_INTERNET_CONNECTION.getFailure());  
    }  
}
  1. 显示用户友好的错误消息:
    为了确保良好的用户体验,请将技术错误消息转换为用户友好的消息。您可以使用辅助函数将错误代码映射到人类可读的消息,以指导用户如何继续操作。
"success": "成功",  
"bad_request_error": "请求错误,请重试",  
"no_content": "请求成功并且服务器创建了新的资源",  
"forbidden_error": "请求已拒绝,请重试",  
"unauthorized_error": "暂无权限,请重试",  
"not_found_error": "请求地址不正确,请重试",  
"conflict_error": "请求冲突,请重试",  
"internal_server_error": "服务器端崩溃,请重试",  
"unknown_error": "未知错误,请重试",  
"timeout_error": "请求超时,请重试",  
"default_error": "出了点问题,请稍后再试,请重试",  
"cache_error": "缓存错误,请重试",  
"no_internet_error": "请检查您的网络链接"
  1. 结论:
    有效的错误处理对于提供健壮可靠的 Flutter 应用程序至关重要。Dio 全面的错误处理机制与用户友好的错误消息相结合,确保用户即使在较差的网络中也能随时了解情况。通过实施这些策略,可以提高应用程序的可靠性并增强整体用户体验。