Aya Elsisy
Aya Elsisy

Reputation: 2449

Error thrown on navigator pop until : "!_debugLocked': is not true."

When popping a screen navigating to other one by clicking on the showBottomSheet, this error is thrown through the following code . I cant get why this is occurring.

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;
    }
  }
}

Error :

>══╡ 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:

Upvotes: 135

Views: 174583

Answers (20)

Csaba Mihaly
Csaba Mihaly

Reputation: 329

I had a lot of issues with PopScope. The .addPostFrameCallback and the other delayed methods didn't work for me.

I'm not sure why this works, but I saw this in another answer and didn't see it here explicitly. Since this is the top answer that comes up in google for the debuglocked error I will leave it here.

PopScope(
      canPop: false,
      onPopInvokedWithResult: (didPop, result) {
        //this empty return is the important part
        if (didPop) {
          return;
        }
        //then do something, like Navigator.pop(context, anyValue); to pass back any value or a contex.go or any other navigation thing that you used before PopScope. 
      },
      child: ///Scaffold or something

It handles the android back button/gesture too now.

Maybe adding android:enableOnBackInvokedCallback="false" to your AndroidManifest.xml is important too.

Upvotes: 1

Tomas Zubrik
Tomas Zubrik

Reputation: 497

Proper solution according to official documentation, when you are trying to customize pop behavior (do extra processing before route pop, return data from pop (resp. push) method, etc.).

https://docs.flutter.dev/release/breaking-changes/popscope-with-result

@override
Widget build(BuildContext context) {
  return PopScope(
     canPop: false, // Indicates that this widget won't handle the pop event automatically
     onPopInvoked: (didPop) { // Callback triggered when a pop event occurs
       if (didPop) { // If the pop event was handled successfully by the system
         return; // Do nothing and return
       } else { // If the pop event wasn't handled by the system
         context.pop({'refresh': true}); // Provide custom behavior: return a value to the previous screen
       }
    },
    child: Scaffold(...)
   }
 )

Cheers!

Upvotes: 0

Sagar Balyan
Sagar Balyan

Reputation: 658

TLDR;

Just Use

Future.delayed(Duration.zero, () {});

So if you are using Navigator or Getx. Just wrap your code in Future.delayed.

Works in 2024!

Upvotes: 0

Shahed Oali Noor
Shahed Oali Noor

Reputation: 656

Use Future.delayed(Duration.zero, () { Navigator. ... });

Upvotes: 0

Mistelo
Mistelo

Reputation: 136

This issue might using PopeScope as Parent Widget

PopScope(
  canPop: false,
  onPopInvoked: (bool didPop) {},
  child: SafeArea(
    child: Scaffold(
      body: GetBuilder<NatoScreenController>(
        id: 'gameStarted',
        builder: (_) => controller.gameStarted
            ? Column(
                children: [
                  const Expanded(flex: 15, child: Center()),
                  Expanded(
                      flex: 85, child: GameBoard(controller: controller)),
                  Expanded(
                      flex: 20, child: ActionBoard(controller: controller))
                ],
              )

Don't use it as a parent widget.

Here I've a home and second page , which I want to navigating from home to second , then pop from second to home with some alert widget. In a second page where you want to customize. Define PopScope as child of Column Like below code , some detail written as command.

Scaffold(
  body: Center(
    child: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        const Text('Page Two'),
        PopScope(
          canPop: false, // turn it to false for custom pop 
          onPopInvoked: (bool didPop) {
            // here is the solution , when u wont to pop you have to use to did pop , so when widget initializing ignored this widget 
            // when u decide to pop or an alert to user , now this widget will work 
            if (didPop) {
              return;
            }
            _showBackDialog(context);
          },
          child: TextButton(
            onPressed: () {
              _showBackDialog(context);
            },
            child: const Text('Go back'),
          ),
        ),
      ],
    ),
  ),
);

Here we Provide an Alert dialog which user decide to dismiss it or closing current page Alert dialog code

AlertDialog(
      title: const Text('Are you sure?'),
      content: const Text(
        'Are you sure you want to leave this page?',
      ),
      actions: <Widget>[
        TextButton(
          style: TextButton.styleFrom(
            textStyle: Theme.of(context).textTheme.labelLarge,
          ),
          child: const Text('Nevermind'),
          onPressed: () {
            Navigator.pop(context);
          },
        ),
        TextButton(
          style: TextButton.styleFrom(
            textStyle: Theme.of(context).textTheme.labelLarge,
          ),
          child: const Text('Leave'),
          onPressed: () {
            // when using getx state management use below code
            Get.back(); // to ensure dialog closed
            Get.until((route) =>
                Get.currentRoute ==
                '/home'); // ensure navigating to specific rotate , mine is home page

            // or using simple navigator use
            Navigator.pop(context); // to ensure dialog closed
            Navigator.pop(context); // getting back to previous page 
          },
        ),
      ],
    );

BINGO there is no issue

Upvotes: 1

Ting Xian Loo
Ting Xian Loo

Reputation: 71

Not sure if anyone else faces this problem. Im using flutter 3.16. previously, I would always use WillPopScope, and onPop show dialog. The issue now is,

PopScope(
      canPop: false,
      onPopInvoked: (pop) async {
        if (!pop) {
          showDialog(
            context: context,
            builder: (context) {
              return ConfirmDiscardDialog(
                popRoute: widget.returnRoute, //Some route for me to popUntil
              );
            },
          );
        }
      },
    
//ConfirmDiscardDialog just a container with buttons to confirm pop, and it 
//pops until the given return route

Apparently, onPopInvoked is called when I use popUntil in the ConfirmDiscardDialog to pop out of the dialog. Which calls onPopInvoked which in turns call another showDialog, IF you dont put "if(!pop)".

Incase, anyone gets the same error, and is using showDialog with Popscope with popUntil. Im using Getx, and Get.back actually is calling popUntil.

Upvotes: 7

Tasnuva Tavasum oshin
Tasnuva Tavasum oshin

Reputation: 4750

Add Some Delay Then Try to do this Your Problem will be Solved :

Future.delayed(const Duration(milliseconds: 500), () {
    setState(() {
      Navigator.of(context).pushAndRemoveUntil(
        MaterialPageRoute(builder: (context) => SetCategory()),
        (route) => false);
    });
  });

Upvotes: 12

Albert George
Albert George

Reputation: 391

I had the same issue and took me some time to figure out. I was listening to the state on the screen based on which it will navigate to different screen. And then on button click I was changing that state and navigating to different screen which was causing an issue.

Upvotes: 0

chujudzvin
chujudzvin

Reputation: 1303

Dialog solution

For those who encounter this when calling Navigator.push(..) from a Dialog.

You need to do Navigator.pop(context);to programmatically close modal first, then call Navigator.push(..).

Upvotes: 2

qix
qix

Reputation: 7902

I've gotten this error due to a typo accidentally calling Navigator.of(context).push during my 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.

The simulator flashed a more informative error:

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

Basically, you should not be trying to push/pop to a new route in the middle of a build. If you really need to, wait for the build to finish, which is why others are suggesting wrapping it in a SchedulerBinding.instance.addPostFrameCallback to execute after everything is rendered, but you probably should find a better way to do this outside of a build.

In my case, I typed: onTap: _onTap(context),

when I really meant to type: onTap: () => _onTap(context),

my _onTap handler was doing the Navigator push. I had forgotten to wrap my handler in a closure that captures the context it needs, and instead actually was executing it instead of passing onTap: my callback.

Upvotes: 3

hugo
hugo

Reputation: 1245

I got this error because my initialRoute was /login. However, the initialRoute is required to be /.

If the route name starts with a slash, then it is treated as a "deep link", and before this route is pushed, the routes leading to this one are pushed also. For example, if the route was /a/b/c, then the app would start with the four routes /, /a, /a/b, and /a/b/c loaded, in that order.

Here is a link to the docs for reference.

Upvotes: 0

Sam Doggett
Sam Doggett

Reputation: 530

For people encountering this issue while using bloc, make sure you are using navigation in a BlocListener (or BlocConsumer's listener). In my case I was using Navigator inside BlocBuilder. I am new to Flutter/Bloc and the accepted answer resolved the problem, but was not the proper solution. Switching my BlocBuilder to a BlocConsumer allowed for me to navigate during specific states.

Example of using BlocConsumer, navigate when state is '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 .....

Upvotes: 2

AmshenShanu
AmshenShanu

Reputation: 31

I am using flutter version 2.3.3 I also faced this issue when i try to pop back into my home screen from a second screen with command Navigator.pop(context) I solved this issue by replacing this line of code with Navigator.of(context).pop(context) It worked fine for me hope it hepls

Upvotes: -1

s k
s k

Reputation: 5192

For those who are invoking the Navigator as part of the build process. I found that it will intermittently throwing asserting error on the debugLocked

I avoided this issue by wrapping with a addPostFrameCallback:

WidgetsBinding.instance.addPostFrameCallback((_) {
  Navigator.pushReplacement(context, MaterialPageRoute(builder: (_) => MyPage()));
});

Upvotes: 51

Luis Cardoza Bird
Luis Cardoza Bird

Reputation: 1472

For those that still have the same issue, this help me to solve it.

navigationService.popUntil((_) => true);
navigationService.navigateTo(
  'authentication',
);

basically i wait until the navigation finish setting everything and then call the navigateTo.

Upvotes: 0

Thiago Silva
Thiago Silva

Reputation: 796

In resume, you just need to remove it from your initState. I would recomend extend the class with AfterLayout and inside the afterFirstLayout you can redirect it to the page you want. This will garantee that everything is ok before routing.

See bellow the steps: Add to pubspec: after_layout: ^1.0.7+2

Then, You will extend it to the class you want to use. In my case was a statefull widget named HomePage. So it will looks like:

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.

Now, you need to implement a method called afterlayout, that will be executed after the build is completed.

@override
  Future<void> afterFirstLayout(BuildContext context) {

  //your code here safetly
}

You can find information here: https://pub.dev/packages/after_layout

Upvotes: 1

Ravi Singh
Ravi Singh

Reputation: 291

For me, it was coming because I created a cycle of pushes that was causing this error. For example,

In the Initial route which was /loading the code was pushing /home

class _LoadingState extends State<Loading> {
  void getTime() async {
    // DO SOME STUFF HERE
    Navigator.pushNamed(context, '/home');
  }

  @override
  void initState() {
    super.initState();
    getTime();
  }

And in the /home initState I was pushing /loading creating a cycle.

class _HomeState extends State<Home> {
  @override
  void initState() {
    super.initState();
    Navigator.pushNamed(context, '/loading');
  }

Upvotes: 4

Rahul sharma
Rahul sharma

Reputation: 1574

I had this same issue any answer not worked for me and this error doesn't explain any thing.

After going each line code i found that we cannot launch any state in build method like this

 @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();
}

I was getting error in CreateItemPage screen because of that line.

Solution of this issue create button which call this line Navigator.of(context).push(MaterialPageRoute(builder: (context)=>CreateItemPage(viewmodel.catalogData)));

Upvotes: 4

Anoop
Anoop

Reputation: 146

I had similar error, like a dialog box which had a logout button, which when pressed goes to login screen, but an _debugLocked error occurs, so I used

Navigator.of(context).pushNamedAndRemoveUntil('/screen4', (Route<dynamic> route) => false);

This removes all routes in the stack so that user cannot go back to the previous routes after they have logged out.

Setting (Route<dynamic> route) => false will make sure that all routes before the pushed route are removed.

I don't know if this is the "real" solution but it helped me as a beginner to Flutter.

Upvotes: 3

rmtmckenzie
rmtmckenzie

Reputation: 40433

Instead of giving you a direct answer, I'm going to walk you through how I thought about this when I saw the question, in the hope that it'll help you in the future.

Let's take a look at the assertion. It says Failed assertion: line 1995 pos 12: '!_debugLocked': I/flutter (24830): is not true.. Hmm, interesting. Let's take a look at that line of code.

assert(!_debugLocked);

Well, that doesn't give me much more information, let's look at the variable.

bool _debugLocked = false; // used to prevent re-entrant calls to push, pop, and friends

That's better. It's there to prevent re-entrant calls to push, pop, etc (by that it means that it doesn't want you calling 'push', 'pop', etc from within a call to 'push', 'pop'). So let's trace that back to your code.

This seems like the likely culprit:

bottomSheet.closed.then((v) {
  Navigator.of(context)
    .popUntil((r) => r.settings.isInitialRoute);
});

I'm going to skip a step here and use deductive reasoning instead - I'm betting that the closed future is finished during a pop. Go ahead and confirm that by reading the code if you feel like it.

So, if the issue is that we're calling pop from within a pop function, we need to figure out a way to defer the call to pop until after the pop has completed.

This becomes quite simple - there's two ways to do this. The simple way is to just use a delayed future with zero delay, which will have dart schedule the call as soon as possible once the current call stack returns to the event loop:

Future.delayed(Duration.zero, () {
  Navigator. ...
});

The other more flutter-y way of doing it would be to use the Scheduler to schedule a call for after the current build/render cycle is done:

SchedulerBinding.instance.addPostFrameCallback((_) {
  Navigator. ...
});

Either way should eliminate the problem you're having.

Another option is also possible though - in your ExtendedFloatingActionButton where you call pop:

ExtendedFloatingActionButton(
 text: "ORDER DETAILS",
  action: () {
    Navigator.of(context).pop();
  },
),

you could instead simply do the call to Navigator.of(context).popUntil.... That would eliminate the need for the doing anything after bottomSheet.closed is called. However, depending on whatever else you might or might not need to do in your logic this may not be ideal (I can definitely see the issue with having the bottom sheet set off a change to the main part of the page and why you've tried to make that happen in the page's logic).

Also, when you're writing your code I'd highly recommend separating it into widgets - for example the bottom sheet should be its own widget. The more you have in a build function, the harder it is to follow and it can actually have an effect on performance as well. You should also avoid using GlobalKey instances wherever possible - you can generally either pass objects (or callbacks) down if it's only through a few layers, use the .of(context) pattern, or use inherited widgets.

Upvotes: 360

Related Questions