0

我想showModalBottomSheet在代码中验证布尔条件为真时关闭 a 。但是,在 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:如果我将逻辑移到它showModalBottomSheetonPressed回调附近,FlatButton它不会引发异常,但是,它允许插入一个超出所需数量的额外小部件,因为它是否已满的检查只会在下一个状态更新(我猜),这就是为什么我在builder方法中插入验证,在它之前return,但反过来,在调试控制台中接收异常。

PS2:如果我正在做一些被认为是不好的做法,请告诉我。

4

1 回答 1

1

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

WidgetsBinding.instance.addPostFrameCallback((_) => Navigator.pop(context));
于 2020-07-13T17:00:35.660 回答