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

Flutter中Cannot use "ref" after widget disposed错误解决求助

问题解决:Riverpod 「Cannot use "ref" after the widget was disposed」错误修复

错误根源

执行nightContext.go('/day/$yesterday')后,当前页面的Widget已被销毁,但后续代码仍尝试通过已失效的ref修改loadingProvider状态,触发报错。同时GlobalLoading组件未在Widget销毁时清理OverlayEntry,存在潜在内存泄漏问题。

修复方案

1. 修正异步任务的状态更新逻辑

将状态重置操作移至导航前,确保在Widget销毁前完成状态修改,同时添加异常捕获避免加载框永久显示:

MyButton(
  title: 'go to day',
  onPressed: () async {
    ref.read(loadingProvider.notifier).state = true;

    try {
      final isar = await ref.read(isarServiceProvider.future);
      final yesterday = await isar.getDayNumber();

      await isar.putGameStatus(
        dayNumber: yesterday,
        isDay: true,
        situation: MyStrings.dayPage,
      );

      await ref.read(currentPlayersProvider.notifier).action(MyStrings.dayPage);

      // 先重置加载状态,再执行导航
      ref.read(loadingProvider.notifier).state = false;
      nightContext.go('/day/$yesterday');
    } catch (e) {
      // 异常时强制重置状态
      ref.read(loadingProvider.notifier).state = false;
      debugPrint('任务执行失败: $e');
    }
  },
)

2. 完善GlobalLoading的资源清理

在Widget销毁时自动清理所有OverlayEntry,同时显式设置监听的cancelOnDispose属性,避免内存泄漏:

class GlobalLoading extends HookConsumerWidget {
  const GlobalLoading({required this.child, Key? key}) : super(key: key);

  final Widget child;

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final _entries = useState<List<OverlayEntry?>>([]);

    // 监听加载状态,显式声明销毁时取消监听
    ref.listen<bool>(
      loadingProvider,
      (previous, next) {
        if (previous == next) return;
        if (next) {
          final barrierEntry = OverlayEntry(
            builder: (_) => ModalBarrier(color: AppColors.backGround.withOpacity(0.85)),
          );
          final loadingEntry = OverlayEntry(
            builder: (_) => Center(
              child: LoadingAnimationWidget.inkDrop(
                color: AppColors.secondaries[1],
                size: 160,
              ),
            ),
          );
          _entries.value.addAll([barrierEntry, loadingEntry]);
          Overlay.of(context).insertAll(_entries.value.whereType<OverlayEntry>());
        } else {
          _entries.value.forEach((entry) => entry?.remove());
          _entries.value.clear();
        }
      },
      cancelOnDispose: true,
    );

    // Widget销毁时强制清理OverlayEntry
    ref.onDispose(() {
      _entries.value.forEach((entry) => entry?.remove());
      _entries.value.clear();
    });

    return child;
  }
}

3. 可选:改用AsyncNotifier优化加载状态管理

如果想更优雅地处理异步任务的加载/成功/失败状态,可以替换StateProvider为AsyncNotifierProvider,自动管理状态生命周期:

// 定义AsyncNotifier
class DayTransitionNotifier extends AsyncNotifier<void> {
  @override
  FutureOr<void> build() {}

  Future<void> transitionToDay() async {
    state = const AsyncLoading();
    try {
      final isar = await ref.read(isarServiceProvider.future);
      final yesterday = await isar.getDayNumber();

      await isar.putGameStatus(
        dayNumber: yesterday,
        isDay: true,
        situation: MyStrings.dayPage,
      );

      await ref.read(currentPlayersProvider.notifier).action(MyStrings.dayPage);

      state = const AsyncData(null);
      ref.read(goRouterProvider).go('/day/$yesterday');
    } catch (e, stackTrace) {
      state = AsyncError(e, stackTrace);
      // 这里可添加用户提示等异常处理逻辑
    }
  }
}

// 声明Provider
final dayTransitionProvider = AsyncNotifierProvider<DayTransitionNotifier, void>(
  DayTransitionNotifier.new,
);

// UI中调用
MyButton(
  title: 'go to day',
  onPressed: () {
    ref.read(dayTransitionProvider.notifier).transitionToDay();
  },
)

// GlobalLoading监听状态变化
ref.listen<AsyncValue<void>>(
  dayTransitionProvider,
  (previous, next) {
    if (next is AsyncLoading) {
      // 显示加载框逻辑
    } else {
      // 隐藏加载框逻辑
    }
  },
)

内容的提问来源于stack exchange,提问作者Behzad Ayubifar

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 23:48:21