How to deactivate or override the Android "BACK" button, in Flutter?
Asked Answered
M

23

248

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

Matelda answered 28/8, 2017 at 10:13 Comment(0)
M
447

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(),
        ),
      ),
    ),
  );
}
Maximilien answered 28/8, 2017 at 11:43 Comment(5)
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.Mokas
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 itTyrannize
I know this answer maybe old. But is a GEM! Think about expanding the explanation. It work superb. Thank you.Rist
For someone want to do something before pop(), can use this onWillPop: () async { onBack(); // "DO YOUR FUNCTION IS HERE WHEN POP" return false; }Gaylegayleen
As of version 3.16 WillPopScope has been deprecated. the replacement is PopScope. Simply providing with canPop: false will disable the popping of current route. If you need to execute some code on back press, use onPopInvoked parameter.Demurrer
B
57

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.

Beaufort answered 3/1, 2019 at 0:20 Comment(3)
@CopsOnRoad No, since it's not a comment to another answer, but rather a totally different way to solve the same problem.Beaufort
It's a good alternative and it could have been ignored if it was part of a comment.Schoolgirl
It's a great alternative, works like a charm and thanks for highlighting it in a separate answerAtory
T
49

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),
          ),
        ],
      ),
    ),
  );
}
Tegan answered 30/9, 2019 at 6:43 Comment(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 doPectase
T
19

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
    },
  );
}
Tiercel answered 6/4, 2019 at 9:51 Comment(5)
FYI, this is equivalent to onWillPop: () async => _allow.Jakoba
@Jakoba yes I know that. I just wanted to share Future.value() call.Tiercel
@Tiercel return SafeArea() already defined then how to handle back pressed in a flutterMayes
@Tiercel hi, in my class code Widget build(BuildContext context) { return SafeArea( child: Stack( children: <Widget>[ ..... so here return WillPopScope( ); cannot define two return statement then how to initialize backpressed in flutterMayes
@Mayes Wrap your SafeArea widget in WillPopScope.Tiercel
F
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

Fotinas answered 18/1, 2020 at 10:20 Comment(0)
H
10

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.


Replacement

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

Answer

To answer your question:

  • if you want to deactivate the "back" button (i.e. disable system back gesture) you can set canPop: false;
  • if you want to override its behaviour, you can set canPop: true (or calculate its value with some logic condition or a sync function) and set a callback with onPopInvoked: (bool didPop) {}.

Example

PopScope(
  canPop: _isSettingsChanged(),
  onPopInvoked: (bool didPop) async {
    if (didPop) {
      return;
    }
    final NavigatorState navigator = Navigator.of(context);
    final bool? shouldPop = await _showConfirmDialog("Unsaved settings. Discard?");
    if (shouldPop ?? false) {
      navigator.pop();
    }
  },
  child: child,
)
Homesteader answered 5/12, 2023 at 7:53 Comment(1)
This is the right answer for the time being. I didn't do anything more than just upgrade the Flutter 3.16.1.Stercoraceous
H
9

Just a simple method. Wrap Scaffold with WillPopScope widget.

  WillPopScope(
    onWillPop: () async => false,
    child: Scaffold();
Hexapod answered 9/9, 2021 at 10:10 Comment(0)
C
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"),
              )
          )
      ),
    );
  }
}
Chaeta answered 13/6, 2018 at 21:51 Comment(2)
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 knowChaeta
@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.Beaufort
A
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),
              ),
            ],
          ),
        ),
      ),
    ),
Ambrose answered 13/4, 2021 at 17:48 Comment(1)
Use a BackButtonIcon() to get the appropriate icon for the platform.Blind
J
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(),
    );
  }
Jackijackie answered 2/7, 2021 at 10:57 Comment(0)
H
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(),
Hermie answered 30/9, 2019 at 13:30 Comment(0)
D
2

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.

Darill answered 8/9, 2022 at 8:19 Comment(0)
K
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.

Kephart answered 22/10, 2021 at 16:23 Comment(0)
W
1

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"),
  );
}
Windgall answered 5/12, 2023 at 18:10 Comment(0)
J
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);
         }),
       )
Jargon answered 7/2, 2022 at 4:47 Comment(0)
L
0

For those looking for a quick exit:

Navigator.popAndPushNamed(context, '/your_page_route');
Laburnum answered 19/11, 2022 at 12:29 Comment(0)
D
0

Since Flutter version 3.12.0 WillPopScope is deprecated.

You can use PopScope
ref: https://api.flutter.dev/flutter/widgets/PopScope-class.html

Deguzman answered 18/11, 2023 at 17:52 Comment(0)
L
0
AppBar(
          leadingWidth: 0,
          leading: Container(),
          ...)

Just like that!

Lerner answered 13/12, 2023 at 17:27 Comment(2)
This just removes the AppBar widget back button and has nothing to do with the Android back button, which was the subject of OP question. Also it wasn't specified anywhere in the question that he's using an AppBarHomesteader
As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.Shizukoshizuoka
M
0

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();
          }
        },
Marginalia answered 4/1 at 13:21 Comment(0)
B
0

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(),
        ),
      ),
    ),
  );
}
Bdellium answered 3/2 at 4:57 Comment(0)
P
0

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');
          }
        )
      )
    );
  }
}
Proline answered 10/2 at 17:52 Comment(0)
L
0

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

    },
Logroll answered 11/2 at 13:6 Comment(0)
E
0
return Scaffold(
      backgroundColor: backgroundColor,
      appBar: AppBar(
        leadingWidth: 0,
        foregroundColor: Colors.transparent,
      ),
    );
Edo answered 6/3 at 14:0 Comment(1)
Thank you for your interest in contributing to the Stack Overflow community. This question already has quite a few answers—including one that has been extensively validated by the community. Are you certain your approach hasn’t been given previously? If so, it would be useful to explain how your approach is different, under what circumstances your approach might be preferred, and/or why you think the previous answers aren’t sufficient. Can you kindly edit your answer to offer an explanation?Toothbrush

© 2022 - 2024 — McMap. All rights reserved.