满足条件后以编程方式关闭flutter中的showModalBottomSheet-在构建过程中调用setState()或markNeedsBuild()

时间:2020-07-13 16:36:52

标签: flutter dart mobx flutter-showmodalbottomsheet

在代码中将布尔条件验证为true时,我想关闭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代码,您可以在 Observer builder 方法中找到Navigator.pop(context, true)调用:

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),
                    // )
                  ],
                ),
              ),
            );
          }),
        );
      },
    );
  }

我已经检查了其他一些类似的问题及其解决方案,例如this one,但没有上面提到的例外,我无法使它工作。

那么,如何在不引发异常的情况下实现预期的行为(在计算值评估为true时关闭模式)?

PS:如果我移动逻辑以将showModalBottomSheet关闭到FlatButton onPressed 回调中,它不会引发异常,但是它允许插入一个除了所需数量之外的其他小部件,因为只有在下一次状态更新时才会检查它是否已满(这是我想),这就是为什么我要在 builder 方法中插入验证, return之前,但又在调试控制台中收到异常。

PS2:如果我做某事被认为是不好的做法,请也告诉我。

1 个答案:

答案 0 :(得分:1)

构建小部件时,您不应该调用Navigator.pop(context)。您可以在构建完成后使用以下代码行执行它:

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