flutter开发实战-dio文件下载实现

2024-01-26 01:05:49 作者:欧亿体育

flutter开发实战-dio文件下载实现

在开发中,需要下载文件,这里使用的是dio
dio 是一个强大的 Dart HTTP 请求库,支持全局配置、Restful API、FormData、拦截器、 请求取消、Cookie 管理、文件上传/下载、超时以及自定义适配器等。

一、引入dio

在工程中pubspec.yaml引入dio

dio: ^5.1.1
  dio_cookie_manager: ^3.0.0

二、代码实现

我们对dio进行封装

// 定义枚举,请求方法枚举
enum HttpApiMethod {
  GET,
  POST,
  DELETE,
  PUT,
}

// 网络请求的成功与失败
// 上传
typedef OnUploaded = void Function(Map<String, dynamic> result);
// 下载进度
typedef OnDownloadProgress = void Function(int count, int total);
// 下载成功
typedef OnDownloaded = void Function();
// 请求成功
typedef OnSuccess = void Function(ResponseData responseData);
// 请求失败
typedef OnFailure = void Function(ApiHttpError error);

// 请求Api
class HttpApi {
  // 网络请求库dio
  Dio dio = Dio(BaseOptions(
    // connectTimeout: 60000, // 连接服务器超时时间,单位是毫秒.
    // receiveTimeout: 10000, // 响应流上前后两次接受到数据的间隔,单位为毫秒, 这并不是接收数据的总时限
    headers: {
      HttpHeaders.acceptHeader: "text/plain,"
          "text/plain,"
          "multipart/form-data,"
          "application/json,"
          "text/html,"
          "image/jpeg,"
          "image/png,"
          "application/octet-stream,"
          "text/json,"
          "text/javascript,"
          "text/html",
    },
  ));

  // 私有构造函数
  HttpApi._internal();

  //保存单例
  static HttpApi _singleton = HttpApi._internal();

  //工厂构造函数
  factory HttpApi() => _singleton;

  /// 配置请求头header
  ///   /// The request Content-Type. The default value is 'application/json; charset=utf-8'.
  //   /// If you want to encode request body with 'application/x-www-form-urlencoded',
  //   /// you can set [Headers.formUrlEncodedContentType], and [Dio]
  //   /// will automatically encode the request body.
  Future<void> configHeaders(
      String requestUrl, Map<String, dynamic>? params) async {

    dio.options.headers['Content-Type'] = Headers.jsonContentType;

    LoggerManager().info(
        "requestUrl:${requestUrl} dio.options.headers:${dio.options.headers}");
  }

  get(String url, ApiServiceDomain serviceDomain,
      {Map<String, dynamic>? params, OnSuccess? success, OnFailure? failure}) {
    doRequest(url, serviceDomain, HttpApiMethod.GET,
        params: params, success: success, failure: failure);
  }

  post(String url, ApiServiceDomain serviceDomain,
      {Map<String, dynamic>? params, OnSuccess? success, OnFailure? failure}) {
    doRequest(url, serviceDomain, HttpApiMethod.POST,
        params: params, success: success, failure: failure);
  }

