You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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问题修复,这里也会抛出变量未定义的异常。
修复方案

按照以下步骤调整即可彻底解决问题:

  1. 将CreateJoinPage从StatelessWidget改为StatefulWidget,把socket监听的注册、移除逻辑和页面生命周期绑定,不要在无状态组件里注册长期持有的异步回调。
  2. 按照官方报错提示,在didChangeDependencies生命周期里提前获取Provider的引用缓存,不要在异步回调里临时通过context查找祖先节点。
  3. 注册监听前先移除同事件已存在的监听,页面销毁时在dispose方法里必须移除对应的socket监听,避免回调持有失效context。
  4. 异步回调执行逻辑前,先判断当前页面是否还挂载在组件树上,未挂载直接终止逻辑,杜绝操作失效widget。
  5. 修正回调参数名不匹配的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 13:33:24