Introduction

Calling Navigator.of(context).pop() when there is only one route in the navigation stack (the root route) causes Flutter to throw an exception or behave unexpectedly. On Android, this can cause the app to close when the user presses the back button on the home screen. This is a common issue in apps with complex navigation flows or when using deep links.

Symptoms

  • Console error:
  • `
  • Navigator operation requested with a context that does not include a Navigator.
  • `
  • Or silent failure where pop() does nothing on the root route
  • Android back button exits the app unexpectedly on the home screen
  • willPopScope not triggered on the first route

Common Causes

  • Calling Navigator.pop() unconditionally in a callback
  • Using WillPopScope without checking if there are routes to pop
  • Deep linking pushes a route on top of the stack, then popping removes it, leaving the first route
  • Custom back button handlers that do not check the navigation stack

Step-by-Step Fix

  1. 1.Check if Navigator can pop before calling it:
  2. 2.```dart
  3. 3.void handleBack() {
  4. 4.if (Navigator.of(context).canPop()) {
  5. 5.Navigator.of(context).pop();
  6. 6.} else {
  7. 7.// On the root route - handle differently
  8. 8.SystemNavigator.pop(); // Exits the app (Android)
  9. 9.}
  10. 10.}
  11. 11.`
  12. 12.Use PopScope (Flutter 3.12+) instead of WillPopScope:
  13. 13.```dart
  14. 14.class HomeScreen extends StatelessWidget {
  15. 15.const HomeScreen({super.key});

@override Widget build(BuildContext context) { return PopScope( canPop: false, // Prevent default pop behavior onPopInvokedWithResult: (didPop, result) { if (didPop) return; // Show exit confirmation dialog showDialog( context: context, builder: (context) => AlertDialog( title: const Text('Exit App?'), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('Cancel'), ), TextButton( onPressed: () => SystemNavigator.pop(), child: const Text('Exit'), ), ], ), ); }, child: Scaffold( appBar: AppBar(title: const Text('Home')), body: const Center(child: Text('Home Screen')), ), ); } } ```

  1. 1.For WillPopScope (pre-Flutter 3.12):
  2. 2.```dart
  3. 3.WillPopScope(
  4. 4.onWillPop: () async {
  5. 5.if (Navigator.of(context).canPop()) {
  6. 6.return true; // Allow default pop
  7. 7.}
  8. 8.// Show confirmation on root route
  9. 9.final shouldExit = await showDialog<bool>(
  10. 10.context: context,
  11. 11.builder: (context) => AlertDialog(
  12. 12.title: const Text('Exit App?'),
  13. 13.actions: [
  14. 14.TextButton(
  15. 15.onPressed: () => Navigator.pop(context, false),
  16. 16.child: const Text('No'),
  17. 17.),
  18. 18.TextButton(
  19. 19.onPressed: () => Navigator.pop(context, true),
  20. 20.child: const Text('Yes'),
  21. 21.),
  22. 22.],
  23. 23.),
  24. 24.);
  25. 25.return shouldExit ?? false;
  26. 26.},
  27. 27.child: Scaffold(...),
  28. 28.);
  29. 29.`
  30. 30.Use GoRouter for declarative navigation:
  31. 31.```dart
  32. 32.final router = GoRouter(
  33. 33.routes: [
  34. 34.GoRoute(
  35. 35.path: '/',
  36. 36.builder: (context, state) => const HomeScreen(),
  37. 37.routes: [
  38. 38.GoRoute(
  39. 39.path: 'detail/:id',
  40. 40.builder: (context, state) => DetailScreen(id: state.pathParameters['id']!),
  41. 41.),
  42. 42.],
  43. 43.),
  44. 44.],
  45. 45.);
  46. 46.`

Prevention

  • Always check Navigator.canPop() before calling pop()
  • Use PopScope for modern Flutter apps (3.12+)
  • Use GoRouter for complex navigation with deep linking
  • Handle the root route back button explicitly on Android
  • Avoid wrapping the entire app in a WillPopScope - use it per-screen