Skip to content

PopScope Replacement for Deprecated WillPopScope in Flutter

Problem Statement

Flutter deprecated WillPopScope starting in v3.12.0, replacing it with the PopScope widget. This change affects navigation patterns in Flutter apps, particularly back-button handling. Attempting to use WillPopScope now triggers deprecation warnings:

dart
// Deprecated approach
WillPopScope(
  onWillPop: () async => false, // Triggers warning
  child: //...
)

Developers need to migrate to PopScope with its updated API to maintain predictable navigation behavior and ensure compatibility with future Flutter releases.


1. Basic Migration Pattern

Replace WillPopScope with PopScope and use onPopInvokedWithResult for back-button handling:

dart
PopScope(
  canPop: false, // Disables immediate popping
  onPopInvokedWithResult: (didPop, result) {
    if (didPop) return; // Already handled
    // Your custom logic here
    Navigator.pop(context, returnValue); // Optional return value
  },
  child: //...,
)

2. Key Differences Explained

ParameterRole
canPopDetermines if route can exit immediately
onPopInvokedWithResultCallback with didPop status and return data
Context RequirementsRequires access to current BuildContext

3. Important Considerations

  1. canPop must be false to intercept back gestures (else default pop occurs)
  2. didPop flag checks if navigation was already handled
  3. Use result parameter to access values from Navigator.maybePop()

Practical Examples

Preventing Exit Without Confirmation

dart
PopScope(
  canPop: false,
  onPopInvokedWithResult: (didPop, result) async {
    if (didPop) return;
    final shouldExit = await _showExitConfirmation(context);
    if (shouldExit && context.mounted) Navigator.pop(context);
  },
  child: Scaffold(/*...*/),
);

Future<bool> _showExitConfirmation(BuildContext context) async {
  return await showDialog<bool>(
    context: context,
    builder: (context) => AlertDialog(
      title: const Text('Exit app?'),
      actions: [
        TextButton(
          onPressed: () => Navigator.pop(context, false),
          child: const Text('Cancel'),
        ),
        TextButton(
          onPressed: () => Navigator.pop(context, true),
          child: const Text('Exit'),
        ),
      ],
    ),
  ) ?? false;
}

WebView Back Navigation Handling

dart
PopScope(
  canPop: false,
  onPopInvokedWithResult: (didPop, _) async {
    if (didPop) return;
    if (await webViewController.canGoBack()) {
      webViewController.goBack(); // Navigate within WebView
    } else if (context.mounted) {
      Navigator.pop(context); // Exit screen if no history
    }
  },
  child: WebViewWidget(controller: webViewController),
);

Multi-step Confirmation Flow (e.g., double-tap to exit)

dart
bool backButtonPressedOnce = false;

@override
Widget build(BuildContext context) {
  return PopScope(
    canPop: false,
    onPopInvokedWithResult: (didPop, _) {
      if (didPop) return;
      if (backButtonPressedOnce) {
        SystemNavigator.pop(); // Exit app
        return;
      }
      backButtonPressedOnce = true;
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(
          content: Text('Press again to exit'),
          duration: Duration(seconds: 2),
        ),
      );
      Future.delayed(const Duration(seconds: 2), () {
        backButtonPressedOnce = false;
      });
    },
    child: //...,
  );
}

Migration Do's and Don'ts

Best Practices

  • Always check mounted when using Navigator.pop after async operations
  • Use onPopInvokedWithResult instead of deprecated onPopInvoked
  • Return values explicitly via Navigator.pop(context, returnValue)

Avoid These

  • Setting canPop: true while intercepting back navigation
  • Ignoring the didPop parameter leading to duplicate handle attempts
  • Using obsolete onPopInvoked or onWillPop signatures

API History

  • Flutter 3.12+ introduces PopScope as replacement for WillPopScope
  • Deprecated onPopInvoked replaced by onPopInvokedWithResult in later updates

For detailed migration guides, consult Flutter's official documentation on predictive back navigation.

Legacy Note

Older answers mentioning onPopInvoked are outdated. Always use onPopInvokedWithResult in new code.