WillPopScope
's callback isn't being fired when I expected it to be. How can I use WillPopScope
inside a Navigator
so a route can handle it's own back pressed behaviour.
I'm learning to use flutter, and I came across Navigator
for creating multiple pages (I know there are other navigation widgets but I'm after something which only supports programatic navigation so I can handle all the UI).
The next thing I thought to look at with the Navigator
was going back, I found WillPopScope
which wraps a component and has a callback that gets called when the back button is pressed (if the component is rendered). This seemed ideal for me since I only want the callback to be called if the Widget is rendered.
I tried to use WillPopScope
within a Navigator
with the intention for only the rendered route to have it's callback (onWillPop
) called when the back button is pressed, but putting WillPopScope
within a Navigator
does nothing (the callback isn't called).
The intention is to have a Navigator
navigate to top level routes and those routes themselves potentially having Navigator
s, so putting WillPopScope
inside means each route (or subroute) is responsible for it's own back navigation.
Many questions I've looked up seem to focus on MaterialApp
, Scaffold
, or other ways of handling navigation; I'm looking how to handle this without the UI that those things bring in (a use case could be a quiz app, where you need to click a next button to move forward, or something similar).
Here is the minimal main.dart
file I expect route 2 to handle it's own back navigation (to keep things simple I've not put nested routes in this example).
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Navigation',
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext parentContext) {
return Container(
color: Theme.of(context).colorScheme.primary,
child: Navigator(
initialRoute: "1",
onGenerateRoute: (settings) {
return PageRouteBuilder(pageBuilder: (BuildContext context,
Animation animation, Animation secondaryAnimation) {
switch (settings.name) {
case "1":
return Container(
color: Colors.red,
child: GestureDetector(onTap: () {
debugPrint("going from 1 to 2");
Navigator.of(context).pushNamed("2");
}));
case "2":
return WillPopScope(
child: Container(color: Colors.green),
onWillPop: ()async {
debugPrint("popping from route 2 disabled");
return false;
},
);
default:
throw Exception("unrecognised route \"${settings.name}\"");
}
});
})
);
}
}
When the back button is pressed in either route 1 or 2, the app exits. I expect that to only be the case in route 1, and route 2 should only log popping from route 2 disabled
(with no navigation away from the page or leaving the app).
From what I understand, Navigator
and WillPopScope
are the Widgets to use for this sort of thing, but if not then how would I implement self contained (potentially nested) routes.
You can forward onWillPop to another navigator by using below code:
onWillPop: () async {
return !await otherNavigatorKey.currentState.maybePop();
}
When you create a Navigator you are automatically creating a new stack. Everything you .push
below your Navigator using Navigator.of(context)
is added to the new Navigator stack you just created. However, when you press the backbutton it doesn't know what you want to pop (if the root navigator or the new navigator).
First you need to add a WillPopScope outside your Navigator and add a NavigatorKey to your Navigator
return WillPopScope(
onWillPop: () => _backPressed(_yourKey),
child: Scaffold(
body: Navigator(
key: _yourKey
onGenerateRoute: _yourMaterialPageRouteLogic,
),
bottomNavigationBar: CustomNavigationBar(navBarOnTapCallback),
),
);
Your key can be instatiated like this
GlobalKey<NavigatorState> _yourKey = GlobalKey<NavigatorState>();
The _backPressed
method will receive any backPressed you do on your device. By definition it returns true
, and we don't always want to pop.
We've added a key to the navigator, and now it will be used to understand whether the new Navigator has anything in the stack in order to be popped (ie if it 'canPop').
Future<bool> _backPressed(GlobalKey<NavigatorState> _yourKey) async {
//Checks if current Navigator still has screens on the stack.
if (_yourKey.currentState.canPop()) {
// 'maybePop' method handles the decision of 'pop' to another WillPopScope if they exist.
//If no other WillPopScope exists, it returns true
_yourKey.currentState.maybePop();
return Future<bool>.value(false);
}
//if nothing remains in the stack, it simply pops
return Future<bool>.value(true);
Next you just need to add the WillPopScope anywhere inside your Navigator. Its onWillPop
will be called with the logic you want. Don't forget to return true or false depending on whether you want to pop it or not :)
Here is a example of my onWillPop
method (_popCamera) in a WillPopScope
widget, which is placed inside my Navigator widget tree. In this example I've added a dialog when the user presses the back button in the Camera Widget:
static Future<bool> _popCamera(BuildContext context) {
debugPrint("_popCamera");
showDialog(
context: context,
builder: (_) => ExitCameraDialog(),
barrierDismissible: true);
return Future.value(false);
}
class ExitCameraDialog extends StatelessWidget {
const ExitCameraDialog({
Key key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return AlertDialog(
title: Text('Leaving camera forever'),
content:
Text('Are you S-U-R-E, S-I-R?'),
actions: <Widget>[
FlatButton(
child: Text('no'),
onPressed: Navigator.of(context).pop,
),
FlatButton(
//yes button
child: Text('yes'),
onPressed: () {
Navigator.of(context).pop();
_yourKey.currentState.pop();
},
),
],
);
}
Hope it's clear!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With