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

Flutter:FutureBuilder内调用Provider方法触发构建时错误的解决咨询

问题描述

使用Flutter 3.19.6 + Dart 3.3.4结合Provider开发时遇到如下错误:

main.dart 布局代码

Widget build(BuildContext context) {
  return Scaffold(
    body: Stack(
      children: [
        MyWiget(),
        Consumer<LoaderProvider>(
          builder: (context, loader, child) {
            return Positioned.fill(
              child: loader.isLoading ? const OverlayLoadingView() : const SizedBox(),
            );
          },
        ),
        Consumer<NotificationProvider>(
          builder: (context, notification, child) {
            if (notification.message.isNotEmpty) {
              WidgetsBinding.instance.addPostFrameCallback((_) {
                ScaffoldMessenger.of(context)
                    .showSnackBar(
                      SnackBar(content: Text(notification.message)),
                    )
                    .closed
                    .then(
                      (_) => notification.remove(),
                    );
              });
            }
            return const SizedBox();
          },
        ),
      ],
    ),
  );
}

MyWidget 代码

class MyWidget extends StatelessWidget {
  const MyWidget({super.key});

  @override
  Widget build(BuildContext context) {
    return Consumer3<HttpProvider, LoaderProvider, NotificationProvider>(
      builder: (context, http, loader, notification, child) {
        return FutureBuilder(
          future: http.get('...'),
          builder: (context, snapshot) {
            loader.show();

            if (snapshot.hasError) {
              notification.addException(snapshot.error!);
            }

            return Container();
          },
        );
      },
    );
  }
}

触发的错误信息

══╡ EXCEPTION CAUGHT BY FOUNDATION LIBRARY ╞═════════════════════
The following assertion was thrown while dispatching
notifications for LoaderProvider:
setState() or markNeedsBuild() called during build.
This _InheritedProviderScope<LoaderProvider?> 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<LoaderProvider?>
The widget which was currently being built when the offending
call was made was:
FutureBuilder

同时存在疑问:LoaderProvider和NotificationProvider是否应在Consumer中隔离,以避免MyWidget重复渲染?

解决方案

1. 解决build期间调用Provider状态变更的错误

错误核心是在FutureBuilder的builder方法(属于Widget构建流程)中直接调用loader.show()和notification.addException(),这两个方法会触发Provider的notifyListeners(),而此时框架正处于Widget构建阶段,不允许标记组件需要重新构建。

修正方式是将状态变更逻辑延迟到构建流程完成后执行,使用WidgetsBinding.instance.addPostFrameCallback,同时添加判断避免重复触发:

修改后的MyWidget代码

class MyWidget extends StatelessWidget {
  const MyWidget({super.key});

  @override
  Widget build(BuildContext context) {
    return Consumer3<HttpProvider, LoaderProvider, NotificationProvider>(
      builder: (context, http, loader, notification, child) {
        return FutureBuilder(
          future: http.get('...'),
          builder: (context, snapshot) {
            // 仅在Future处于等待状态且未显示加载时触发
            if (snapshot.connectionState == ConnectionState.waiting && !loader.isLoading) {
              WidgetsBinding.instance.addPostFrameCallback((_) {
                loader.show();
              });
            }

            // 仅在有错误且通知未存在时触发
            if (snapshot.hasError && notification.message.isEmpty) {
              WidgetsBinding.instance.addPostFrameCallback((_) {
                notification.addException(snapshot.error!);
              });
            }

            // Future完成后隐藏加载
            if (snapshot.connectionState == ConnectionState.done && loader.isLoading) {
              WidgetsBinding.instance.addPostFrameCallback((_) {
                loader.hide();
              });
            }

            return Container();
          },
        );
      },
    );
  }
}

2. 关于Provider隔离与MyWidget重复渲染的解答

LoaderProvider和NotificationProvider不需要强制隔离,但可以通过优化Consumer的使用减少不必要的重渲染:

  • 拆分Consumer监听:不要用Consumer3同时监听三个Provider,改为仅监听HttpProvider,Loader和Notification通过Provider.of<T>(context, listen: false)获取实例(不监听状态变化):
class MyWidget extends StatelessWidget {
  const MyWidget({super.key});

  @override
  Widget build(BuildContext context) {
    // 仅监听HttpProvider,只有Http状态变化时才重建MyWidget核心部分
    return Consumer<HttpProvider>(
      builder: (context, http, child) {
        return FutureBuilder(
          future: http.get('...'),
          builder: (context, snapshot) {
            // 获取不监听状态的Provider实例
            final loader = Provider.of<LoaderProvider>(context, listen: false);
            final notification = Provider.of<NotificationProvider>(context, listen: false);

            if (snapshot.connectionState == ConnectionState.waiting && !loader.isLoading) {
              WidgetsBinding.instance.addPostFrameCallback((_) => loader.show());
            }

            if (snapshot.hasError && notification.message.isEmpty) {
              WidgetsBinding.instance.addPostFrameCallback((_) => notification.addException(snapshot.error!));
            }

            if (snapshot.connectionState == ConnectionState.done && loader.isLoading) {
              WidgetsBinding.instance.addPostFrameCallback((_) => loader.hide());
            }

            return Container();
          },
        );
      },
    );
  }
}

这样优化后:

  • 只有HttpProvider状态变化时,MyWidget才会重渲染
  • LoaderProvider和NotificationProvider的状态变化只会触发main.dart中对应的独立Consumer重渲染,不会影响MyWidget的构建

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.24 23:11:07