如何解决使用 Provider 时 Flutter 中构建期间调用 setState() 或 markNeedsBuild() 错误?

iam*_*bin 0 mobile-application flutter flutter-provider

在 Flutter 中使用 Provider 时出现以下错误:

The following assertion was thrown while dispatching notifications for CampaignProvider: setState() or markNeedsBuild() called during build. This _InheritedProviderScope<CampaignProvider?> widget cannot be marked as needing to build because the framework is already in the process of building widgets. A widget can be marked as needing to be built during the build phase only if one of its ancestors is currently building. This exception is allowed because the framework builds parent widgets before children, which means a dirty descendant will always be built. Otherwise, the framework might not visit this widget during this build phase. The widget on which setState() or markNeedsBuild() was called was: _InheritedProviderScope<CampaignProvider?> The widget which was currently being built when the offending call was made was: KeyedSubtree-[GlobalKey#a8154]

这是我的小部件:

class CampaignsScreen extends StatefulWidget {
  const CampaignsScreen({Key? key}) : super(key: key);

  @override
  State<CampaignsScreen> createState() => _CampaignsScreenState();
}

class _CampaignsScreenState extends State<CampaignsScreen> {
  @override
  void initState() {
    super.initState();
    context.read<CampaignProvider>().getCampaigns();
  }

  @override
  Widget build(BuildContext context) {
    List<Campaign> campaigns = context.watch<CampaignProvider>().campaigns;

    return Scaffold(
      appBar: AppBar(
        title: const Text('Campagnes'),
      ),
      body: Center(
        child: ListView.builder(
          itemCount: campaigns.length,
          itemBuilder: (BuildContext context, int index) {
            return ListTile(
              title: Text(campaigns[index].title),
              subtitle: Text(campaigns[index].description),
            );
          },
        ),
      ),
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

还有我的提供者:

class CampaignProvider with ChangeNotifier {
  final CampaignService _campaignService = CampaignService();

  List<Campaign> _campaigns = [];

  List<Campaign> get campaigns {
    return _campaigns;
  }

  void getCampaigns() {
    _campaigns = _campaignService.getCampaigns();
    notifyListeners();
  }
}
Run Code Online (Sandbox Code Playgroud)

看来我不能在 initState() 中使用 context.read() 。但是,当小部件正在构建并侦听值更改时,如何从 Provider 类中调用方法 getCampaigns 以便更新我的屏幕?

我尝试用 Consumer 替换 context.watch() 但出现了相同的错误。

die*_*per 5

That's because you're refreshing the widget when this was not built yet.

You'll need to wait until the first frame is loaded, like this:


  @override
  void initState() {
    WidgetsBinding.instance.addPostFrameCallback((_) {
      if (mounted) {
        context.read<CampaignProvider>().getCampaigns();
      }
    });
}
Run Code Online (Sandbox Code Playgroud)

Adding more context, as jraufeisen mentioned, it seems like your code is not using any async method, getCampaigns() is not returning a Future, so in this case you can omit the notifyListeners and it will work.

If your method getCampaigns returns a Future, then notifyListeners will work (or the solution I proposed first).