"setState() or markNeedsBuild() called during build" 尝试在消费者小部件(提供程序包)内的导航器中推送替换时出错

"setState() or markNeedsBuild() called during build" error trying to push a replacement in Navigator inside a Consumer widget (provider package)

这周我开始在flutter中开发,我无法解决这个问题。

我正在构建一个登录页面,该页面调用 API 进行登录,然后重定向到主页。 这是第一个代码块中 Navigator.pushReplacement 产生的异常。 在那一刻 apiCall.isFetching 是错误的,因为获取结束并且 apiCall.response 包含所需的数据。

异常详情:

════════ Exception caught by widgets library ═══════════════════════════════════════════════════════
The following assertion was thrown building Consumer<ApiCallChangeNotifier>(dirty, dependencies: [InheritedProvider<ApiCallChangeNotifier>]):
setState() or markNeedsBuild() called during build.

This Overlay 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: Overlay-[LabeledGlobalKey<OverlayState>#4dc85]
  state: OverlayState#bd97e(tickers: tracking 1 ticker, entries: [OverlayEntry#2941b(opaque: false; maintainState: false), OverlayEntry#37814(opaque: false; maintainState: true), OverlayEntry#f92c0(opaque: false; maintainState: false), OverlayEntry#da26d(opaque: false; maintainState: true)])
The widget which was currently being built when the offending call was made was: Consumer<ApiCallChangeNotifier>
  dirty
  dependencies: [InheritedProvider<ApiCallChangeNotifier>]
User-created ancestor of the error-causing widget was: 
  Expanded file:///C:/flutter_test/lib/screens/login/LoginScreen.dart:153:37
When the exception was thrown, this was the stack: 
#0      Element.markNeedsBuild.<anonymous closure> (package:flutter/src/widgets/framework.dart:3687:11)
#1      Element.markNeedsBuild (package:flutter/src/widgets/framework.dart:3702:6)
#2      State.setState (package:flutter/src/widgets/framework.dart:1161:14)
#3      OverlayState.insertAll (package:flutter/src/widgets/overlay.dart:346:5)
#4      OverlayRoute.install (package:flutter/src/widgets/routes.dart:43:24)
...

这是我创建登录按钮的函数,它是从 LoginScreen (StatelessWidget)

build 函数调用的
Widget loginButton(BuildContext context) {
    return Consumer<ApiCallChangeNotifier>(
        builder: (context, apiCall, child) => apiCall.isFetching
            ? CircularProgressIndicator()
            : apiCall.response != null
                ? Navigator.pushReplacement(
                    context,
                    MaterialPageRoute(
                        builder: (context) => HomeScreen(
                            (apiCall.response as LoginResponse).email)))
                : RaisedButton( 
                     ...
                     onPressed: () {
                         attemptLogin(context);
                     },
                     ...
                  ));
  }

尝试登录函数:

void attemptLogin(BuildContext context) {
    Provider.of<ApiCallChangeNotifier>(context, listen: false).callApi(
        MyApiServices().attemptLogin,
        {
          'email': emailController.value.text,
          'password': passwordController.value.text,
        },
        urlController.value.text
    );
  }

ApiCallChangeNotifier

class ApiCallChangeNotifier extends ChangeNotifier {
  bool isFetching = false;
  Object response;

  Future<LoginResponse> callApi(apiFunction, bodyParams, customUrl) async {
    isFetching = true;
    notifyListeners();

    response = await apiFunction(bodyParams, customUrl);

    isFetching = false;
    notifyListeners();
    return response;
  }
}

MyApiServices.attemptLogin 是一个处理 API 调用的函数,returns 是一个对象 LoginResponse

希望我提供了足够的信息!

我没有尝试从 LoginResponse Consumer 推送新路线,而是修改了 attemptLogin() 以等待结果并导航到新路线!

void attemptLogin(BuildContext context) async {
    LoginResponse _apiResponse =
        await Provider.of<ApiCallChangeNotifier>(context, listen: false)
            .callApi(
                MyApiServices().attemptLogin,
                {
                  'email': emailController.value.text,
                  'password': passwordController.value.text,
                },
                urlController.value.text);

    if (_apiResponse != null) {
      if (_apiResponse.email != null) {
        Navigator.pushReplacement(
            context,
            MaterialPageRoute(
                builder: (context) => HomeScreen(_apiResponse.email)));
      } else if (_apiResponse.errorMessage != null) {
        Scaffold.of(context)
            .showSnackBar(SnackBar(content: Text(_apiResponse.errorMessage)));
      } else {
        Scaffold.of(context).showSnackBar(
            SnackBar(content: Text(KanbanBOXApi().unknownErrorMessage)));
      }
    }
  }

对我来说,是在构建完成之前使用导航器的时候! 只需将您的导航代码放在这里:

WidgetsBinding.instance.addPostFrameCallback((_) {
  // Do everything you want here...
});