Flutter中BottomSheet内Socket进度条无法更新的问题
问题解决:Flutter BottomSheet内Socket实时进度不更新的修复
核心问题原因
你在Socket回调中调用的setState是页面State的全局setState,而StatefulBuilder拥有独立的局部setState,页面的状态更新不会触发BottomSheet的UI重建,导致进度条无法实时刷新。
解决方案步骤
1. 改造initSocket,添加进度回调参数
让Socket初始化方法接收一个进度更新回调,直接将实时进度传递给BottomSheet,不再依赖页面的全局进度变量:
void initSocket(String livelapseId, Function(double) onProgressUpdate) { socket = IO.io(Endpoints.baseUrl, <String, dynamic>{ 'autoConnect': true, 'transports': ['websocket'], }); socket!.onConnect((_) { print('Connection established'); socket!.emit('joinRoom', "livelapse:$livelapseId"); socket!.on('livelapse:progress', (data) { print("progress" + data.toString()); print("operation" + data["operation"].toString()); double progress = double.parse(data["progress"].toString()); // 触发外部回调传递进度 onProgressUpdate(progress); }); socket!.on('livelapse:completed', (data) { print("completed" + data.toString()); // 完成时设置进度为100 onProgressUpdate(100); }); socket!.on('livelapse:errored', (data) { print("errored" + data.toString()); }); }); socket!.onDisconnect((_) => print('Connection Disconnection')); socket!.onConnectError((err) => print(err)); socket!.onError((err) => print(err)); }
2. 修改_showProgressBottomSheet,内部维护进度并绑定回调
在BottomSheet内部用StatefulBuilder的局部setState更新进度,同时将更新方法传递给Socket回调:
void _showProgressBottomSheet(BuildContext context) { // BottomSheet内部维护进度变量 double _sheetProgress = 0; // 保存StatefulBuilder的setState方法 StateSetter? sheetSetState; showModalBottomSheet( context: context, backgroundColor: Colors.transparent, builder: (context) => StatefulBuilder( builder: (BuildContext context, StateSetter setState) { sheetSetState = setState; return Container( padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 28.h), decoration: BoxDecoration( borderRadius: BorderRadius.only( topLeft: Radius.circular(16.r), topRight: Radius.circular(16.r)), color: Colors.white, ), height: 288.h, width: MediaQuery.of(context).size.width, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ Text( 'Generating LiveLapse', style: TextStyle( color: Helper.baseBlack, fontSize: 18.sp, fontWeight: FontWeight.w500), ), ], ), SizedBox(height: 20.h), Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ ListTile( contentPadding: EdgeInsets.zero, isThreeLine: true, leading: Container( padding: EdgeInsets.symmetric(horizontal: 6.w, vertical: 6.h), width: 32.w, height: 32.h, decoration: BoxDecoration( color: Color.fromRGBO(229, 240, 255, 1), borderRadius: BorderRadius.circular(32.r), border: Border.all( color: Color.fromRGBO(245, 249, 255, 1), width: 4.w)), child: SvgPicture.asset( 'assets/images/film.svg', ), ), title: Text( "Camera 2 - The Bridges.mp4", style: TextStyle( color: Helper.textColor700, fontSize: 14, fontWeight: FontWeight.w500), ), subtitle: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text( "Fetching images", style: TextStyle( color: Helper.textColor600, fontSize: 14, fontWeight: FontWeight.w400), ), SizedBox(height: 10.h), Row(mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ ClipRRect( borderRadius: BorderRadius.circular(4.r), child: LinearPercentIndicator( width: 210.w, fillColor: Helper.textColor300, backgroundColor: Helper.textColor300, progressColor: Helper.primary, padding: EdgeInsets.zero, curve: Curves.easeInOut, barRadius: Radius.circular(4.r), lineHeight: 8.h, // 使用BottomSheet内部的进度变量 percent: _sheetProgress / 100 ), ), Text( "${(_sheetProgress).toInt()}%", style: TextStyle( color: Helper.textColor700, fontSize: 14, fontWeight: FontWeight.w500), ) ]) ]), trailing: SvgPicture.asset( 'assets/images/checkbox_base.svg', ), ), SizedBox(height: 20.h), Container( height: 52.h, width: double.infinity, child: ElevatedButton( child: Text( "Close", style: TextStyle( color: Colors.white, fontSize: 16, fontWeight: FontWeight.w500), ), style: ButtonStyle( backgroundColor: MaterialStatePropertyAll(Helper.baseBlack), shape: MaterialStateProperty.all( RoundedRectangleBorder( borderRadius: BorderRadius.circular(8.r), ), )), onPressed: () { context.pop(); }, ), ), ], ), ], ), ); }, ), ); // 初始化Socket,传入BottomSheet的进度更新回调 initSocket(res["_id"], (progress) { // 检查BottomSheet是否还存在,避免上下文销毁报错 if (context.mounted && sheetSetState != null) { sheetSetState!(() { _sheetProgress = progress; }); } }); }
3. 调用方式简化
现在只需直接调用BottomSheet方法即可,无需单独调用initSocket:
_showProgressBottomSheet(context);
关键说明
- 局部setState的作用:
StatefulBuilder提供的setState仅负责重建BottomSheet内部UI,和页面全局状态完全独立,必须用它来触发BottomSheet的进度更新。 - 回调解耦:通过回调传递进度,避免Socket逻辑和页面/BottomSheet状态强绑定,代码更健壮。
- 上下文安全检查:
context.mounted确保在更新状态时BottomSheet未被关闭,防止出现上下文已销毁的异常。
内容的提问来源于stack exchange,提问作者Hadi Khan
相关产品推荐
相关产品推荐

