skip to Main Content

I’m working on a Flutter application using go_router for navigation and PopScope to handle back navigation. I’m experiencing issues with PopScope not behaving as expected.

Context:

  • I’m using go_router for routing in my Flutter app.

  • I need to show an AlertDialog when the user tries to navigate back from a specific route, but PopScope doesn’t seem to work as intended.

  • I want to control the behavior when the back button is pressed and either allow or prevent the navigation based on user confirmation.

Code Example:

Here is the relevant code snippet where I configure PopScope and GoRouter:

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';

class AppNavigation {
  AppNavigation._();

  static final rootNavigatorKey = GlobalKey<NavigatorState>();

  static List<RouteBase> routes = [
    GoRoute(
      path: "/home",
      name: "Home",
      builder: (BuildContext context, GoRouterState state) {
        return HomePage();
      },
    ),
    // Other routes...
  ];

  static final GoRouter router = GoRouter(
    navigatorKey: rootNavigatorKey,
    routes: routes,
  );
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Home')),
      body: PopScope(
        onPopInvoked: () async {
          final shouldExit = await showDialog(
            context: context,
            builder: (context) => AlertDialog(
              title: Text('Confirmation'),
              content: Text('Do you want to exit?'),
              actions: [
                TextButton(
                  onPressed: () => Navigator.of(context).pop(false),
                  child: Text('No'),
                ),
                TextButton(
                  onPressed: () => Navigator.of(context).pop(true),
                  child: Text('Yes'),
                ),
              ],
            ),
          );
          return shouldExit;
        },
        child: Center(child: Text('Home Page Content')),
      ),
    );
  }
}

Issue:

  • The PopScope widget is supposed to intercept the back navigation and display an AlertDialog, but it doesn’t seem to work as expected.

  • The AlertDialog does not appear when the back button is pressed, and the navigation behavior is not controlled.

What I’ve Tried:

  • Ensured that PopScope is correctly used within the Navigator widget.

  • Verified that GoRouter is properly set up and that the route configuration is correct.

Question:

How can I make sure that PopScope works properly with go_router? Is there a known issue or workaround for using PopScope with go_router to control back navigation?

Thank you for any help or insights!

What I Tried:

  • Implemented PopScope to handle back navigation within the GoRouter setup.

  • Used PopScope to show an AlertDialog asking the user for confirmation when they attempt to navigate back.

  • Configured the onPopInvoked callback in PopScope to display the AlertDialog and determine if navigation should proceed.

What I Expected:

  • When the back button is pressed, PopScope should intercept the event and show the AlertDialog.

  • The AlertDialog should give the user the option to either confirm or cancel the navigation.

  • If the user confirms, navigation should proceed. If the user cancels, the navigation should be blocked.

What Actually Happened:

  • The AlertDialog does not appear when the back button is pressed.

  • Navigation occurs immediately without showing the dialog, ignoring the PopScope configuration.

2

Answers


  1. Chosen as BEST ANSWER
     void _onBackPressed({required BuildContext context}) {
          WidgetsBinding.instance.addPostFrameCallback((_) {
            if (mounted) {
              showDialog(
                context: context,
                builder: (context) {
                  return AlertDialog(
                    title: const Text('Are you sure?'),
                    content: const Text('Do you want to end the call?'),
                    actions: [
                      TextButton(
                        onPressed: () {
                          Navigator.pop(context);
                        },
                        child: const Text('No'),
                      ),
                      TextButton(
                        onPressed: () {
                          Navigator.pop(context);
                          Navigator.pop(context);
                          openCallEndReviewBottomSheet(context, navigationProvider);
                        },
                        child: const Text('Yes'),
                      ),
                    ],
                  );
                },
              );
            }
          });
        }
    
        return BackButtonListener(
          onBackButtonPressed: () async {
            _onBackPressed(context: context);
            return true;
          },
    /// ...  My Solution 
    

  2. You should take a look at the canPop parameter of the PopScope widget because that parameter disables the back gesture.

    The documentation has your use case as an example:

    // COPIED AND PASTED FROM https://api.flutter.dev/flutter/widgets/PopScope-class.html
    import 'package:flutter/material.dart';
    
    void main() => runApp(const NavigatorPopHandlerApp());
    
    class NavigatorPopHandlerApp extends StatelessWidget {
      const NavigatorPopHandlerApp({super.key});
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          initialRoute: '/home',
          routes: <String, WidgetBuilder>{
            '/home': (BuildContext context) => const _HomePage(),
            '/two': (BuildContext context) => const _PageTwo(),
          },
        );
      }
    }
    
    class _HomePage extends StatefulWidget {
      const _HomePage();
    
      @override
      State<_HomePage> createState() => _HomePageState();
    }
    
    class _HomePageState extends State<_HomePage> {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                const Text('Page One'),
                TextButton(
                  onPressed: () {
                    Navigator.of(context).pushNamed('/two');
                  },
                  child: const Text('Next page'),
                ),
              ],
            ),
          ),
        );
      }
    }
    
    class _PageTwo extends StatefulWidget {
      const _PageTwo();
    
      @override
      State<_PageTwo> createState() => _PageTwoState();
    }
    
    class _PageTwoState extends State<_PageTwo> {
      /// Shows a dialog and resolves to true when the user has indicated that they
      /// want to pop.
      ///
      /// A return value of null indicates a desire not to pop, such as when the
      /// user has dismissed the modal without tapping a button.
      Future<bool?> _showBackDialog() {
        return showDialog<bool>(
          context: context,
          builder: (BuildContext context) {
            return 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, false);
                  },
                ),
                TextButton(
                  style: TextButton.styleFrom(
                    textStyle: Theme.of(context).textTheme.labelLarge,
                  ),
                  child: const Text('Leave'),
                  onPressed: () {
                    Navigator.pop(context, true);
                  },
                ),
              ],
            );
          },
        );
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: <Widget>[
                const Text('Page Two'),
                PopScope(
                  canPop: false,
                  onPopInvoked: (bool didPop) async {
                    if (didPop) {
                      return;
                    }
                    final bool shouldPop = await _showBackDialog() ?? false;
                    if (context.mounted && shouldPop) {
                      Navigator.pop(context);
                    }
                  },
                  child: TextButton(
                    onPressed: () async {
                      final bool shouldPop = await _showBackDialog() ?? false;
                      if (context.mounted && shouldPop) {
                        Navigator.pop(context);
                      }
                    },
                    child: const Text('Go back'),
                  ),
                ),
              ],
            ),
          ),
        );
      }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search