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下载功能,实现文件下载
学习记录,每天不停进步。