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
canPop
must befalse
to intercept back gestures (else default pop occurs)didPop
flag checks if navigation was already handled- Use
result
parameter 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
mounted
when usingNavigator.pop
after async operations - Use
onPopInvokedWithResult
instead of deprecatedonPopInvoked
- 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
oronWillPop
signatures
API History
- Flutter 3.12+ introduces
PopScope
as replacement forWillPopScope
- Deprecated
onPopInvoked
replaced byonPopInvokedWithResult
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.