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:
// 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.
Recommended Solution for Modern Flutter
1. Basic Migration Pattern
Replace WillPopScope with PopScope and use onPopInvokedWithResult for back-button handling:
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
| Parameter | Role |
|---|---|
canPop | Determines if route can exit immediately |
onPopInvokedWithResult | Callback with didPop status and return data |
| Context Requirements | Requires access to current BuildContext |
3. Important Considerations
canPopmust befalseto intercept back gestures (else default pop occurs)didPopflag checks if navigation was already handled- Use
resultparameter to access values fromNavigator.maybePop()
Practical Examples
Preventing Exit Without Confirmation
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
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)
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
mountedwhen usingNavigator.popafter async operations - Use
onPopInvokedWithResultinstead of deprecatedonPopInvoked - Return values explicitly via
Navigator.pop(context, returnValue)
Avoid These
- Setting
canPop: truewhile intercepting back navigation - Ignoring the
didPopparameter leading to duplicate handle attempts - Using obsolete
onPopInvokedoronWillPopsignatures
API History
- Flutter 3.12+ introduces
PopScopeas replacement forWillPopScope - Deprecated
onPopInvokedreplaced byonPopInvokedWithResultin 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.