Flutter中ConnectManagerContent组件为何被重复实例化?
解决Flutter中ConnectManagerContent组件重复实例化的问题
问题分析
从打印日志可以看到,ConnectManagerContent的initState(对应打印3、4)被执行了两次,导致定时器重复创建,网络恢复时触发两次API调用。核心原因有两点:
- Flutter框架在SnackBar的动画渲染过程中,可能会重新构建content组件;未指定唯一Key时,Flutter无法复用原有组件实例,会创建新的StatefulWidget实例。
- 未在组件销毁时取消定时器,旧实例的定时器仍在运行,叠加新实例的定时器导致重复回调。
解决方案
1. 给ConnectManagerContent添加唯一Key
在创建ConnectManagerContent时指定唯一Key,让Flutter识别这是同一个组件,避免重复实例化。
修改ConnectManager中showConnectManager方法的SnackBar内容部分:
content: ConnectManagerContent( key: ValueKey(id), // 添加唯一Key id: id, message: dioInternetConnectionRequired, onConnect: onConnect, ),
同时修改ConnectManagerContent的构造函数,移除currentContext参数(改用组件自身context):
const ConnectManagerContent({ super.key, required this.message, required this.onConnect, this.id, });
2. 在组件销毁时取消定时器
在_ConnectManagerContentState中重写dispose方法,确保定时器被及时取消,避免内存泄漏和重复回调:
@override void dispose() { timer.cancel(); super.dispose(); }
3. 优化定时器逻辑与context使用
将checkInternetConnection的回调中使用组件自身的context,替换原来的外部传递的currentContext:
if (value) { timer.cancel(); widget.onConnect(); ScaffoldMessenger.of(context).hideCurrentSnackBar(); // 使用自身context }
4. 确保弹窗只被调用一次
在调用方的Widget中添加状态标记,避免因Bloc状态重复触发导致多次调用showConnectManager:
// 在调用方的State类中添加变量 bool _isConnectSnackBarVisible = false; // 触发逻辑中添加判断 if (isCennectionError(state.error) && !_isConnectSnackBarVisible) { _isConnectSnackBarVisible = true; print("1 SETUP ========"); ConnectManager( id: "1234", context: context, connectBloc: connectionBloc, onVisible: handleErrorVisible, onConnect: () { context.read<MediasBloc>().add( UpdateMedia( id: widget.media.id!, className: className, data: { "title": mediaTitleFieldController.text, "description": mediaDescFieldController.text }, ), ); handleErrorDismiss(); _isConnectSnackBarVisible = false; // 重置标记 }, ).showConnectManager(); return; }
修改后的完整ConnectManagerContent代码
class ConnectManagerContent extends StatefulWidget { final String message; final Function onConnect; final String? id; const ConnectManagerContent({ super.key, required this.message, required this.onConnect, this.id, }); @override State<ConnectManagerContent> createState() => _ConnectManagerContentState(); } class _ConnectManagerContentState extends State<ConnectManagerContent> { final Debouncer debouncer = Debouncer(); bool connected = false; late final Timer timer; @override void initState() { super.initState(); print("3 SETUP ========"); print(widget.id); print("3 SETUP ========"); init(); } Future checkInternetConnection() async { try { final result = await InternetAddress.lookup("google.com"); if (result.isNotEmpty && result[0].rawAddress.isNotEmpty) { return true; } } on SocketException catch (_) { return false; } return false; } void init() { print("4 SETUP ========"); timer = Timer.periodic( const Duration(seconds: 3), (timer) { checkInternetConnection().then( (value) { if (value) { timer.cancel(); widget.onConnect(); ScaffoldMessenger.of(context).hideCurrentSnackBar(); } }, ); }, ); } @override void dispose() { timer.cancel(); super.dispose(); } @override Widget build(BuildContext context) { return Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Expanded( child: Text( widget.message, textAlign: TextAlign.center, style: const TextStyle( height: 1.3, ), ), ), ], ), const SizedBox(height: 20), const Row( mainAxisAlignment: MainAxisAlignment.center, children: [Text("Checking...")], ), ], ); } }
内容的提问来源于stack exchange,提问作者Aaron Balthaser
相关产品推荐
相关产品推荐

