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

如何用Dio实现上传进度显示并结合Cubit更新UI?

Dio上传进度更新UI(Cubit架构实现)

针对你的场景,这里提供两种实用的实现方式,适配Cubit架构下Repository和UI的状态同步需求:


方式一:通过Stream传递进度

适合需要对进度流做额外处理(比如节流、多监听)的场景,步骤如下:

1. Repository层封装上传逻辑并暴露进度Stream

class UploadRepository {
  final ApiService _apiService = ApiService();

  Stream<double> uploadFile(String url, FormData formData) {
    final progressController = StreamController<double>();

    _apiService.dio.post(
      url,
      data: formData,
      onSendProgress: (count, total) {
        if (total > 0) {
          // 计算百分比进度,避免除以0
          final progress = (count / total) * 100;
          progressController.add(progress);
        }
      },
    ).then((_) {
      progressController.close();
    }).catchError((error) {
      progressController.addError(error);
      progressController.close();
    });

    return progressController.stream;
  }
}

2. Cubit层监听Stream并更新状态

// 定义上传状态类
abstract class UploadState {}
class UploadInitial extends UploadState {}
class UploadInProgress extends UploadState {
  final double progress;
  UploadInProgress({required this.progress});
}
class UploadSuccess extends UploadState {}
class UploadFailed extends UploadState {
  final String error;
  UploadFailed({required this.error});
}

// Cubit实现
class UploadCubit extends Cubit<UploadState> {
  final UploadRepository _repository;
  StreamSubscription? _progressSubscription;

  UploadCubit(this._repository) : super(UploadInitial());

  void startUpload(String url, FormData formData) {
    emit(UploadInProgress(progress: 0.0));
    _progressSubscription = _repository.uploadFile(url, formData)
      .listen(
        (progress) => emit(UploadInProgress(progress: progress)),
        onError: (error) => emit(UploadFailed(error: error.toString())),
        onDone: () => emit(UploadSuccess()),
      );
  }

  @override
  Future<void> close() {
    _progressSubscription?.cancel(); // 取消订阅防止内存泄漏
    return super.close();
  }
}

3. UI层用BlocBuilder渲染进度

BlocBuilder<UploadCubit, UploadState>(
  builder: (context, state) {
    if (state is UploadInProgress) {
      return LinearProgressIndicator(value: state.progress / 100);
    } else if (state is UploadSuccess) {
      return const Text('上传完成');
    } else if (state is UploadFailed) {
      return Text('上传失败:${state.error}');
    }
    return ElevatedButton(
      onPressed: () {
        // 触发上传逻辑
        context.read<UploadCubit>().startUpload(url, formData);
      },
      child: const Text('开始上传'),
    );
  },
)

方式二:通过回调函数传递进度

代码更简洁,适合单一监听的场景,无需额外维护Stream:

1. Repository层添加进度回调参数

class UploadRepository {
  final ApiService _apiService = ApiService();

  Future<void> uploadFile(
    String url,
    FormData formData,
    Function(double) onProgress, // 进度回调
  ) async {
    try {
      await _apiService.dio.post(
        url,
        data: formData,
        onSendProgress: (count, total) {
          if (total > 0) {
            final progress = (count / total) * 100;
            onProgress(progress);
          }
        },
      );
    } catch (e) {
      rethrow;
    }
  }
}

2. Cubit层通过回调更新状态

class UploadCubit extends Cubit<UploadState> {
  final UploadRepository _repository;

  UploadCubit(this._repository) : super(UploadInitial());

  Future<void> startUpload(String url, FormData formData) async {
    try {
      emit(UploadInProgress(progress: 0.0));
      await _repository.uploadFile(
        url,
        formData,
        (progress) => emit(UploadInProgress(progress: progress)),
      );
      emit(UploadSuccess());
    } catch (e) {
      emit(UploadFailed(error: e.toString()));
    }
  }
}

UI层代码和方式一完全一致,无需修改。


注意事项

  • 处理total=0的边界情况:部分服务器不返回文件总大小,此时可隐藏进度指示器或显示"上传中..."的提示
  • Cubit的close方法必须取消Stream订阅(方式一),避免内存泄漏
  • 状态类要清晰区分不同阶段,确保UI能正确响应状态变化

内容的提问来源于stack exchange,提问作者As Kira

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 03:35:19