Программно закрыть showModalBottomSheet во флаттере после выполнения условия - setState () или markNeedsBuild () вызывается во время сборки - PullRequest
0 голосов
/ 13 июля 2020

Я хотел бы закрыть showModalBottomSheet, когда логическое условие проверяется как истинное в коде. Тем не менее, предполагаемое поведение работает, на консоли отладки VSCode я вижу, что генерируется исключение, и я боюсь, что это может привести к определенным ошибкам, когда оно будет развернуто в производственной среде позже. Исключением является следующее:

════════ Exception caught by animation library ═════════════════════════════════
The following assertion was thrown while notifying status listeners for AnimationController:
setState() or markNeedsBuild() called during build.

This _ModalScope<void> 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: _ModalScope<void>-[LabeledGlobalKey<_ModalScopeState<void>>#8025f]
    state: _ModalScopeState<void>#76437
The widget which was currently being built when the offending call was made was: Observer
    dirty
When the exception was thrown, this was the stack
#0      Element.markNeedsBuild.<anonymous closure> 
package:flutter/…/widgets/framework.dart:4167
#1      Element.markNeedsBuild 
package:flutter/…/widgets/framework.dart:4182
#2      State.setState 
package:flutter/…/widgets/framework.dart:1253
#3      _ModalScopeState._routeSetState 
package:flutter/…/widgets/routes.dart:759
#4      ModalRoute.setState 
package:flutter/…/widgets/routes.dart:878
...
The AnimationController notifying status listeners was: AnimationController#6cac6(◀ 1.000; for BottomSheet)

Я использую Mobx в качестве инструмента управления состоянием, и когда вычисленное значение становится true, я хочу, чтобы showModalBottomSheet закрылся .

Код showModalBottomSheet показан ниже, где вы можете найти вызов Navigator.pop(context, true) в методе builder объекта Observer :

void _addGroupBottomSheet(BuildContext context) {
    Size screenSize = MediaQuery.of(context).size;

    showModalBottomSheet<void>(
      // enableDrag: true,
      elevation: 5,
      isScrollControlled:
          true, // make it bigger, being able to fill the whole screen
      shape: RoundedRectangleBorder(
        borderRadius: BorderRadius.only(
          topLeft: Radius.circular(30),
          topRight: Radius.circular(30),
        ),
      ),
      // backgroundColor: Theme.of(context).primaryColor,
      backgroundColor: Colors.amber,
      context: context,
      builder: (BuildContext context) {
        final store = Provider.of<GroupStore>(context, listen: false);

        return GestureDetector(
          onTap: () {
            FocusScopeNode currentFocus = FocusScope.of(context);

            if (!currentFocus.hasPrimaryFocus) {
              currentFocus.unfocus();
            }
          },
          child: Observer(builder: (_) {

            List<Group> allGroups = store.listOfAllGroupsSelected;

            bool isGroupFull = widget.isGroupA
                ? store.isGroupAFull
                : store.isGroupBFull;

            // close bottomSheet programmatically when condition satisfies
            if (isGroupFull) {
              Navigator.pop(context, true);
            }

            return Padding(
              padding: const EdgeInsets.only(bottom: 12.0),
              child: Container(
                height: screenSize.height * 0.8,
                // color: Colors.amber,

                child: Column(
                  // mainAxisAlignment: MainAxisAlignment.center,
                  mainAxisSize: MainAxisSize.min,
                  children: <Widget>[
                    Text(
                      'Looking at ${widget.isGroupA ? 'group A' : 'group B'}',
                      style: TextStyle(
                        fontSize: 20,
                      ),
                    ),
                    Expanded(
                      child: ListView.separated(
                        itemCount: allGroups.length,
                        physics: const BouncingScrollPhysics(),
                        separatorBuilder: (BuildContext context, int index) =>
                            Divider(),
                        itemBuilder: (BuildContext context, int index) {
                          return ListTile(
                            key: Key(index.toString()),
                            // dense: true,
                            title: Text('${allGroups[index].name}'),
                            leading: ContainerAvatar(
                              url: '${allGroups[index].imageUrl}',
                            ),
                            trailing: allGroups[index].isSelected 
                                ? null
                                : FlatButton(
                                    shape: RoundedRectangleBorder(
                                      borderRadius: BorderRadius.circular(18.0),
                                      side: BorderSide(
                                          color: Theme.of(context).accentColor),
                                    ),
                                    onPressed: () {

                                      FocusScopeNode currentFocus =
                                          FocusScope.of(context);
                                      if (!currentFocus.hasPrimaryFocus) {
                                        currentFocus.unfocus();
                                      }

                                      if (widget.isGroupA) {
                                        if (allGroups[index].isSelected) {
                                          store.modifyEnemyTeamList(
                                            name: allGroups[index].name,
                                            operation: 'Remove',
                                          );
                                        } else {
                                          // add to the enemy team list
                                          store.modifyEnemyTeamList(
                                            name: allGroups[index].name,
                                            operation: 'Add',
                                          );
                                        }
                                      } 
                                    },
                                    child: Text(
                                      '${allGroups[index].isSelected ? 'Remove' : 'Add'}',
                                    ),
                                  ),
                          );
                        },
                      ),
                    ),
                    // RaisedButton(
                    //   child: const Text('Close BottomSheet'),
                    //   onPressed: () => Navigator.pop(context),
                    // )
                  ],
                ),
              ),
            );
          }),
        );
      },
    );
  }

Я уже проверил некоторые другие похожие вопросы и их решения, такие как этот , но я не мог заставить это работать без упомянутого выше исключения.

Итак, как Могу ли я добиться желаемого поведения (закрыть модальное окно, когда вычисленное значение будет истинным), не вызывая исключения?

PS: Если я перемещу logi c, чтобы закрыть showModalBottomSheet на onPressed обратный вызов FlatButton он не генерирует исключение, хотя он позволяет вставлять один дополнительный виджет сверх желаемого числа, поскольку проверка, заполнен он или нет, будет выполняться только в следующем обновлении состояния ( Наверное), поэтому и вставляю th Проверка выполняется в методе builder до его return, но, в свою очередь, получение исключения в консоли отладки.

PS2: Если я делаю что-то, считается плохой практикой пожалуйста, дайте мне знать.

1 Ответ

1 голос
/ 13 июля 2020

Вы не должны звонить Navigator.pop(context) при создании виджета. Вы можете выполнить его после завершения сборки с помощью этой строки кода:

WidgetsBinding.instance.addPostFrameCallback((_) => Navigator.pop(context));
...