#dart #flutter #navigator
#dart #трепетание #навигатор
Вопрос:
При появлении экрана, переходящего к другому, нажав на showBottomSheet, эта ошибка выдается с помощью следующего кода . Я не могу понять, почему это происходит.
class _CheckoutButtonState extends State<_CheckoutButton> {
final GlobalKey<ScaffoldState> _globalKey = GlobalKey();
final DateTime deliveryTime = DateTime.now().add(Duration(minutes: 30));
final double deliveryPrice = 5.00;
@override
Widget build(BuildContext context) {
SubscriptionService subscriptionService =
Provider.of<SubscriptionService>(context);
CheckoutService checkoutService = Provider.of<CheckoutService>(context);
return Container(
height: 48.0,
width: MediaQuery.of(context).size.width * 0.75,
child: StreamBuilder(
stream: subscriptionService.subscription$,
builder: (_, AsyncSnapshot<Subscription> snapshot) {
if (!snapshot.hasData) {
return Text("CHECKOUT");
}
final Subscription subscription = snapshot.data;
final List<Order> orders = subscription.orders;
final Package package = subscription.package;
num discount = _getDiscount(package);
num price = _totalPriceOf(orders, discount);
return StreamBuilder<bool>(
stream: checkoutService.loading$,
initialData: false,
builder: (context, snapshot) {
bool loading = snapshot.data;
return ExtendedFloatingActionButton(
loading: loading,
disabled: loading,
action: () async {
checkoutService.setLoadingStatus(true);
final subscription =
await Provider.of<SubscriptionService>(context)
.subscription$
.first;
try {
await CloudFunctions.instance.call(
functionName: 'createSubscription',
parameters: subscription.toJSON);
final bottomSheet =
_globalKey.currentState.showBottomSheet(
(context) {
return Container(
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Theme.of(context).scaffoldBackgroundColor,
Theme.of(context).primaryColor,
Theme.of(context).primaryColor,
],
stops: [-1.0, 0.5, 1.0],
),
),
child: Column(
children: <Widget>[
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Padding(
padding:
const EdgeInsets.only(bottom: 16.0),
child: Text(
"Thank you for your order",
textAlign: TextAlign.center,
style: Theme.of(context)
.textTheme
.display1,
),
),
SvgPicture.asset(
'assets/images/thumb.svg',
height: 120.0,
width: 100.0,
)
// CircleAvatar(
// radius: 40.0,
// backgroundColor: Colors.transparent,
// child: Icon(
// Icons.check,
// color: Theme.of(context)
// .textTheme
// .display1
// .color,
// size: 80.0,
// ),
// ),
],
),
),
Container(
width:
MediaQuery.of(context).size.width * 0.9,
height: 72.0,
padding: EdgeInsets.only(bottom: 24),
child: ExtendedFloatingActionButton(
text: "ORDER DETAILS",
action: () {
Navigator.of(context).pop();
},
),
),
],
),
);
},
);
bottomSheet.closed.then((v) {
Navigator.of(context)
.popUntil((r) => r.settings.isInitialRoute);
});
} catch (e) {
print(e);
final snackBar =
SnackBar(content: Text('Something went wrong!'));
Scaffold.of(context).showSnackBar(snackBar);
}
},
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
"CHECKOUT ",
style: Theme.of(context)
.textTheme
.display4
.copyWith(color: Colors.white),
),
Text(
"EGP "
(price (orders.length * deliveryPrice))
.toStringAsFixed(2),
style: Theme.of(context)
.textTheme
.display4
.copyWith(color: Theme.of(context).primaryColor),
),
],
),
);
});
},
),
);
}
num _totalPriceOf(List<Order> orders, num discount) {
num price = 0;
orders.forEach((Order order) {
List<Product> products = order.products;
products.forEach((Product product) {
price = price product.price;
});
});
num priceAfterDiscount = price * (1 - (discount / 100));
return priceAfterDiscount;
}
num _getDiscount(Package package) {
if (package == null) {
return 0;
} else {
return package.discount;
}
}
}
Ошибка :
>══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
I/flutter (24830): The following assertion was thrown building Navigator-[GlobalObjectKey<NavigatorState>
I/flutter (24830): _WidgetsAppState#90d1f](dirty, state: NavigatorState#6b2b6(tickers: tracking 1 ticker)):
I/flutter (24830): 'package:flutter/src/widgets/navigator.dart': Failed assertion: line 1995 pos 12: '!_debugLocked':
I/flutter (24830): is not true.
I/flutter (24830): Either the assertion indicates an error in the framework itself, or we should provide substantially
I/flutter (24830): more information in this error message to help you determine and fix the underlying cause.
I/flutter (24830): In either case, please report this assertion by filing a bug on GitHub:
I/flutter (24830): https://github.com/flutter/flutter/issues/new?template=BUG.md
I/flutter (24830): When the exception was thrown, this was the stack:
Ответ №1:
Вместо того, чтобы дать вам прямой ответ, я собираюсь рассказать вам, как я думал об этом, когда увидел вопрос, в надежде, что это поможет вам в будущем.
Давайте посмотрим на утверждение. В нем говорится Failed assertion: line 1995 pos 12: '!_debugLocked': I/flutter (24830): is not true.
. Хм, интересно. Давайте взглянем на эту строку кода.
assert(!_debugLocked);
Что ж, это не дает мне гораздо больше информации, давайте посмотрим на переменную.
bool _debugLocked = false; // used to prevent re-entrant calls to push, pop, and friends
Так лучше. Он предназначен для предотвращения повторных вызовов push, pop и т. Д. (Под этим это означает, что он не хочет, чтобы вы вызывали «push», «pop» И т. Д. Из вызова «push», «pop»). Итак, давайте проследим это до вашего кода.
Это похоже на вероятного виновника:
bottomSheet.closed.then((v) {
Navigator.of(context)
.popUntil((r) => r.settings.isInitialRoute);
});
Я собираюсь пропустить здесь шаг и вместо этого использовать дедуктивные рассуждения — держу пари, что закрытое будущее завершено во pop
время . Продолжайте и подтвердите это, прочитав код, если вам так хочется.
Итак, если проблема в том, что мы вызываем pop из функции pop, нам нужно найти способ отложить вызов pop до завершения pop.
Это становится довольно простым — есть два способа сделать это. Простой способ — просто использовать отложенное будущее с нулевой задержкой, при котором dart запланирует вызов как можно скорее, как только текущий стек вызовов вернется в цикл событий:
Future.delayed(Duration.zero, () {
Navigator. ...
});
Другим, более простым способом сделать это было бы использовать планировщик для планирования вызова после завершения текущего цикла сборки / рендеринга:
SchedulerBinding.instance.addPostFrameCallback((_) {
Navigator. ...
});
В любом случае это должно устранить проблему, с которой вы столкнулись.
Возможен и другой вариант — в вашем ExtendedFloatingActionButton, где вы вызываете pop:
ExtendedFloatingActionButton(
text: "ORDER DETAILS",
action: () {
Navigator.of(context).pop();
},
),
вместо этого вы могли бы просто выполнить вызов Navigator.of(context).popUntil...
. Это избавило бы от необходимости делать что-либо после вызова BottomSheet.closed . Однако, в зависимости от того, что еще вам может понадобиться или не понадобиться в вашей логике, это может быть не идеально (я определенно вижу проблему с тем, что нижний лист вызывает изменение основной части страницы, и почему вы пытались сделать это в логике страницы).
Кроме того, когда вы пишете свой код, я бы настоятельно рекомендовал разделить его на виджеты — например, нижний лист должен быть его собственным виджетом. Чем больше у вас функций сборки, тем сложнее их отслеживать, и это может также повлиять на производительность. Вам также следует избегать использования экземпляров GlobalKey везде, где это возможно — обычно вы можете либо передавать объекты (или обратные вызовы) вниз, если это только через несколько слоев, использовать шаблон .of (context) или использовать унаследованные виджеты.
Комментарии:
1. Да, очень хороший ответ, который больше о том, как решить проблему, чем просто решение. Это определенно помогло мне найти область, которую мне нужно было поместить в SchedulerBinding() . Спасибо!
2. Впечатляет! Очень подробный и последовательный способ обучения вместо того, чтобы просто давать вам конфеты 🙂 Спасибо @rmtmckenzie
3. Единственное, что сработало для меня, это использование
SchedulerBinding
4. немного не по теме, но это может кому-то помочь, моя проблема заключалась в том, что я забыл добавить
WidgetsBinding.instance.removeObserver(this); Constants.routeObserver.unsubscribe(this);
dispose()
метод в виджете, который я перестраивал с помощью совершенно другого ключа, каким-то образом это заблокировало класс navigator, и я получил это сообщение, когда попытался перейти после перестройки с помощьюsetState
5. Отлично сделано! Хорошее объяснение, ведущее к решению, а не только к решению, как в других ответах. Мне нравятся ваши дедуктивные рассуждения.
Ответ №2:
Для тех, кто вызывает Navigator
как часть процесса сборки. Я обнаружил, что он будет периодически выдавать ошибку утверждения на debugLocked
Я избежал этой проблемы, обернув addPostFrameCallback
:
WidgetsBinding.instance.addPostFrameCallback((_) {
Navigator.pushReplacement(context, MaterialPageRoute(builder: (_) => MyPage()));
});
Комментарии:
1. Я могу гарантировать, что, будучи новичком в flutter, я понятия не имею, куда это поместить.
2. После реализации этого с помощью Navigator.pushReplacementNamed он не переходит к следующему маршруту. Теперь даже не отображается эта предыдущая ошибка. Но когда я нажимаю эту кнопку, чтобы повторить процесс навигации, я получаю ошибку, как указано в этом вопросе . Пожалуйста, помогите. Я застрял на этой вещи с 2 дней.
3. @CristianG просто завершите свое
Navigator
утверждениеaddPostFrameCallback
символом if, если вы вызываете Navigator вbuild
процессе. В противном случае вам не нужно4. @ChetanGoyal вам не нужно использовать это решение, если оно не является частью процесса сборки. Вы вызываете его при нажатии кнопки, не так ли?
5. @sk для лучшего понимания позвольте мне дать подробный ответ. Я сделал onPressed асинхронным и использую await для получения некоторых данных из firestore. Когда данные загружены, затем на основе полученных данных я загружаю еще некоторые данные из firestore с помощью await . а затем, после проверки окончательных полученных данных, я вызываю навигатор. Я пробовал как метод задержки, так и метод обратного вызова, они не сработали. Я попытался создать новое логическое значение и использовать setState после окончательной проверки данных и использовать Navigator поверх сборки на основе этого логического значения. Но все эти обходные пути не сработали. 🙁
Ответ №3:
У меня была такая же проблема, любой ответ не сработал для меня, и эта ошибка ничего не объясняет.
Пройдя каждую строку кода, я обнаружил, что мы не можем запустить какой state
-либо метод сборки, подобный этому
@override
Widget build(BuildContext context) {
var viewmodel = Provider.of<ViewModel>(context);
Navigator.of(context).push(MaterialPageRoute(builder:
(context)=>CreateItemPage(viewmodel.catalogData))); // this is way i was getting error.
return Scaffold();
}
Я получал сообщение об ошибке на CreateItemPage
экране из-за этой строки.
Решение этой проблемы создает button
, вызывающую эту строку Navigator.of(context).push(MaterialPageRoute(builder: (context)=>CreateItemPage(viewmodel.catalogData)));
Комментарии:
1. Я нахожусь в ситуации, когда я хотел бы перемещаться автоматически, если применяются некоторые условия. Так что кнопка в моем случае не подходит.
2. @CsabaToth Есть два пути. 1-й возьмите общий экран контейнера и ваше состояние в качестве виджета, а 2-й используйте
ChangeNotifier
.
Ответ №4:
Для меня это происходило потому, что я создал цикл нажатий, который вызывал эту ошибку. Например,
В начальном маршруте, который был /loading
кодом, нажимался /home
class _LoadingState extends State<Loading> {
void getTime() async {
// DO SOME STUFF HERE
Navigator.pushNamed(context, '/home');
}
@override
void initState() {
super.initState();
getTime();
}
И в /home
initState я нажимал /loading
на создание цикла.
class _HomeState extends State<Home> {
@override
void initState() {
super.initState();
Navigator.pushNamed(context, '/loading');
}
Ответ №5:
Добавьте некоторую задержку, затем попробуйте сделать это, и ваша проблема будет решена :
Future.delayed(const Duration(milliseconds: 500), () {
// Здесь вы можете написать свой код
setState(() {
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(builder: (context) => SetCategory()),
(route) => false);
});
});
Ответ №6:
У меня была похожая ошибка, например, диалоговое окно, в котором была кнопка выхода из системы, которая при нажатии переходит на экран входа в систему, но возникает _debugLocked
ошибка, поэтому я использовал
Navigator.of(context).pushNamedAndRemoveUntil('/screen4', (Route<dynamic> route) => false);
Это удаляет все маршруты в стеке, чтобы пользователь не мог вернуться к предыдущим маршрутам после выхода из системы.
Настройка (Route<dynamic> route) => false
гарантирует, что все маршруты перед отправленным маршрутом будут удалены.
Я не знаю, является ли это «реальным» решением, но оно помогло мне, как новичку, развеяться.
Комментарии:
1. Это то, что сработало быстро для меня, спасибо. Я не мог понять проблему с управлением экранами навигатора. Но позже я сделаю простой проект, чтобы лучше проанализировать и понять Navigator.
2. Я все еще получаю ту же проблему после использования этого
Ответ №7:
Я получил эту ошибку из-за опечатки, случайно вызванной Navigator.of(context).push
во время моего build()
: E/flutter ( 6954): [ERROR:flutter/lib/ui/ui_dart_state.cc(198)] Unhandled Exception: 'package:flutter/src/widgets/navigator.dart': Failed assertion: line 2845 pos 18: '!navigator._debugLocked': is not true.
Симулятор выдал более информативную ошибку:
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>#de69b]
The widget which was currently being built when the offending call was made was:
FutureBuilder
В принципе, вы не должны пытаться перейти / перейти на новый маршрут в середине build
.Если вам действительно нужно, дождитесь завершения сборки, именно поэтому другие предлагают обернуть ее в a SchedulerBinding.instance.addPostFrameCallback
для выполнения после того, как все будет отрисовано, но вам, вероятно, следует найти лучший способ сделать это за пределами a build
.
В моем случае я набрал: onTap: _onTap(context),
когда я действительно хотел ввести: onTap: () => _onTap(context),
мой _onTap
обработчик выполнял навигатор push
. Я забыл обернуть свой обработчик в замыкание, которое фиксирует context
то, что ему нужно, и вместо этого фактически выполнял его вместо передачи onTap:
моего обратного вызова.
Ответ №8:
Диалоговое решение
Для тех, кто сталкивается с этим при вызове Navigator.push(..)
из a Dialog
.
Сначала вам нужно сделать Navigator.pop(context);
, чтобы программно закрыть modal, а затем вызвать Navigator.push(..)
.
Ответ №9:
Для людей, сталкивающихся с этой проблемой при использовании блока, убедитесь, что вы используете навигацию в BlocListener (или прослушивателе BlocConsumer). В моем случае я использовал Navigator внутри blockbuilder. Я новичок в Flutter / Bloc, и принятый ответ решил проблему, но не был правильным решением. Переключение моего blockbuilder на blockconsumer позволило мне перемещаться во время определенных состояний.
Пример использования BlocConsumer, навигация при состоянии «LoginSuccess»:
BlocConsumer<LoginBloc, LoginState>(
listener: (BuildContext context, state) {
if (state is LoginSuccess) {
Navigator.of(context).pushReplacement(
// Add your route here
PageRouteBuilder(
pageBuilder: (_, __, ___) => BlocProvider.value(
value: BlocProvider.of<NavigationBloc>(context),
child: HomeScreen(),
),
),
);
}
},
// Only build when the state is not LoginSuccess
buildWhen: (previousState, state) {
return state is! LoginSuccess;
},
// Handle all states other than LoginSuccess here
builder: (BuildContext context, LoginState state) {
if (state is LoginLoading) {
return Center(child: CircularProgressIndicator());
} else .....
Комментарии:
1. Привет, Сэм, большое спасибо. Это действительно сработало!
Ответ №10:
В resume вам просто нужно удалить его из вашего initState. Я бы рекомендовал расширить класс с помощью afterLayout, а внутри afterFirstLayout вы можете перенаправить его на нужную страницу. Это гарантирует, что все в порядке перед маршрутизацией.
Смотрите ниже шаги: Добавить в pubspec: after_layout: ^1.0.7 2
Затем вы расширите его до класса, который хотите использовать. В моем случае это был виджет с полным состоянием с именем HomePage . Таким образом, это будет выглядеть так:
class HomePage extends StatefulWidget {
@override
HomePageState createState() => HomePageState();
} //no changes here
class HomePageState extends State<HomePage> with AfterLayoutMixin<HomePage> {
//the with AfterLayoutMixin<pageName> is the only thing you need to change.
Теперь вам нужно реализовать метод с именем afterlayout, который будет выполнен после завершения сборки.
@override
Future<void> afterFirstLayout(BuildContext context) {
//your code here safetly
}
Вы можете найти информацию здесь:
https://pub.dev/packages/after_layout
Ответ №11:
Для тех, у кого все еще есть такая же проблема, это поможет мне решить ее.
navigationService.popUntil((_) => true);
navigationService.navigateTo(
'authentication',
);
в основном я жду, пока навигация не завершит настройку всего, а затем вызовет NavigateTo.
Ответ №12:
Я получил эту ошибку, потому что мой initialRoute
был /login
. Тем не менее, initialRoute
это должно быть /
.
Если имя маршрута начинается с косой черты, то оно обрабатывается как «глубокая ссылка», и перед тем, как этот маршрут будет отправлен, также будут отправлены маршруты, ведущие к этому. Например, если маршрут был /a/b / c , то приложение запустится с четырьмя загруженными маршрутами /, /a, /a/b и /a/b / c в таком порядке.
Вот ссылка на документы для справки.
Ответ №13:
У меня была такая же проблема, и мне потребовалось некоторое время, чтобы разобраться. Я прослушивал состояние на экране, на основе которого он будет переходить на другой экран. А затем при нажатии кнопки я менял это состояние и переходил на другой экран, что вызывало проблему.
Ответ №14:
Я использую версию flutter 2.3.3
, я также столкнулся с этой проблемой, когда попытался вернуться на свой домашний экран со второго экрана с помощью команды Navigator.pop(context)
. Я решил эту проблему, заменив эту строку кода на Navigator.of(context).pop(context)
Она работала нормально для меня, надеюсь, это поможет