Ошибка «setState () или markNeedsBuild (), вызванная во время сборки», при попытке вставить замену в Navigator внутри виджета «Потребитель» (пакет поставщика) - PullRequest
1 голос
/ 20 сентября 2019

На этой неделе я начал разрабатывать флаттер и не могу решить эту проблему.

Я создаю страницу входа, которая вызывает 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)
...

Вот моя функция для создания кнопки входа, она называетсяот build функция LoginScreen (StatelessWidget)

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 и возвращает объект LoginResponse

Надеюсь, я предоставил достаточно информации!

1 Ответ

0 голосов
/ 23 сентября 2019

Вместо того, чтобы пытаться выдвинуть новый маршрут из LoginResponse Consumer, я изменил попытку Login (), чтобы дождаться результата и перейти к новому маршруту!

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)));
      }
    }
  }
...