When i display a modal dialog in Flutter Web, i would like to make the back button close the current dialog instead of go back to the previous route. After some search, i found PopScope but i can't make it working: the method onPopInvokedWithResult
is called only when i click "Close" button in the modal, when i click "Previous" the browser goes back to page one.
Of course i tried setting canPop
to true/false: the behavior is the same. I tried with WillPopScope
or onPopInvoked
(both are deprecated) but it didn't work.
I use go_router in version 14.6.3
I join a minimal reproduction case:
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
MyApp({super.key});
final GoRouter _router = GoRouter(
routes: [
GoRoute(
path: '/',
builder: (context, state) => const Page1(),
),
GoRoute(
path: '/page2',
builder: (context, state) => const Page2(),
),
],
);
@override
Widget build(BuildContext context) {
return MaterialApp.router(
routerConfig: _router,
);
}
}
class Page1 extends StatelessWidget {
const Page1({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Page 1')),
body: Center(
child: ElevatedButton(
onPressed: () {
context.go('/page2');
},
child: const Text('goto Page 2'),
),
),
);
}
}
class Page2 extends StatelessWidget {
const Page2({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Page 2')),
body: Center(
child: ElevatedButton(
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) {
return PopScope(
canPop: true,
onPopInvokedWithResult: (didPop, result) {
//Section called only after i click Close button
print('onPopInvokedWithResult');
print('$didPop');
//perform some Navigator.of(context).pop();
},
child: AlertDialog(
title: const Text('Dialog'),
content: const Text('Dialog content'),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
print('Close button clicked');
},
child: const Text('Close'),
),
],
),
);
},
);
},
child: const Text('Open dialog'),
),
),
);
}
}
As far as I can tell you can't really control, trigger or stop the browser back button funcionality.
Found two solutions.
One solution is to add an extra page, this could be anything not just page 2, with the dialog if you really want to use the browser's back button, but you will end up with two Page 2 in the navigation history.
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
MyApp({super.key});
final GoRouter _router = GoRouter(
routes: [
GoRoute(
path: '/',
builder: (context, state) => const Page1(),
),
GoRoute(
path: '/page2',
builder: (context, state) => const Page2(),
),
GoRoute(
path: "/page2/dialog",
builder: (context, state) => Page2(
dialog: true,
),
),
],
);
@override
Widget build(BuildContext context) {
return MaterialApp.router(
routerConfig: _router,
);
}
}
class Page1 extends StatelessWidget {
const Page1({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Page 1')),
body: Center(
child: ElevatedButton(
onPressed: () {
context.go('/page2');
},
child: const Text('goto Page 2'),
),
),
);
}
}
class Page2 extends StatefulWidget {
const Page2({super.key, this.dialog = false});
final bool dialog;
@override
State<Page2> createState() => _Page2State();
}
class _Page2State extends State<Page2> {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
if (widget.dialog) _showAlert(context);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Page 2')),
body: Center(
child: ElevatedButton(
onPressed: () {
context.push('/page2/dialog');
//context.pop;
},
child: const Text('Open dialog'),
),
),
);
}
_showAlert(BuildContext context) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Dialog'),
content: const Text('Dialog content'),
actions: [
TextButton(
onPressed: () {
context.pop();
},
child: const Text('Close'),
),
],
);
},
);
}
}
The other solution is to use routerNeglect: true
and context.push
. This doesn't update the browser history, basically disables browser button navigation. If you want to put a page in the navigation history use context.go
. I would use this.
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
MyApp({super.key});
final GoRouter _router = GoRouter(
routerNeglect: true,
routes: [
GoRoute(
path: '/',
builder: (context, state) => const Page1(),
),
GoRoute(
path: '/page2',
builder: (context, state) => const Page2(),
),
],
);
@override
Widget build(BuildContext context) {
return MaterialApp.router(
routerConfig: _router,
);
}
}
class Page1 extends StatelessWidget {
const Page1({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Page 1')),
body: Center(
child: ElevatedButton(
onPressed: () {
context.push('/page2');
},
child: const Text('goto Page 2'),
),
),
);
}
}
class Page2 extends StatelessWidget {
const Page2({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Page 2')),
body: Center(
child: ElevatedButton(
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: const Text('Dialog'),
content: const Text('Dialog content'),
actions: [
TextButton(
onPressed: () {
context.pop();
},
child: const Text('Close'),
),
],
);
},
);
},
child: const Text('Open dialog'),
),
),
);
}
}