Flutter Dio多文件FormData上传进度直接跳100的问题求助
多文件FormData上传准确进度追踪方案
问题根源:默认的MultipartFile.fromFile会一次性将文件读取到内存后再发送,导致Dio的onSendProgress只能感知到"瞬间完成"的进度跳变。要实现精准进度,需要监听每个文件的流式读取进度,并汇总到全局进度中。
实现步骤
1. 计算上传内容总大小
需要提前计算表单所有字段(文本+文件)的总字节数,作为进度计算的分母:
// 计算单个文件大小 Future<int> getFileSize(File file) async { final stats = await file.stat(); return stats.size; } // 计算表单文本字段的multipart格式字节数 int calculateFormFieldsSize(Map<String, dynamic> fields) { final boundary = 'dio-boundary'; // 和Dio默认边界符一致 int size = 0; fields.forEach((key, value) { size += '--$boundary\r\n'.length; size += 'Content-Disposition: form-data; name="$key"\r\n\r\n'.length; size += '$value\r\n'.length; }); size += '--$boundary--\r\n'.length; return size; }
2. 自定义带进度监听的MultipartFile
通过MultipartFile的fromStream构造函数,包装文件读取流并实时累加已发送字节:
// 局部变量维护当前上传的进度状态 int _totalBytes = 0; int _sentBytes = 0; Future<MultipartFile> createProgressTrackingMultipartFile(File file, String filename) async { final fileSize = await getFileSize(file); _totalBytes += fileSize; final stream = file.openRead().transform(StreamTransformer.fromHandlers( handleData: (data, sink) { _sentBytes += data.length; sink.add(data); // 实时更新进度 loadingProvider.setProgress(((_sentBytes / _totalBytes) * 100).toInt()); }, )); return MultipartFile.fromStream( () => stream, fileSize, filename: filename, ); }
3. 重构上传代码
替换原有的MultipartFile.fromFile为自定义方法,同时将表单文本字段大小计入总字节:
static Future<Response> registerVariation(VariationRegistrationForm variationRegistrationForm, int productId) async { // 重置进度状态 _totalBytes = 0; _sentBytes = 0; List<File?> nonNullImageFiles = variationRegistrationForm.variationImages!.where((image) => image != null).toList(); // 分离表单文本字段并计算其大小 final Map<String, dynamic> formFields = { 'price': variationRegistrationForm.price, 'quantity': variationRegistrationForm.quantity, 'is_default': variationRegistrationForm.isPrimary, 'product_id': productId, 'property_values_id': properties }; _totalBytes += calculateFormFieldsSize(formFields); // 创建带进度追踪的文件列表 List<Future<MultipartFile>> variationImagesFutures = nonNullImageFiles .map( (File? image) => createProgressTrackingMultipartFile(image!, image.path.split('/').last), ) .toList(); List<MultipartFile> variationImages = await Future.wait(variationImagesFutures); // 构建最终FormData final Map<String, dynamic> form = { ...formFields, 'varient_images': variationImages, }; final FormData formData = FormData.fromMap(form); loadingProvider.setLoading(true); final Response response = await dio.post( '$endPoint/$target', options: Options(headers: headers), data: formData, // 此处onSendProgress可留空,进度已在流处理中实时更新 ); loadingProvider.setLoading(false); return response; }
注意事项
- 确保使用Dio最新稳定版,避免流式上传的兼容性问题
- 若存在多并发上传场景,需将
_totalBytes和_sentBytes改为局部变量或封装到类中,避免状态冲突 - 表单字段的大小计算需与Dio的Multipart格式完全一致,否则会导致进度计算偏差
内容的提问来源于stack exchange,提问作者Ahmed
相关产品推荐
相关产品推荐

