Reputation: 7964
Is there a way to deactivate the Android back button when on a specific page?
class WakeUpApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: "Time To Wake Up ?",
home: new WakeUpHome(),
routes: <String, WidgetBuilder>{
'/pageOne': (BuildContext context) => new pageOne(),
'/pageTwo': (BuildContext context) => new pageTwo(),
},
);
}
}
On pageOne I have a button to go to pageTwo:
new FloatingActionButton(
onPressed: () {
Navigator.of(context).pushNamed('/pageTwo');
},
)
My problem is that if I press the Back arrow at the bottom of the android screen, I go back to pageOne. I would like this button to not show up at all. Ideally, I would like to have no possible way out of this screen unless the user for example keeps his finger pressed on the screen for 5 seconds. (I am trying to write an App for toddlers, and would like only the parents to be able to navigate out of the particular screen).
Upvotes: 259
Views: 211927
Reputation: 2248
The accepted answer is outdated
WillPopScope
is now deprecated, and shouldn't be used anymore. Reference: Android Predictive Back
To support Android 14’s Predictive Back feature, a set of ahead-of-time APIs have replaced just-in-time navigation APIs, like WillPopScope and Navigator.willPop.
If you're using version 3.14.0-7.0.pre of Flutter or greater, you should replace WillPopScope
widgets with PopScope
. It has 2 fields:
bool canPop
, enables/disables system back gestures (pop);void Function(bool didPop)? onPopInvoked
, is a callback that's always invoked on system back gestures, even if canPop
is set to false, but in this case the route will not be popped off, and didPop
will be set to false (so you can check its value to discriminate the logic).Property onPopInvoked
was also deprecated after version v3.22.0-12.0.pre, in favor of onPopInvokedWithResult
. Reference: Breaking Change: Generic types in PopScope.
onPopInvokedWithResult
expects a callback of type void Function(bool didPop, T? result)
, which is called after a route pop was handled. Its behaviour is similar to onPopInvoked
but it also provides the result of the pop through the result
parameter.To answer your question:
canPop: false
;canPop: true
(or calculate its value with some logic condition or a sync function) and set a callback with onPopInvokedWithResult: (bool didPop, T? result) {}
.With WillPopScope
:
WillPopScope(
onWillPop: () async {
final bool shouldPop = await _showBackDialog() ?? false;
return shouldPop;
},
child: child,
)
With PopScope
:
PopScope<Object?>(
canPop: false,
onPopInvokedWithResult: (bool didPop, Object? result) async {
if (didPop) {
return;
}
final bool shouldPop = await _showBackDialog() ?? false;
if (context.mounted && shouldPop) {
Navigator.pop(context);
}
},
child: child
),
Full example: flutter/widgets/PopScope-class
Upvotes: 21
Reputation: 490
onPopInvoked has been deprecated in the flutter 3.24.0. You can use onPopInvokedWithResult. This will not allow the user to go back to the previous screen.
PopScope(
canPop: boolean, // can be true or false
onPopInvokedWithResult: (didPop, result) async{
if(didPop){
return ;
}
// or
if(!didPop){
return ;
}
},
child: child
Upvotes: 2
Reputation: 1048
If you looking to disable the android back button for all the screen and also you dont want disable iPhone swipe to pop feature then the solution is to use the bellow
go to your android folder find MainActivity
and add the onBackPressed method
override fun onBackPressed() {}
This will disable android device back button for of your screen.
for JAVA code
public void onBackPressed() {}
Upvotes: 0
Reputation: 1
return Scaffold(
backgroundColor: backgroundColor,
appBar: AppBar(
leadingWidth: 0,
foregroundColor: Colors.transparent,
),
);
Upvotes: -2
Reputation: 4110
You can use PopScope
instead of the depreciated Widget WillPopScope
PopScope(
///[true] if the page should pop, else false
canPop: true/false,
onPopInvoked: (bool didPop) async {
///didPop boolean indicates whether or not back navigation
/// succeeded.
if (didPop) {
return;
}
///Your logic saying what should happen
///if the screen is not popped
},
Upvotes: 0
Reputation: 1
You can use the new PopScope class (Flutter SDK 3.16.x or greater)
class PageOne extends StatelessWidget {
const PageOne({super.key});
@override
Widget build(BuildContext context) {
return PopScope(
onPopInvoked: (_) async => false,
child: Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.of(context).pushNamed('/pageTwo');
}
)
)
);
}
}
Upvotes: 0
Reputation: 5986
WillPopScope is deprecated after v3.12.0
onwards use Use PopScope
instead
Widget build(BuildContext context) {
return new WillPopScope(
onWillPop: () async => false,
child: new Scaffold(
appBar: new AppBar(
title: new Text("data"),
leading: new IconButton(
icon: new Icon(Icons.ac_unit),
onPressed: () => Navigator.of(context).pop(),
),
),
),
);
}
Upvotes: 0
Reputation: 650
In my case, the solution from mikyll98 did not close my app but showed a black screen. The following solution worked for me:
return PopScope(
canPop: false,
onPopInvoked: (bool didPop) async {
if (didPop) {
return;
}
final bool? shouldPop = await _closeAppDialog(context);
if (shouldPop ?? false) {
SystemNavigator.pop();
}
},
Upvotes: 0
Reputation: 1153
Since Flutter Version 3.16.2 the WillPopScope
Widget is deprecated.
Here is the new way to to this:
@override
Widget build(BuildContext context) {
return TextButton(
onPressed: () {
// Push to Page but disable possibility to pop page
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const PopScope(
canPop: false,
child: NextPage(),
),
),
);
},
child: const Text("Naviagte to Page"),
);
}
Upvotes: 1
Reputation: 977
Since Flutter version 3.12.0 WillPopScope is deprecated.
You can use PopScope
ref: https://api.flutter.dev/flutter/widgets/PopScope-class.html
Upvotes: 1
Reputation: 116
For those looking for a quick exit:
Navigator.popAndPushNamed(context, '/your_page_route');
Upvotes: 0
Reputation: 156
WillPopScope
is a good solution, and with onWillPop: () async => false
the back button is disabled, but the icon is still there.
To remove and disable the back button just use leading: Container()
in AppBar
.
Upvotes: 2
Reputation: 682
Using generated route you can return to destination page by doing this-
AppBar(
leading: new IconButton(
icon: new Icon(Icons.arrow_back),
onPressed: () {
Navigator.of(context) .pushNamedAndRemoveUntil(AppRouter.dashboard, (route) => false);
}),
)
Upvotes: 0
Reputation: 434
In case you need to have different behavior for system back button tap, and app bar back button tap: you can remove the onWillPop
callback before calling Navigator.of(context).pop()
:
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: onWillPop,
child: Scaffold(
appBar: AppBar(
leading: IconButton(
onPressed: () {
ModalRoute.of(context)?.removeScopedWillPopCallback(onWillPop);
Navigator.of(context).pop();
},
icon: const Icon(Icons.arrow_back),
),
title: Text(context.l10n.searchResults),
),
body: MyBody(),
),
);
}
In this case when user will tap system back button, onWillPop
callback will decide should the screen be popped or not. But when user will tap app bar back button, screen will be popped immediatly.
Upvotes: 1
Reputation: 441
Just a simple method. Wrap Scaffold with WillPopScope widget.
WillPopScope(
onWillPop: () async => false,
child: Scaffold();
Upvotes: 10
Reputation: 953
Trying this will kill your app state
@override
Widget build(BuildContext context) {
return WillPopScope(
////////////////
onWillPop: () => showDialog<bool>(
context: context,
builder: (c) => AlertDialog(
title: Text(
'Warning',
textAlign: TextAlign.center,
),
content: Text('Are you sure to exit?'),
actions: [
TextButton(
style: TextButton.styleFrom(
primary: Colors.green,
),
onPressed: () async {
exit(0);// kill app
},
child: Text('Yes'),
),
TextButton(
style: TextButton.styleFrom(
primary: Colors.red,
),
onPressed: () => Navigator.pop(c, false),
child: Text('No'),
)
],
),
),
/////////////////////
child: Scaffold(),
);
}
Upvotes: 4
Reputation: 737
Here's an alternative solution that works if you're coding with null safety. You need to disable the the default back button, and replace it with an IconButton. In this example, I'm pushing an AlertDialog when the user clicks the back button to confirm before exiting. You can replace this function and send the user to any other page
return WillPopScope(
onWillPop: () async => false,
child: Scaffold(
appBar: AppBar(
automaticallyImplyLeading: true,
title: Text(),
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () => showDialog<bool>(
context: context,
builder: (c) => AlertDialog(
title: Text('Warning'),
content: Text('Are you sure you want to exit?'),
),
actions: [
TextButton(
child: Text('Yes'),
onPressed: () {
Navigator.pop(c, true);
Navigator.pop(context);
}),
TextButton(
child: Text('No'),
onPressed: () => Navigator.pop(c, false),
),
],
),
),
),
),
Upvotes: 4
Reputation: 30879
As Rémi Rousselet pointed out, WillPopScope
is usually the way to go. However, if you are developing a stateful widget that should react to the back button directly, you may use this:
https://pub.dartlang.org/packages/back_button_interceptor
Note: I am the author of this package.
Upvotes: 61
Reputation: 2828
The answer maybe you knew that's use WillPopScope,but unfortunately on IOS you can't swipe to back the previous page, so let's custom your MaterialPageRoute:
class CustomMaterialPageRoute<T> extends MaterialPageRoute<T> {
@protected
bool get hasScopedWillPopCallback {
return false;
}
CustomMaterialPageRoute({
@required WidgetBuilder builder,
RouteSettings settings,
bool maintainState = true,
bool fullscreenDialog = false,
}) : super(
builder: builder,
settings: settings,
maintainState: maintainState,
fullscreenDialog: fullscreenDialog,
);
}
Now you can use WillPopScope and swipe to back will work on IOS. The details answer is here: https://github.com/flutter/flutter/issues/14203#issuecomment-540663717
Upvotes: 10
Reputation: 31
I used mixin and WillPopScope widget just couldn't get the job done for me.
This is best approach I found, much better than WillPopScope in my opinion.
final bool canPop = ModalRoute.of(context)?.canPop ?? false;
Used it like this inside appbar:
leading: ModalRoute.of(context)?.canPop ?? false
? IconButton(
onPressed: () {
Navigator.pop(context);
},
icon: (Platform.isAndroid)
? const Icon(Icons.arrow_back)
: const Icon(Icons.arrow_back_ios),
)
: Container(),
Upvotes: 3
Reputation: 9476
While Remi's answer is right, usually you don't want to simply block the back button but want a user to confirm the exit.
You can do it similar way by getting an answer from the confirmation dialog, because onWillPop
is a future.
@override
Widget build(BuildContext context) {
return WillPopScope(
child: Scaffold(...),
onWillPop: () => showDialog<bool>(
context: context,
builder: (c) => AlertDialog(
title: Text('Warning'),
content: Text('Do you really want to exit'),
actions: [
FlatButton(
child: Text('Yes'),
onPressed: () => Navigator.pop(c, true),
),
FlatButton(
child: Text('No'),
onPressed: () => Navigator.pop(c, false),
),
],
),
),
);
}
Upvotes: 49
Reputation: 267384
You can use Future.value(bool)
to handle the back button.
bool _allow = true;
@override
Widget build(BuildContext context) {
return WillPopScope(
child: Scaffold(appBar: AppBar(title: Text("Back"))),
onWillPop: () {
return Future.value(_allow); // if true allow back else block it
},
);
}
Upvotes: 19
Reputation: 276881
The answer is WillPopScope
. It will prevent the page from being popped by the system. You'll still be able to use Navigator.of(context).pop()
@override
Widget build(BuildContext context) {
return new WillPopScope(
onWillPop: () async => false,
child: new Scaffold(
appBar: new AppBar(
title: new Text("data"),
leading: new IconButton(
icon: new Icon(Icons.ac_unit),
onPressed: () => Navigator.of(context).pop(),
),
),
),
);
}
Upvotes: 459
Reputation: 199
I'm posting this here in case anyone out there finds this and wishes they would find a simple example https://gist.github.com/b-cancel/0ca372017a25f0c120b14dfca3591aa5
import 'package:flutter/material.dart';
import 'dart:async';
void main() => runApp(new BackButtonOverrideDemoWidget());
class BackButtonOverrideDemoWidget extends StatefulWidget{
@override
_BackButtonOverrideDemoWidgetState createState() => new _BackButtonOverrideDemoWidgetState();
}
class _BackButtonOverrideDemoWidgetState extends State<BackButtonOverrideDemoWidget> with WidgetsBindingObserver{
//-------------------------Test Variable
bool isBackButtonActivated = false;
//-------------------------Required For WidgetsBindingObserver
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
//-------------------------Function That Triggers when you hit the back key
@override
didPopRoute(){
bool override;
if(isBackButtonActivated)
override = false;
else
override = true;
return new Future<bool>.value(override);
}
//-------------------------Build Method
@override
Widget build(BuildContext context) {
return new Directionality(
textDirection: TextDirection.ltr,
child: new Container(
color: (isBackButtonActivated) ? Colors.green : Colors.red,
child: new Center(
child: new FlatButton(
color: Colors.white,
onPressed: () {
isBackButtonActivated = !isBackButtonActivated;
setState(() {});
},
child: (isBackButtonActivated) ?
new Text("DeActive the Back Button") : new Text("Activate the Back Button"),
)
)
),
);
}
}
Upvotes: 8