Jackpap
Jackpap

Reputation: 7964

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

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

Answers (25)

mikyll98
mikyll98

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.


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

Upvotes: 21

Ijlal Hussain
Ijlal Hussain

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

Ajumal
Ajumal

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

Abdullah Saeed
Abdullah Saeed

Reputation: 1

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

Upvotes: -2

Bensal
Bensal

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

Ansuman
Ansuman

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

Anandh Krishnan
Anandh Krishnan

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

desmeit
desmeit

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

Theo
Theo

Reputation: 1

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

Just like that!

Upvotes: 0

OverdoseB12
OverdoseB12

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

Sreehari K
Sreehari K

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

JhonaCode
JhonaCode

Reputation: 116

For those looking for a quick exit:

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

Upvotes: 0

Mohamad Reza Norouzi
Mohamad Reza Norouzi

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

Tanvir Ahmed
Tanvir Ahmed

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

Radomir Epur
Radomir Epur

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

NavodDinidu
NavodDinidu

Reputation: 441

Just a simple method. Wrap Scaffold with WillPopScope widget.

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

Upvotes: 10

M E S A B O
M E S A B O

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

Mohammad K. Albattikhi
Mohammad K. Albattikhi

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

Marcelo Glasberg
Marcelo Glasberg

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

Nhật Trần
Nhật Trần

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

Djordje Komazec
Djordje Komazec

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

Airon Tark
Airon Tark

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

CopsOnRoad
CopsOnRoad

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

R&#233;mi Rousselet
R&#233;mi Rousselet

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

Bryan Cancel
Bryan Cancel

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

Related Questions