You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 20:52:07