239

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).

Marcelo Glasberg
  • 29,013
  • 23
  • 109
  • 133
Jackpap
  • 7,314
  • 5
  • 18
  • 12

14 Answers14

433

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(),
        ),
      ),
    ),
  );
}
CopsOnRoad
  • 237,138
  • 77
  • 654
  • 440
Rémi Rousselet
  • 256,336
  • 79
  • 519
  • 432
  • 6
    For those wanting to intercept the pop of a Form, it's more convenient to use the Form's `onWillPop` property. It has access to the form state and can first check to see if there is any state that the user might not want to lose. – Joe Lapp Oct 24 '19 at 13:55
  • Hi @RémiRousselet , Can you please help me on the back stack manage like I have a stack of A, B , C, D screens and I want to navigate from the D->B OR D->A , then how will I manage it. Can you please guide me on it – Ravindra Kushwaha Jan 07 '20 at 05:02
  • 1
    I know this answer maybe old. But is a GEM! Think about expanding the explanation. It work superb. Thank you. – Val Apr 17 '20 at 23:55
  • 7
    For someone want to do something before pop(), can use this `onWillPop: () async { onBack(); // "DO YOUR FUNCTION IS HERE WHEN POP" return false; }` – Huy Tower Apr 24 '20 at 03:22
56

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.

Marcelo Glasberg
  • 29,013
  • 23
  • 109
  • 133
48

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),
          ),
        ],
      ),
    ),
  );
}
Airon Tark
  • 8,900
  • 4
  • 23
  • 19
  • 1
    I want to go to the previous screen rather going to the first screen after pressing back button from device. I dont need a dialog box like this. What should I do – Lakshmi Jul 31 '20 at 09:48
18

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
    },
  );
}
CopsOnRoad
  • 237,138
  • 77
  • 654
  • 440
  • 2
    FYI, this is equivalent to `onWillPop: () async => _allow`. – Lynn Jun 11 '19 at 22:27
  • @Lynn yes I know that. I just wanted to share `Future.value()` call. – CopsOnRoad Jun 12 '19 at 02:08
  • @CopsOnRoad return SafeArea() already defined then how to handle back pressed in a flutter – s.j Dec 16 '20 at 12:15
  • @CopsOnRoad hi, in my class code Widget build(BuildContext context) { return SafeArea( child: Stack( children: [ ..... so here return WillPopScope( ); cannot define two return statement then how to initialize backpressed in flutter – s.j Dec 17 '20 at 05:04
  • 1
    @s.j Wrap your `SafeArea` widget in `WillPopScope`. – CopsOnRoad Dec 17 '20 at 09:22
10

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

Nhật Trần
  • 2,522
  • 1
  • 20
  • 20
8

Just a simple method. Wrap Scaffold with WillPopScope widget.

  WillPopScope(
    onWillPop: () async => false,
    child: Scaffold();
NavodDinidu
  • 383
  • 3
  • 6
7

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"),
              )
          )
      ),
    );
  }
}
Bryan Cancel
  • 189
  • 2
  • 6
  • I literally used it myself yesterday. Keep in mind that it is only for Android, Apple has no back button. Although could you perhaps tell me what goes wrong so I can repair it, or perhaps it only works on my particular emulator. I haven't updated my flutter in a couple weeks so perhaps that's the problem. Let me know – Bryan Cancel Jun 16 '18 at 16:44
  • 1
    @BryanCancel Your answer works only if you don't yet have any pushed routes. See method WidgetsBinding.handlePopRoute(). It notifies the observers in registration order, and stops as soon as it receives true. When there are pushed routes, the navigator returns true first, and then your observer never actually gets called. In other words, your code only works to prevent the application from shutting down when the user clicks the back button, when there are no more routes left. – Marcelo Glasberg Jan 02 '19 at 23:22
4

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),
              ),
            ],
          ),
        ),
      ),
    ),
4

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(),
    );
  }
M E S A B O
  • 783
  • 1
  • 11
  • 15
3

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(),
1

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.

Radomir Epur
  • 369
  • 4
  • 4
0

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);
         }),
       )
Tanvir Ahmed
  • 564
  • 6
  • 13
0

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.

0

For those looking for a quick exit:

Navigator.popAndPushNamed(context, '/your_page_route');
JhonaCode
  • 51
  • 4