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

迁移至Riverpod Generator与Flutter Hooks时Provider修改报错求助

Riverpod Generator迁移后修改Provider触发构建错误

错误信息

FlutterError (Tried to modify a provider while the widget tree was building.
If you are encountering this error, chances are you tried to modify a provider
in a widget life-cycle, such as but not limited to:
- build
- initState
- dispose
- didUpdateWidget
- didChangeDependencies

Modifying a provider inside those life-cycles is not allowed, as it could
lead to an inconsistent UI state. For example, two widgets could listen to the
same provider, but incorrectly receive different states.

To fix this problem, you have one of two solutions:
- (preferred) Move the logic for modifying your provider outside of a widget
  life-cycle. For example, maybe you could update your provider inside a button's
  onPressed instead.

- Delay your modification, such as by encapsulating the modification
  in a `Future(() {...})`.
  This will perform your update after the widget tree is done building.)

相关代码

问题组件代码(HookConsumerWidget版本)

class CategoryScreen extends HookConsumerWidget {
  const CategoryScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    useEffect(() {
      ref.read(categoryControllerProvider.notifier).fetchCategory();
      return null;
    }, []);

    final categoryList = ref.watch(categoryControllerProvider);

    return Scaffold(
      appBar: AppBar(
        title: const Text('Select Category'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
          children: [
            CategoryGrid(categoryList: categoryList),
            GestureDetector(
              onTap: () {
                Navigator.pop(context);
              },
              child: const BoxContainer(
                height: 40,
                width: 80,
                colorGradient: Palette.gWhite,
                child: Center(child: Text('Back')),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

控制器代码

@Riverpod(keepAlive: true)
class CategoryController extends _$CategoryController {
  @override
  List<CategoryModel> build() {
    return [];
  }

  Future<List<CategoryModel>> fetchCategory() async {
    try {
      final result =
          await ref.read(categoryRepositoryProvider).getCategoryList();

      state = result;

      return result;
    } catch (e) {
      rethrow;
    }
  }
}

旧的initState实现方式

class CategoryScreen extends ConsumerStatefulWidget {
  const CategoryScreen({Key? key}) : super(key: key);

  @override
  ConsumerState<CategoryScreen> createState() => _CategoryScreenState();
}

class _CategoryScreenState extends ConsumerState<CategoryScreen> {
  @override
  void initState() {
    ref.read(categoryControllerProvider.notifier).fetchCategory();
    super.initState();
  }

  // Rest of the code remains the same
}

解决方案

方案1:将数据初始化逻辑移至Notifier内部(推荐)

这种方式利用Riverpod的Provider生命周期自动触发数据加载,完全避开组件构建阶段的状态修改限制,同时用AsyncValue统一管理加载、成功、错误状态,更符合Riverpod的最佳实践。

修改控制器代码:

@Riverpod(keepAlive: true)
class CategoryController extends _$CategoryController {
  @override
  AsyncValue<List<CategoryModel>> build() {
    // 初始化时自动调用数据获取方法
    fetchCategory();
    return const AsyncValue.loading();
  }

  Future<void> fetchCategory() async {
    try {
      final result = await ref.read(categoryRepositoryProvider).getCategoryList();
      state = AsyncValue.data(result);
    } catch (e, stackTrace) {
      state = AsyncValue.error(e, stackTrace);
    }
  }
}

修改组件代码,适配AsyncValue状态:

class CategoryScreen extends HookConsumerWidget {
  const CategoryScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final categoryState = ref.watch(categoryControllerProvider);

    return Scaffold(
      appBar: AppBar(title: const Text('Select Category')),
      body: categoryState.when(
        loading: () => const Center(child: CircularProgressIndicator()),
        error: (error, stack) => Center(child: Text('加载失败:$error')),
        data: (categoryList) => Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: [
              CategoryGrid(categoryList: categoryList),
              GestureDetector(
                onTap: () => Navigator.pop(context),
                child: const BoxContainer(
                  height: 40,
                  width: 80,
                  colorGradient: Palette.gWhite,
                  child: Center(child: Text('Back')),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

方案2:延迟状态修改至组件构建完成后

如果不想修改现有状态结构,可以通过延迟执行的方式,把数据获取逻辑放到组件构建完成后的时机执行:

方式A:使用addPostFrameCallback

useEffect(() {
  WidgetsBinding.instance.addPostFrameCallback((_) {
    ref.read(categoryControllerProvider.notifier).fetchCategory();
  });
  return null;
}, []);

方式B:使用Future包裹

useEffect(() {
  Future(() {
    ref.read(categoryControllerProvider.notifier).fetchCategory();
  });
  return null;
}, []);

原因说明

initState和空依赖的useEffect都会在组件构建阶段执行,此时调用fetchCategory会同步修改Provider的state,违反了Riverpod的规则——禁止在组件生命周期(build/initState等)中直接修改Provider状态,否则可能导致UI状态不一致。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 14:33:18