262

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

25 Answers 25

460

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(),
        ),
      ),
    ),
  );
}
Sign up to request clarification or add additional context in comments.

5 Comments

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.
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
I know this answer maybe old. But is a GEM! Think about expanding the explanation. It work superb. Thank you.
For someone want to do something before pop(), can use this onWillPop: () async { onBack(); // "DO YOUR FUNCTION IS HERE WHEN POP" return false; }
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.
61

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.

4 Comments

@CopsOnRoad No, since it's not a comment to another answer, but rather a totally different way to solve the same problem.
It's a good alternative and it could have been ignored if it was part of a comment.
It's a great alternative, works like a charm and thanks for highlighting it in a separate answer
'WillPopScope.new' is deprecated and shouldn't be used. Use PopScope instead. This feature was deprecated after v3.12.0-1.0.pre. (Documentation)
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),
          ),
        ],
      ),
    ),
  );
}

1 Comment

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
25

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

Update

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.

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 onPopInvokedWithResult: (bool didPop, T? result) {}.

Migration Example

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

1 Comment

This is the right answer for the time being. I didn't do anything more than just upgrade the Flutter 3.16.1.
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
    },
  );
}

5 Comments

FYI, this is equivalent to onWillPop: () async => _allow.
@Lynn yes I know that. I just wanted to share Future.value() call.
@CopsOnRoad return SafeArea() already defined then how to handle back pressed in a flutter
@CopsOnRoad 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 flutter
@s.j Wrap your SafeArea widget in WillPopScope.
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

Comments

10

Just a simple method. Wrap Scaffold with WillPopScope widget.

  WillPopScope(
    onWillPop: () async => false,
    child: Scaffold();

Comments

8

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"),
              )
          )
      ),
    );
  }
}

2 Comments

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
@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.
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),
              ),
            ],
          ),
        ),
      ),
    ),

1 Comment

Use a BackButtonIcon() to get the appropriate icon for the platform.
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(),
    );
  }

Comments

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

Comments

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.

Comments

2

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 

Comments

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.

Comments

1

Since Flutter version 3.12.0 WillPopScope is deprecated.

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

Comments

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"),
  );
}

Comments

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);
         }),
       )

Comments

0

For those looking for a quick exit:

Navigator.popAndPushNamed(context, '/your_page_route');

Comments

0
AppBar(
          leadingWidth: 0,
          leading: Container(),
          ...)

Just like that!

2 Comments

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 AppBar
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.
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();
          }
        },

Comments

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(),
        ),
      ),
    ),
  );
}

Comments

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');
          }
        )
      )
    );
  }
}

Comments

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

    },

Comments

0

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() {}

Comments

-4
return Scaffold(
      backgroundColor: backgroundColor,
      appBar: AppBar(
        leadingWidth: 0,
        foregroundColor: Colors.transparent,
      ),
    );

1 Comment

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?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.