  // 请求服务器
  // params,参数
  // 请求成功
  // 请求失败
  Future<void> doRequest(
      String url, ApiServiceDomain serviceDomain, HttpApiMethod method,
      {Map<String, dynamic>? params,
      OnSuccess? success,
      OnFailure? failure}) async {
    String requestUrl = getRequestUrl(url, serviceDomain);

    try {
      /// 可以添加header
      await configHeaders(requestUrl, params);
      Response? response;
      switch (method) {
        case HttpApiMethod.GET:
          {
            // get请求
            if (params != null && params.isNotEmpty) {
              response = await dio.get(requestUrl,
                  queryParameters: params,
                  options: Options(contentType: Headers.jsonContentType));
              LoggerManager()
                  .debug("await dio.get response:$response,params:$params");
            } else {
              response = await dio.get(requestUrl,
                  options: Options(contentType: Headers.jsonContentType));
            }
            break;
          }
        case HttpApiMethod.POST:
          {
            // post请求
            String? contentType = Headers.formUrlEncodedContentType;
            if (params != null && params.isNotEmpty) {
              response = await dio.post(requestUrl,
                  data: params, options: Options(contentType: contentType));
              LoggerManager()
                  .debug("await dio.post response:$response,params:$params");
            } else {
              response = await dio.post(requestUrl,
                  options: Options(contentType: contentType));
            }
            break;
          }
        // case HttpApiMethod.PUT: {
        //   break;
        // }
        // case HttpApiMethod.DELETE: {
        //   break;
        // }
        default:
      }
      LoggerManager().debug('doRequest: $response, params:$params');

      if (response != null) {
        Map<String, dynamic> result = json.decode(response.toString());
        assert(() {
          // assert只会在debug模式下执行,release模式下不会执行
          // 打印信息
          LoggerManager().debug('''api: $requestUrl\nresult: $result''');
          return true;
        }());

        ResponseData responseData = ResponseData.fromJson(result);
        if (responseData.status == 0) {
          if (success != null) {
            //返回请求数据
            success(responseData);
          }
        } else {
          //返回失败信息
          ApiHttpError apiHttpError = getErrorRequestResponseData(responseData);

          LoggerManager().debug("apiHttpError:${apiHttpError.toString()}");

          LoggerManager().error('''api: $requestUrl\nresult: $result''');

          if (failure != null) {
            failure(apiHttpError);
          }
        }
      } else {
        // 没有获得response,failure
        ApiHttpError apiHttpError =
            ApiHttpError(ApiHttpErrorType.Default, "请求失败!");

        LoggerManager().debug("apiHttpError:${apiHttpError.toString()}");

        if (failure != null) {
          failure(apiHttpError);
        }
      }
    } on DioError catch (e, s) {
      // catch到异常,failure
      // The request was made and the server responded with a status code
      // that falls out of the range of 2xx and is also not 304.
      LoggerManager()
          .error("doRequest api: $requestUrl, dioError:${e.message}, s:$s");
      ApiHttpError apiHttpError = getRequestFailure(e.response, e.type);

      LoggerManager().debug("apiHttpError:${apiHttpError.toString()}");

      if (failure != null) {
        failure(apiHttpError);
      }
    } catch (e) {
      // 可以捕获任意异常
      ApiHttpError apiHttpError =
          ApiHttpError(ApiHttpErrorType.Default, "${e.toString()}");

      if (failure != null) {
        failure(apiHttpError);
      }
    }
  }

  // 上传文件(图片)
  doUploadFile(String url, UploadFileInfo fileInfo,
      {Map<String, dynamic>? params,
      OnUploaded? uploaded,
      OnFailure? failure}) async {
    try {
      String timeStamp = DateTime.now().millisecondsSinceEpoch.toString();
      Map<String, dynamic> fromParams = Map();
      if (params != null && params.isNotEmpty) {
        fromParams.addAll(params);
      }

      fromParams["file"] = await MultipartFile.fromFile(fileInfo.file.path,
          filename: '${fileInfo.key}-${timeStamp}.jpg');

      FormData formData = FormData.fromMap(fromParams);
      Response? response = await dio.post(url, data: formData);
      assert(() {
        // assert只会在debug模式下执行,release模式下不会执行
        // 打印信息
        LoggerManager().error('''api: $url\nresult: $response''');
        return true;
      }());

      if (response != null) {
        Map<String, dynamic> result = json.decode(response.toString());
        assert(() {
          // assert只会在debug模式下执行,release模式下不会执行
          // 打印信息
          LoggerManager().debug('''api: $url\nresult: $result''');
          return true;
        }());

        if (response.statusCode == 200) {
          if (uploaded != null) {
            uploaded(result);
          }
        } else {
          //返回失败信息
          LoggerManager().error('''api: $url\nresult: $result''');

          ApiHttpError apiHttpError =
              ApiHttpError(ApiHttpErrorType.Default, "请求失败!");

          if (failure != null) {
            failure(apiHttpError);
          }
        }
      } else {
        //返回失败信息
        // 没有获得response,failure
        ApiHttpError apiHttpError =
            ApiHttpError(ApiHttpErrorType.Default, "请求失败!");

        if (failure != null) {
          failure(apiHttpError);
        }
      }
    } on DioError catch (e, s) {
      // catch到异常,failure
      LoggerManager().error("doUploadFile api: $url, dioError:$e, s:$s");
      ApiHttpError apiHttpError = getRequestFailure(e.response, e.type);

      if (failure != null) {
        failure(apiHttpError);
      }
    } catch (e) {
      // 可以捕获任意异常
      ApiHttpError apiHttpError =
          ApiHttpError(ApiHttpErrorType.Default, "${e.toString()}");

      if (failure != null) {
        failure(apiHttpError);
      }
    }
  }

