监听Stream后在Bloc中无法触发状态更新问题求助
Flutter上传进度更新异常:StreamController结合Dio触发事件但Widget不重建
在Flutter开发中,使用StreamController配合Dio跟踪图片上传进度,向Stream添加值时能触发事件并发送新状态,但Widget无法正常更新。调试日志显示事件已触发,却看不到UI刷新。
ChatBloc 代码
class Chat extends Bloc<ChatEvent, ChatState> { final StreamController<FileDetails> uploadStream = StreamController<FileDetails>(); Chat(this.chatUseCases, this.chat) : super(const SingleChatState()) { on<ChatEmitUploadList>(_emitUploadList); /// listen to changes form upload stream uploadStream.stream.listen((event) { List<FileDetails> details = state.fileDetailsList; details .firstWhereOrNull((element) => element.id == event.id)! .percentage = event.percentage; add(SingleChatEvent.emitUploadList(fileDetails: event)); }); } } FutureOr<void> _emitUploadList( ChatEmitUploadList event, Emitter<SingleChatState> emit) { print('emit once'); List<FileDetails> details = List.from(state.fileDetailsList); details .firstWhereOrNull((element) => element.id == event.fileDetails.id)! .percentage = event.fileDetails.percentage; emit(state.copyWith(fileDetailsList: details)); }
BlocBuilder 代码
class UploadingWidget extends StatelessWidget { const UploadingWidget({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return BlocBuilder<ChatBloc, ChatState>( builder: (context, state) { if (state.status == SingleChatStatus.uploadingFiles) { return Container( decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(20.r)), margin: const EdgeInsets.all(8), padding: const EdgeInsets.all(8), child: CustomScrollView( shrinkWrap: true, slivers: [ SliverGrid.builder( gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 100), itemBuilder: (context, index) => Material( child: SizedBox( height: 100, width: 100, child: Center( child: Text( state.fileDetailsList[index].percentage.toString()), ), ), ), itemCount: state.fileDetailsList.length, ) ], ), ); } return const SizedBox.shrink(); }, ); } }
Dio 上传进度监听代码
final response = await dio.post(BASE_TEST+sendMessageEndPoint,data: data, onSendProgress: (count, total) { if(file!=null){ String fileName = file.path.toString().split('/').last; double percentage = (count / total * 100).toDouble(); uploadStream.sink.add(FileDetails(id: file.path!, percentage: percentage,file: file)); } },);
调试日志
[log] onEvent -- ChatBloc, ChatEvent.emitUploadList(fileDetails: Instance of 'FileDetails') [log] onEvent -- ChatBloc, ChatEvent.emitUploadList(fileDetails: Instance of 'FileDetails') [log] onEvent -- ChatBloc, ChatEvent.emitUploadList(fileDetails: Instance of 'FileDetails') [log] onEvent -- ChatBloc, ChatEvent.emitUploadList(fileDetails: Instance of 'FileDetails') [log] onEvent -- ChatBloc, ChatEvent.emitUploadList(fileDetails: Instance of 'FileDetails')
问题原因及修复方案
核心问题
- 状态不可变性被破坏:
FileDetails是可变类,即便用List.from复制列表,列表内的FileDetails对象还是同一引用。修改对象的percentage属性后,Bloc的浅对比无法识别状态变化,导致UI不触发重建。 - 提前修改状态违反原则:在
uploadStream的监听回调里直接修改state中的对象,违背了Bloc状态不可变的核心规则,容易引发状态不一致问题。
修复步骤
将
FileDetails改为不可变类
用final修饰所有属性,并提供copyWith方法生成新对象:class FileDetails { final String id; final double percentage; final File? file; const FileDetails({required this.id, required this.percentage, this.file}); FileDetails copyWith({double? percentage}) { return FileDetails( id: id, percentage: percentage ?? this.percentage, file: file, ); } }修正Bloc状态更新逻辑
在_emitUploadList中通过替换对象而非直接修改属性来更新列表:FutureOr<void> _emitUploadList( ChatEmitUploadList event, Emitter<SingleChatState> emit) { print('emit once'); final updatedList = state.fileDetailsList.map((detail) { if (detail.id == event.fileDetails.id) { return event.fileDetails; // 直接传入新的FileDetails对象 // 或者用copyWith:return detail.copyWith(percentage: event.fileDetails.percentage); } return detail; }).toList(); emit(state.copyWith(fileDetailsList: updatedList)); }移除Stream监听中对状态的直接修改
回调里只需要添加事件,不要操作现有状态:uploadStream.stream.listen((event) { add(SingleChatEvent.emitUploadList(fileDetails: event)); });确认状态类
copyWith方法正确性
确保SingleChatState的copyWith返回全新实例,且fileDetailsList是新的列表引用,避免返回原状态。
额外检查点
- 确认上传过程中
SingleChatStatus.uploadingFiles状态保持正确,防止因状态切换导致Widget隐藏。 - 可以在
emit后打印状态的fileDetailsList,验证percentage是否确实更新。
内容的提问来源于stack exchange,提问作者Yaser Hesham
相关产品推荐
相关产品推荐

