Программно закрыть showModalBottomSheet в flutter после выполнения условия - setState() или markNeedsBuild() вызывается во время сборки
Я бы хотел закрыть 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)
позвонить в строителе способе наблюдателя:
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: Если я закрою логику showModalBottomSheet
к обратному вызову onPressed дляFlatButton
он не генерирует исключение, однако он позволяет вставить один дополнительный виджет сверх желаемого числа, поскольку проверка, заполнен он или нет, будет выполняться только в следующем обновлении состояния (я думаю), поэтому я вставка проверки в метод построителя перед ееreturn
, но, в свою очередь, получение исключения в консоли отладки.
PS2: Если я делаю что-то плохое, пожалуйста, дайте мне знать.
1 ответ
Вы не должны звонить Navigator.pop(context)
при создании виджета. Вы можете выполнить его после завершения сборки с помощью этой строки кода:
WidgetsBinding.instance.addPostFrameCallback((_) => Navigator.pop(context));