Flutter调用Provider报错:查找已停用widget的祖先不安全
问题原因
- 核心诱因是socket事件监听未随页面生命周期解绑,持有了已销毁页面的失效BuildContext。你首次进入
CreateJoinPage时注册的createRoomSuccess回调,会持有当前页面的context引用;当你返回首页时,该页面已经被从组件树卸载(deactivated),但对应的socket监听没有被移除,回调依然保留着对失效context的引用。 - 你第二次进入
CreateJoinPage时,会再次注册一个同事件的新回调,此时socket客户端上同时绑定了两个同事件回调:一个持有着已经失效的旧页面context,一个持有新页面的context。当socket推送createRoomSuccess事件时,持有旧context的回调率先执行,调用Provider.of时会尝试从已经销毁的widget节点向上查找祖先Provider,直接触发"查找已停用widget的祖先不安全"的报错。 - 代码里还有一处显性bug:事件回调的入参命名为
room2,但更新Provider时传入的是未定义的room变量,就算context问题修复,这里也会抛出变量未定义的异常。
修复方案
按照以下步骤调整即可彻底解决问题:
- 将
CreateJoinPage从StatelessWidget改为StatefulWidget,把socket监听的注册、移除逻辑和页面生命周期绑定,不要在无状态组件里注册长期持有的异步回调。 - 按照官方报错提示,在
didChangeDependencies生命周期里提前获取Provider的引用缓存,不要在异步回调里临时通过context查找祖先节点。 - 注册监听前先移除同事件已存在的监听,页面销毁时在
dispose方法里必须移除对应的socket监听,避免回调持有失效context。 - 异步回调执行逻辑前,先判断当前页面是否还挂载在组件树上,未挂载直接终止逻辑,杜绝操作失效widget。
- 修正回调参数名不匹配的bug。
修复后参考代码
// 将原StatelessWidget改为StatefulWidget class CreateJoinPage extends StatefulWidget { const CreateJoinPage({super.key}); @override State<CreateJoinPage> createState() => _CreateJoinPageState(); } class _CreateJoinPageState extends State<CreateJoinPage> { // 提前缓存Provider引用,避免异步回调中重复查找 late final RoomDataProvider _roomProvider; @override void didChangeDependencies() { super.didChangeDependencies(); // 按官方提示,在此处获取祖先InheritedWidget的引用 _roomProvider = Provider.of<RoomDataProvider>(context, listen: false); // 先移除旧的同事件监听,防止重复绑定 _socketClient.off('createRoomSuccess'); // 注册新的事件监听 _socketClient.on('createRoomSuccess', _onCreateRoomSuccess); } @override void dispose() { // 页面销毁时移除监听,释放所有持有当前页面context的引用 _socketClient.off('createRoomSuccess'); super.dispose(); } // 抽离事件处理逻辑 void _onCreateRoomSuccess(dynamic roomData) { // 校验当前组件是否仍挂载在组件树,未挂载直接返回 if (!mounted) return; // 修正原参数名不匹配的问题 _roomProvider.updateRoomData(roomData); _roomProvider.insertPlayer(roomData); Navigator.push( context, MaterialPageRoute(builder: (_) => const ActivitySelectionPage()), ); } @override Widget build(BuildContext context) { // 原有页面构建逻辑保持不变 return const Scaffold(); } }
额外注意事项
- 如果你使用的是全局单例的socket客户端,所有页面注册的事件监听都必须在页面销毁时调用
off方法移除,否则回调会持续累积,不仅会出现context报错,还会导致同一个事件触发多次重复逻辑。 - 所有异步回调(包括网络请求、流监听、定时器、事件总线回调)中使用context前,都必须校验
mounted状态,避免操作已卸载的组件。
内容的提问来源于stack exchange,提问作者Basty b
相关产品推荐
相关产品推荐

