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
willPopScopenot triggered on the first route
Common Causes
- Calling
Navigator.pop()unconditionally in a callback - Using
WillPopScopewithout 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.Check if Navigator can pop before calling it:
- 2.```dart
- 3.void handleBack() {
- 4.if (Navigator.of(context).canPop()) {
- 5.Navigator.of(context).pop();
- 6.} else {
- 7.// On the root route - handle differently
- 8.SystemNavigator.pop(); // Exits the app (Android)
- 9.}
- 10.}
- 11.
` - 12.Use PopScope (Flutter 3.12+) instead of WillPopScope:
- 13.```dart
- 14.class HomeScreen extends StatelessWidget {
- 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.For WillPopScope (pre-Flutter 3.12):
- 2.```dart
- 3.WillPopScope(
- 4.onWillPop: () async {
- 5.if (Navigator.of(context).canPop()) {
- 6.return true; // Allow default pop
- 7.}
- 8.// Show confirmation on root route
- 9.final shouldExit = await showDialog<bool>(
- 10.context: context,
- 11.builder: (context) => AlertDialog(
- 12.title: const Text('Exit App?'),
- 13.actions: [
- 14.TextButton(
- 15.onPressed: () => Navigator.pop(context, false),
- 16.child: const Text('No'),
- 17.),
- 18.TextButton(
- 19.onPressed: () => Navigator.pop(context, true),
- 20.child: const Text('Yes'),
- 21.),
- 22.],
- 23.),
- 24.);
- 25.return shouldExit ?? false;
- 26.},
- 27.child: Scaffold(...),
- 28.);
- 29.
` - 30.Use GoRouter for declarative navigation:
- 31.```dart
- 32.final router = GoRouter(
- 33.routes: [
- 34.GoRoute(
- 35.path: '/',
- 36.builder: (context, state) => const HomeScreen(),
- 37.routes: [
- 38.GoRoute(
- 39.path: 'detail/:id',
- 40.builder: (context, state) => DetailScreen(id: state.pathParameters['id']!),
- 41.),
- 42.],
- 43.),
- 44.],
- 45.);
- 46.
`
Prevention
- Always check
Navigator.canPop()before callingpop() - Use
PopScopefor modern Flutter apps (3.12+) - Use
GoRouterfor 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