  // 下载文件
  void doDownload(String url, String savePath,
      {required CancelToken cancelToken,
      Map<String, dynamic>? params,
      dynamic? data,
      Options? options,
      OnDownloadProgress? progress,
      OnDownloaded? completion,
      OnFailure? failure}) async {
    try {
      dio.download(
        url,
        savePath,
        queryParameters: params,
        cancelToken: cancelToken,
        onReceiveProgress: (int count, int total) {
          if (total != -1) {
            if (!cancelToken.isCancelled) {
              double downloadRatio = (count / total);
              if (downloadRatio == 1) {
                if (completion != null) {
                  completion();
                }
              } else {
                if (progress != null) {
                  progress(count, total);
                }
              }
            }
          } else {
            ApiHttpError apiHttpError =
                ApiHttpError(ApiHttpErrorType.Default, "无法获取文件大小,下载失败!");

            if (failure != null) {
              failure(apiHttpError);
            }
          }
        },
      );
    } on DioError catch (e) {
      ApiHttpError apiHttpError =
          ApiHttpError(ApiHttpErrorType.Default, e.toString());
      if (CancelToken.isCancel(e)) {
        apiHttpError = ApiHttpError(ApiHttpErrorType.Cancel, "下载已取消!");
      } else {
        if (e.response != null) {
          apiHttpError = getRequestFailure(e.response, e.type);
        } else {
          apiHttpError = ApiHttpError(ApiHttpErrorType.Default, e.message??"");
        }
      }

      if (failure != null) {
        failure(apiHttpError);
      }
    } on Exception catch (e) {
      // EasyLoading.showError(e.toString());
      ApiHttpError apiHttpError =
          ApiHttpError(ApiHttpErrorType.Default, e.toString());

      if (failure != null) {
        failure(apiHttpError);
      }
    } catch (e) {
      // 可以捕获任意异常
      ApiHttpError apiHttpError =
          ApiHttpError(ApiHttpErrorType.Default, "${e.toString()}");

      if (failure != null) {
        failure(apiHttpError);
      }
    }
  }

  // 根据服务器来拼接服务器具体地址
  String getRequestUrl(String url, ApiServiceDomain serviceDomain) {
    String requestUrl = url;
    return requestUrl;
  }

  ApiHttpError getErrorRequestResponseData(ResponseData responseData) {
    //返回失败信息
    ApiHttpError apiHttpError =
        ApiHttpError(ApiHttpErrorType.Default, responseData.errorMsg);

    if (kNeedAuthLoginErrorCode == responseData.errorCode) {
      apiHttpError = ApiHttpError(ApiHttpErrorType.Auth, responseData.errorMsg);
    }

    return apiHttpError;
  }

  ApiHttpError getRequestFailure(
      Response? response, DioErrorType dioErrorType) {
    LoggerManager()
        .error("getRequestFailure: $response, dioError:$dioErrorType");

    ApiHttpErrorType errorType = ApiHttpErrorType.Default;
    String errorMessage = "请求失败!";

    if (response != null) {
      if (dioErrorType == DioErrorType.connectionTimeout) {
        errorType = ApiHttpErrorType.NetWork;
        errorMessage = "网络链接异常!";
      } else if (dioErrorType == DioErrorType.sendTimeout) {
        errorType = ApiHttpErrorType.Timeout;
        errorMessage = "网络链接异常!";
      } else if (dioErrorType == DioErrorType.receiveTimeout) {
        errorType = ApiHttpErrorType.Timeout;
        errorMessage = "网络链接异常!";
      } else if (dioErrorType == DioErrorType.badResponse) {
        // When the server response, but with a incorrect status, such as 404, 503...
        if (response != null) {
          if (response.statusCode == 401) {
            errorType = ApiHttpErrorType.Auth;
            errorMessage = "认证失败!";
          } else if (response.statusCode == 400) {
            errorType = ApiHttpErrorType.BadRequest;
            errorMessage = "无效请求!";
          } else if (response.statusCode == 404) {
            errorType = ApiHttpErrorType.NotFound;
            errorMessage = "访问的资源丢失了!";
          } else if (response.statusCode == 405) {
            // 请求的方法错误
            errorType = ApiHttpErrorType.BadParamHeader;
            errorMessage = "参数出错!";
          } else if (response.statusCode! >= 500) {
            errorType = ApiHttpErrorType.BadRequest;
            errorMessage = "服务器居然累倒了!";
          }
        }
      } else if (dioErrorType == DioErrorType.cancel) {
        errorType = ApiHttpErrorType.Cancel;
        errorMessage = "请求已经取消";
      }
    } else {
      errorType = ApiHttpErrorType.NetWork;
      errorMessage = "网络链接异常!";
    }

    ApiHttpError apiHttpError = ApiHttpError(errorType, errorMessage);
    return apiHttpError;
  }
}

/// 上传的文件类
class UploadFileInfo {
  File file;
  String key;

  UploadFileInfo({required this.file, required this.key});
}

文件下载页面实现实例

class DownloadPage extends StatefulWidget {
  const DownloadPage({
    Key? key,
    this.messages,
    this.uniqueId,
    this.arguments,
  }) : super(key: key);

  final Object? arguments;
  final String? messages;
  final String? uniqueId;

  
  State<DownloadPage> createState() => _DownloadPageState();
}

