dio与Flutter File Picker:文件选择与上传
【免费下载链接】dio 项目地址: https://gitcode.com/gh_mirrors/dio/dio
在移动应用开发中,文件选择与上传是常见的功能需求。无论是头像上传、文档提交还是媒体分享,都需要可靠的文件处理方案。本文将介绍如何结合dio(强大的HTTP客户端)和Flutter File Picker(文件选择工具)实现高效的文件选择与上传功能,解决开发者在实际项目中遇到的痛点问题。
技术准备与环境配置
核心依赖库
实现文件选择与上传功能需要以下两个核心库:
- dio:一个强大的Dart HTTP客户端,支持拦截器、FormData、请求取消、超时等高级功能。在项目中负责处理HTTP请求,特别是文件上传部分。
- Flutter File Picker:一个Flutter插件,用于从设备中选择文件,支持多种文件类型和平台。
项目配置
在pubspec.yaml文件中添加依赖:
dependencies:
dio: ^5.4.0
file_picker: ^5.5.0
然后运行flutter pub get安装依赖。
平台特定配置
Android配置
在android/app/src/main/AndroidManifest.xml中添加文件读取权限:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
iOS配置
在ios/Runner/Info.plist中添加文件访问权限描述:
<key>NSPhotoLibraryUsageDescription</key>
<string>需要访问相册以选择文件</string>
<key>NSDocumentDirectoryUsageDescription</key>
<string>需要访问文档目录以选择文件</string>
文件选择:使用Flutter File Picker
基本文件选择
Flutter File Picker提供了简单易用的API来选择文件。以下是一个基本的文件选择示例:
import 'package:file_picker/file_picker.dart';
Future<FilePickerResult?> pickFile() async {
// 选择单个文件
FilePickerResult? result = await FilePicker.platform.pickFiles(
type: FileType.any, // 允许选择任何类型的文件
allowMultiple: false, // 不允许选择多个文件
);
if (result != null) {
// 获取选中的文件信息
PlatformFile file = result.files.first;
print('选择的文件名称: ${file.name}');
print('文件大小: ${file.size} bytes');
print('文件路径: ${file.path}');
return result;
} else {
// 用户取消了选择
print('用户取消了文件选择');
return null;
}
}
高级文件选择选项
Flutter File Picker还支持更多高级选项,如筛选文件类型、限制文件大小等:
Future<FilePickerResult?> pickImageFile() async {
FilePickerResult? result = await FilePicker.platform.pickFiles(
type: FileType.image, // 只允许选择图片文件
allowedExtensions: ['jpg', 'jpeg', 'png'], // 允许的文件扩展名
allowCompression: true, // 允许压缩图片
withData: false, // 不直接加载文件数据,节省内存
withReadStream: true, // 获取文件读取流
);
return result;
}
文件上传:使用dio
了解dio的FormData
dio使用FormData类来构建multipart/form-data请求,这是文件上传的标准方式。FormData类位于dio/lib/src/form_data.dart文件中,它提供了多种构造方式来创建表单数据。
单文件上传
结合Flutter File Picker和dio实现单文件上传:
import 'package:dio/dio.dart';
import 'package:file_picker/file_picker.dart';
Future<void> uploadFile(FilePickerResult? result) async {
if (result == null) return;
PlatformFile file = result.files.first;
// 创建dio实例
Dio dio = Dio();
// 构建FormData
FormData formData = FormData.fromMap({
'file': await MultipartFile.fromFile(
file.path!, // 文件路径
filename: file.name, // 文件名
contentType: MediaType('image', 'jpeg'), // 文件类型
),
'description': '这是一张图片', // 其他表单字段
});
try {
// 发送POST请求上传文件
Response response = await dio.post(
'https://api.example.com/upload',
data: formData,
options: Options(
headers: {
'Authorization': 'Bearer your_token_here', // 添加认证头
},
),
onSendProgress: (int sent, int total) {
// 上传进度回调
double progress = sent / total;
print('上传进度: ${(progress * 100).toStringAsFixed(0)}%');
},
);
print('文件上传成功: ${response.data}');
} catch (e) {
print('文件上传失败: $e');
}
}
多文件上传
dio同样支持多文件上传,只需在FormData中添加多个文件字段即可:
Future<void> uploadMultipleFiles(List<PlatformFile> files) async {
Dio dio = Dio();
FormData formData = FormData();
// 添加多个文件
for (int i = 0; i < files.length; i++) {
formData.files.add(MapEntry(
'files[$i]', // 字段名,后端可以通过files数组获取
await MultipartFile.fromFile(
files[i].path!,
filename: files[i].name,
),
));
}
// 添加其他表单字段
formData.fields.add(const MapEntry('user_id', '123456'));
try {
Response response = await dio.post(
'https://api.example.com/upload/multiple',
data: formData,
onSendProgress: (int sent, int total) {
print('总上传进度: ${(sent / total * 100).toStringAsFixed(0)}%');
},
);
print('多文件上传成功: ${response.data}');
} catch (e) {
print('多文件上传失败: $e');
}
}
完整示例:文件选择与上传综合应用
文件选择与上传流程
下面是一个完整的文件选择与上传流程示例,包括错误处理和用户反馈:
import 'package:flutter/material.dart';
import 'package:dio/dio.dart';
import 'package:file_picker/file_picker.dart';
class FileUploader extends StatefulWidget {
@override
_FileUploaderState createState() => _FileUploaderState();
}
class _FileUploaderState extends State<FileUploader> {
FilePickerResult? _selectedFile;
double _uploadProgress = 0.0;
String _statusMessage = '请选择文件';
Future<void> _pickAndUploadFile() async {
setState(() {
_statusMessage = '正在选择文件...';
});
// 选择文件
FilePickerResult? result = await FilePicker.platform.pickFiles(
type: FileType.image,
allowMultiple: false,
);
if (result == null) {
setState(() {
_statusMessage = '文件选择已取消';
});
return;
}
setState(() {
_selectedFile = result;
_statusMessage = '正在上传文件: ${result.files.first.name}';
_uploadProgress = 0.0;
});
// 上传文件
await _uploadSelectedFile(result);
}
Future<void> _uploadSelectedFile(FilePickerResult result) async {
PlatformFile file = result.files.first;
Dio dio = Dio();
try {
FormData formData = FormData.fromMap({
'file': await MultipartFile.fromFile(
file.path!,
filename: file.name,
),
});
Response response = await dio.post(
'https://api.example.com/upload',
data: formData,
onSendProgress: (int sent, int total) {
setState(() {
_uploadProgress = sent / total;
});
},
);
setState(() {
_statusMessage = '文件上传成功! 服务器响应: ${response.statusCode}';
});
} catch (e) {
setState(() {
_statusMessage = '文件上传失败: $e';
});
}
}
@override
Widget build(BuildContext context) {
return Column(
children: [
ElevatedButton(
onPressed: _pickAndUploadFile,
child: const Text('选择并上传文件'),
),
const SizedBox(height: 20),
Text(_statusMessage),
const SizedBox(height: 10),
LinearProgressIndicator(
value: _uploadProgress,
minHeight: 8,
),
if (_selectedFile != null)
Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
Text('选中的文件: ${_selectedFile!.files.first.name}'),
Text('文件大小: ${_selectedFile!.files.first.size} bytes'),
],
),
),
],
);
}
}
示例代码解析
上述示例实现了一个完整的文件选择与上传功能,主要包含以下几个部分:
- UI组件:包含一个按钮用于触发文件选择,一个文本组件显示状态信息,一个进度条显示上传进度。
- 文件选择:调用
FilePicker.platform.pickFiles方法选择图片文件。 - 文件上传:使用dio的
FormData和MultipartFile构建上传请求,并通过onSendProgress回调获取上传进度。 - 状态管理:使用setState更新UI状态,包括上传进度、状态消息和选中的文件信息。
高级功能与最佳实践
大文件分片上传
对于大文件,建议使用分片上传的方式,将文件分成多个小块依次上传,以提高上传成功率和断点续传能力。以下是一个简单的分片上传实现:
Future<void> uploadLargeFile(PlatformFile file, {int chunkSize = 2 * 1024 * 1024}) async {
Dio dio = Dio();
int fileSize = file.size;
int chunks = (fileSize / chunkSize).ceil();
for (int i = 0; i < chunks; i++) {
int start = i * chunkSize;
int end = (i + 1) * chunkSize;
if (end > fileSize) end = fileSize;
// 读取文件分片
File fileData = File(file.path!);
Uint8List chunkData = await fileData.readAsBytes().then((bytes) => bytes.sublist(start, end));
// 创建分片上传的FormData
FormData formData = FormData.fromMap({
'file_id': 'unique_file_id', // 唯一文件ID,用于后端合并分片
'chunk_index': i, // 当前分片索引
'total_chunks': chunks, // 总分片数
'chunk_data': MultipartFile.fromBytes(
chunkData,
filename: 'chunk_$i',
),
});
// 上传分片
await dio.post(
'https://api.example.com/upload_chunk',
data: formData,
);
// 更新整体进度
setState(() {
_uploadProgress = (i + 1) / chunks;
});
}
// 所有分片上传完成后,通知后端合并文件
await dio.post('https://api.example.com/merge_chunks', data: {
'file_id': 'unique_file_id',
'total_chunks': chunks,
'filename': file.name,
});
}
错误处理与重试机制
在实际应用中,网络请求可能会失败,因此需要添加错误处理和重试机制:
Future<Response> uploadWithRetry({
required FormData data,
required String url,
int maxRetries = 3,
}) async {
Dio dio = Dio();
int retries = 0;
while (retries < maxRetries) {
try {
Response response = await dio.post(
url,
data: data,
onSendProgress: (sent, total) {
// 处理上传进度
},
);
return response;
} catch (e) {
retries++;
if (retries >= maxRetries) {
throw Exception('上传失败,已重试$maxRetries次: $e');
}
// 指数退避重试
int delay = (retries * retries) * 1000; // 1s, 4s, 9s...
print('上传失败,$delay毫秒后重试($retries/$maxRetries)...');
await Future.delayed(Duration(milliseconds: delay));
}
}
throw Exception('上传失败,达到最大重试次数');
}
取消上传请求
dio支持取消正在进行的请求,可以用于实现取消上传功能:
// 创建取消令牌
CancelToken cancelToken = CancelToken();
// 发起上传请求,传入取消令牌
try {
Response response = await dio.post(
'https://api.example.com/upload',
data: formData,
cancelToken: cancelToken,
);
} on DioException catch (e) {
if (CancelToken.isCancel(e)) {
print('上传已取消');
} else {
print('上传失败: $e');
}
}
// 取消上传
void cancelUpload() {
if (!cancelToken.isCancelled) {
cancelToken.cancel('用户取消上传');
}
}
总结与展望
本文详细介绍了如何结合dio和Flutter File Picker实现文件选择与上传功能,从基本用法到高级特性,涵盖了实际项目开发中可能遇到的各种场景。通过本文的学习,开发者可以掌握:
- 使用Flutter File Picker选择不同类型的文件
- 使用dio的FormData构建文件上传请求
- 实现单文件和多文件上传
- 显示上传进度和处理上传状态
- 大文件分片上传、错误重试和请求取消等高级功能
文件上传是移动应用开发中的常见需求,掌握这些技能可以帮助开发者构建更健壮、更用户友好的应用。未来,随着Web技术的发展,我们可以期待更多新的特性和优化,如更高效的文件压缩算法、更智能的分片策略等,进一步提升文件上传体验。
希望本文对你的项目开发有所帮助,如果你有任何问题或建议,欢迎在评论区留言讨论!
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



