Flutter使用Bloc架构时如何取消Firebase Storage文件下载任务
Android Flutter Bloc 实现文件下载取消功能方案
1. 新增取消下载事件
首先在FileEvent的定义中新增取消事件,用于UI层向Bloc发送取消操作指令:
// 放在原有FileEvent的定义文件中 class CancelDownloadEvent extends FileEvent { @override List<Object?> get props => []; }
2. 注册事件并修改Bloc核心逻辑
2.1 注册事件处理器
在FileBloc的构造函数中注册新增的取消事件处理方法:
class FileBloc extends Bloc<FileEvent, FileState> { FileBloc() : super(FileState.initial()) { on<SaveFileEvent>(_saveFile); // 原有下载事件注册 on<CancelDownloadEvent>(_cancelDownload); // 新增取消事件注册 } // 后续处理方法写在类内部 }
2.2 实现取消下载处理方法
在Bloc中实现_cancelDownload方法,调用存储任务的cancel接口,清理临时文件并重置状态:
Future<void> _cancelDownload(Emitter<FileState> emit) async { final activeTask = state.task; if (activeTask != null) { try { await activeTask.cancel(); } catch (_) { // 取消抛出的异常无需特殊处理,直接走后续状态重置即可 } } // 重置为初始未下载状态 emit(const FileState( status: BlocStatus.initial, exists: false, task: null, )); }
2.3 修复原有下载方法的逻辑问题
原有saveFile方法存在逻辑错误:创建下载任务后直接标记为loaded状态,没有等待任务完成、也没有处理异常和取消场景。修改后的完整下载逻辑如下:
Future<void> _saveFile(Emitter<FileState> emit, SaveFileEvent event) async { emit(_loadingState()); final dir = await getApplicationDocumentsDirectory(); final targetFile = File('${dir.path}/${event.fileToDownload.name}'); // 提前删除残留的不完整文件,避免影响本次下载 if (await targetFile.exists()) { await targetFile.delete(); } final storageRef = FirebaseStorage.instance.ref('uploads/${event.fileToDownload.name}'); final downloadTask = storageRef.writeToFile(targetFile); // 先下发带task引用的加载状态,支持后续取消操作 emit(FileState( status: BlocStatus.loading, exists: false, task: downloadTask, )); try { // 等待下载任务执行完成 await downloadTask; // 校验文件是否真实存在 final isFileSaved = await targetFile.exists(); emit(FileState( status: isFileSaved ? BlocStatus.loaded : BlocStatus.error, exists: isFileSaved, task: null, // 任务结束后清空task引用,避免内存泄漏 )); } on FirebaseException catch (e) { // 捕获任务取消的特定异常,清理临时文件 if (e.code == 'canceled') { if (await targetFile.exists()) { await targetFile.delete(); } return; } // 处理其他下载错误 emit(FileState( status: BlocStatus.error, exists: false, task: null, errorMsg: e.message ?? '下载失败', )); } catch (e) { emit(FileState( status: BlocStatus.error, exists: false, task: null, errorMsg: e.toString(), )); } }
3. 修改UI层触发逻辑
找到代码中标记//here should be cancel functionality的位置,添加事件触发代码即可:
center: GestureDetector( onTap: () { // 向Bloc发送取消下载事件 fileBloc.add(CancelDownloadEvent()); }, child: SizedBox( height: 10.5.h, width: 10.5.w, child: SvgPicture.asset( 'assets/icons/cancel.svg', color: Palette.white, ), ), ),
注意事项
- 不要在UI层直接持有task引用调用cancel方法,所有状态变更都通过Bloc事件分发,符合单向数据流规范
- 取消任务后必须删除未下载完成的临时文件,否则下次下载时的文件存在性校验会出错
- 下载任务结束(成功/失败/取消)后必须清空state中的task引用,避免内存泄漏
- 原代码中
CircularPercentIndicator的percent参数写死为1,如果需要显示真实下载进度,可以监听downloadTask.snapshotEvents获取实时下载字节数,计算进度后更新到state中供UI使用
内容的提问来源于stack exchange,提问作者Veronika Haichenko
相关产品推荐
相关产品推荐