class _DownloadPageState extends State<DownloadPage> {
  String _downloadPath =
      'https://dl.google.com/chrome/mac/stable/GGRO/googlechrome.dmg';
  double _downloadRatio = 0.0;
  String _downloadIndicator = '0.00%';
  late String _destPath;
  late CancelToken _token;
  bool _downloading = false;

  
  void initState() {
    getTemporaryDirectory()
        .then((tempDir) => {_destPath = tempDir.path + 'googlechrome.dmg'});

    super.initState();
  }

  void _downloadFile() {
    if (_downloading == true) {
      return;
    }
    _token = CancelToken();
    _downloading = true;
    HttpApi().doDownload(_downloadPath, _destPath, cancelToken: _token,
        progress: (int received, int total) {
      // 下载进度
      setState(() {
        _downloadRatio = (received / total);
        if (_downloadRatio == 1) {
          _downloading = false;
        }
        _downloadIndicator = (_downloadRatio * 100).toStringAsFixed(2) + '%';
      });
    }, completion: () {
      // 下载成功
      _downloading = false;
      FlutterLoadingHud.showToast(message: "\"下载完成\"");
    }, failure: (error) {
      // 下载出错
      _downloading = false;
      FlutterLoadingHud.showToast(message: error.message);
    });
  }

  void _cancelDownload() {
    if (_downloadRatio < 1.0) {
      _token.cancel();
      _downloading = false;
      setState(() {
        _downloadRatio = 0;
        _downloadIndicator = '0.00%';
      });
    }
  }

  void _deleteFile() {
    try {
      File downloadedFile = File(_destPath);
      if (downloadedFile.existsSync()) {
        downloadedFile.delete();
      } else {
        FlutterLoadingHud.showToast(message: "文件不存在");
      }
    } catch (e) {
      FlutterLoadingHud.showToast(
          message: "${e.toString()}");
    }
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      resizeToAvoidBottomInset: false,
      appBar: AppBar(
        leading: AppBarIconButton(
          icon: Icon(Icons.arrow_back_ios),
          onPressed: () => {NavigatorRoute.pop()},
        ),
        centerTitle: true,
        backgroundColor: ColorUtil.hexColor(0xffffff),
        foregroundColor: ColorUtil.hexColor(0x777777),
        elevation: 0,
        title: Text(
          "下载示例",
          textAlign: TextAlign.center,
          softWrap: true,
          style: TextStyle(
            fontSize: 17,
            color: ColorUtil.hexColor(0x333333),
            fontWeight: FontWeight.w600,
            fontStyle: FontStyle.normal,
            decoration: TextDecoration.none,
          ),
        ),
        shadowColor: ColorUtil.hexColor(0xffffff),
        toolbarHeight: 44.0,
        bottomOpacity: 0.0,
      ),
      body: Container(
        color: ColorUtil.hexColor(0xf7f7f7),
        alignment: Alignment.center,
        padding: EdgeInsets.all(10),
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Row(
              children: [
                _buildDownloadButton(),
                TextButton(
                  child: Text('取消'),
                  onPressed: () {
                    _cancelDownload();
                  },
                ),
                TextButton(
                  child: Text(
                    '删除',
                    style: TextStyle(
                        color: !_downloading ? Colors.red : Colors.grey),
                  ),
                  onPressed: (!_downloading ? _deleteFile : null),
                  style: ButtonStyle(),
                ),
              ],
            ),
            SizedBox(
              height: 25,
            ),
            Row(children: [
              Expanded(
                child: LinearProgressIndicator(
                  backgroundColor: Colors.grey[600],
                  value: _downloadRatio,
                ),
              ),
              SizedBox(
                width: 5,
              ),
              Text(
                _downloadIndicator,
                style: TextStyle(color: Colors.black, fontSize: 12.0),
              ),
            ]),
          ],
        ),
      ),
    );
  }

  Widget _buildDownloadButton() {
    return ButtonWidget(
      onPressed: () {
        _downloadFile();
      },
      child: Text(
        "下载文件",
        textAlign: TextAlign.center,
        softWrap: true,
        style: TextStyle(
          fontSize: 16,
          fontWeight: FontWeight.w400,
          fontStyle: FontStyle.normal,
          color: ColorUtil.hexColor(0xffffff),
          decoration: TextDecoration.none,
        ),
      ),
      height: 40,
      width: 100.0,
      highlightedColor: ColorUtil.hexColor(0xff462e),
      bgColor: ColorUtil.hexColor(0xff462e),
      bgHighlightedColor: ColorUtil.hexColor(0xff462e, alpha: 0.75),
      enabled: true,
      bgDisableColor: Colors.grey,
      borderRadius: 22.0,
    );
  }
}

三、小结

flutter开发实战-dio文件下载实现,封装dio下载功能,实现文件下载

学习记录,每天不停进步。

在线咨询 拨打电话

电话

010-67916526

微信二维码

微信二维码