Milad-Akarie / auto_route_library

Flutter route generator
MIT License
1.59k stars 404 forks source link

On dialog pop + page pop after unawaited async method called ('!_debugLocked': is not true.) #2017

Open HeropolisDa2ny opened 3 months ago

HeropolisDa2ny commented 3 months ago

I've migrated my code from flutter navigator to a auto route navigator and I'm facing an issue on dialog pop.

I open a dialog and wait its response to decide if I should pop or not my current page. The response of my dialog is true then I decide to pop my page. Then because there is a double chained pop I'm receiving an error (that I wasn't receiving on flutter Navigator)

'package:flutter/src/widgets/navigator.dart': Failed assertion: line 4817 pos 12: '!_debugLocked': is not true.
      #0      _AssertionError._doThrowNew (dart:core-patch/errors_patch.dart:51:61)
      #1      _AssertionError._throwNew (dart:core-patch/errors_patch.dart:40:5)
      #2      NavigatorState._pushEntry (package:flutter/src/widgets/navigator.dart:4817:12)
      #3      NavigatorState.push (package:flutter/src/widgets/navigator.dart:4774:5)
      #4      showDialog (package:flutter/src/material/dialog.dart:1436:65)
final bool? result = await showDialog(
  context: context,
  builder: (context) => Dialog(
    child: ElevatedButton(
      onPressed: () => context.router.maybePop(true),
      child: Text('test'),
    ),
  ),
);

_callAsyncCubitMethod(); // take 5 seconds to finish

if (result == true) {
  context.router.maybePop(true);
}

I tried other way to see if it could solve the problem but it didn't work :

if (result == true) {
  WidgetsBinding.instance.addPostFrameCallback((_) async {
    context.router.maybePop();
  });
}

or

if (result == true) {
  Future.delayed(
    Duration(seconds: 0),
    () {
      context.router.maybePop();
    },
  );
}
auto_route: ^9.2.0
Flutter 3.22.3 • channel stable • https://github.com/flutter/flutter.git
Framework • revision b0850beeb2 (3 weeks ago) • 2024-07-16 21:43:41 -0700
Engine • revision 235db911ba
Tools • Dart 3.4.4 • DevTools 2.34.3

Does anyone have an idea to solve this problem ?

HeropolisDa2ny commented 3 months ago

This bug is pretty much annoying when your app doesn't wait for an api response. You call an api method after a dialog confirmation and pop the page because the page isn't needed anymore and the error appears...

PackRuble commented 3 months ago

Same problem.

It helped me to wrap the page where the dialog is called in WillPopScope and use onWillPop. And PopScope gives an error like yours.

Flutter 3.22.3 • channel stable
auto_route: ^9.2.0
HeropolisDa2ny commented 3 months ago

Same problem.

It helped me to wrap the page where the dialog is called in WillPopScope and use onWillPop. And PopScope gives an error like yours.

Flutter 3.22.3 • channel stable
auto_route: ^9.2.0

I migrated to the PopScope and indeed it's producing the error but since I'm backing up to WillPopScope the pages doesn't want to close anymore. Idk what's happening..

tpkowastaken commented 2 weeks ago

My solution was to rethink the logic of the exit confirmation dialog and instead of returning back to the original function I just call removeLast() which removes the last page from the stack instead.

Original: my_form.dart:

  void onTryingToExit() async {
    if (!_isFormDirty()) {
      context.router.popForced();
      return;
    }
    final shouldPop = await showExitConfirmationDialog(context);
    if (shouldPop == true && mounted) {
      context.router.popForced();
    }
  }

  return PopScope(
      canPop: false,
      onPopInvokedWithResult: (didPop, __) => onTryingToExit(context),

show_exit_confirmation_dialog.dart:

Future<bool?> showExitConfirmationDialog(BuildContext context) {
  return showDialog<bool>(
    context: context,
    builder: (context) => AlertDialog(
      title: const Text('Discard changes?'),
      content: const Text('Exit the page and discard changes?'),
      actions: [
        TextButton(
          onPressed: () => context.router.maybePop(false),
          child: const Text('Cancel'),
        ),
        TextButton(
          onPressed: () => context.router.maybePop(true),
          child: const Text(
            'Discard changes',
            style: TextStyle(color: Colors.red),
          ),
        ),
      ],
    ),
  );
}

After:

my_form.dart:

  void onTryingToExit(BuildContext context) async {
    if (!_isFormDirty()) {
      context.router.popForced();
      return;
    }
    showExitConfirmationDialog(context);
  }

  return PopScope(
      canPop: false,
      onPopInvokedWithResult: (didPop, __) => onTryingToExit(context),

show_exit_confirmation_dialog.dart:

Future<bool?> showExitConfirmationDialog(BuildContext context) {
  return showDialog<bool>(
    context: context,
    builder: (context) => AlertDialog(
      title: const Text('Discard changes?'),
      content: const Text('Exit and discard changes?'),
      actions: [
        TextButton(
          onPressed: () => context.router.maybePop(),
          child: const Text('cancel'),
        ),
        TextButton(
          onPressed: () => context.router.removeLast(),
          child: const Text(
            'Discard changes',
            style: TextStyle(color: Colors.red),
          ),
        ),
      ],
    ),
  );
}