Rizky Andriawan
Rizky Andriawan

Reputation: 3566

catch Android back button event on Flutter

Is there any way I can catch the onBackPressed event from Android back button?

I've tried the WillPopScope but my onWillPop function only triggered when I tap on the Material back arrow button

I put it like this:

class MyView extends StatelessWidget{

Widget build(BuildContext context) {

    return new WillPopScope(
      onWillPop: () async {
        debugPrint("Will pop");
        return true;
      },
      child: ScopedModel<AppModel>(
      model: new AppModel(),
      child: new Scaffold(......

I need to catch it because somehow my screen behaved incorrectly when it came to back button pressed, it pops the screen and the screen below it, but somehow, using material back arrow button works normal.

Update:

The code works, my problem was not in the pop of this screen, but on the previous screen, I use 2 MaterialApp widgets, and somehow it gave a weird behavior.

Upvotes: 111

Views: 151750

Answers (12)

mikyll98
mikyll98

Reputation: 2281

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 try using it, you'll get the following warning:

'WillPopScope.new' is deprecated and shouldn't be used.
Use PopScope instead. This feature was deprecated after v3.12.0-1.0.pre.
Try replacing the use of the deprecated member with the replacement.

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.

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: 10

Andrew
Andrew

Reputation: 38029

Now it is PopScope:

PopScope(
 onPopInvokedWithResult: (didPop, result) async {
   if (didPop) {
     return;
   }
   return;
 )

Upvotes: 0

Mitch
Mitch

Reputation: 1866

This should be helpful.

@override
Widget build(BuildContext context) {
  return WillPopScope(
    onWillPop: () {
      _moveToScreen2(context, );
    },
    child: Scaffold(
      key: _scaffoldKey,
      appBar: AppBar(
        leading: IconButton(
            icon: Icon(Icons.arrow_back),
            onPressed: () {
              _moveToScreen2(context);
            }),
        title: Text("Screen 1"),
      ),
    ),
  );
}

/**
* This is probably too thin to be in its own method - consider using
* `Navigator.pushReplacementNamed(context, "screen2")` directly
*/
void _moveToScreen2(BuildContext context) =>
    Navigator.pushReplacementNamed(context, "screen2");

Upvotes: 27

Arnold Parge
Arnold Parge

Reputation: 6877

In order to prevent navigating back, WillPopScope is the correct way and should be used as follows:

class Page2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new WillPopScope(
      child: new Scaffold(
        appBar: new AppBar(
          title: new Text('Page 2'),
        ),
        body: new Center(
          child: new Text('PAGE 2'),
        ),
      ),
      onWillPop: () async {
        return false;
      },
    );
  }
}

Future<T> pushPage<T>(BuildContext context, Widget page) {
  return Navigator.of(context)
      .push<T>(MaterialPageRoute(builder: (context) => page));
}

Can call the page like:

pushPage(context, Page2());

Upvotes: 106

Muhammad Aamir
Muhammad Aamir

Reputation: 2696

try this code snippet.

import 'package:flutter/material.dart';

class HomePage extends StatefulWidget {
  const HomePage({Key? key}) : super(key: key);

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {


  DateTime? _lastPressedAt;

  @override
  Widget build(BuildContext context) {



    return WillPopScope(
      onWillPop: () async{

        bool shouldCloseApp = false;
        DateTime now = DateTime.now();
        if (_lastPressedAt == null || now.difference(_lastPressedAt!) > Duration(seconds: 2)) {
          // If this is the first time the user presses back or more than 2 seconds
          // have passed since the last press, show a message
          _lastPressedAt = now;
          ScaffoldMessenger.of(context).showSnackBar(SnackBar(
            content: Text('Press back again to close the app.'),
          ));
        } else {
          // If this is the second time the user presses back within 2 seconds,
          // set shouldCloseApp to true to exit the app
          shouldCloseApp = true;
        }

        return shouldCloseApp;
      },
      child: Scaffold(
        body: Container(
          child: Text("Home Page"),
        ),
      ),
    );
  }
}

Upvotes: 0

Drown
Drown

Reputation: 109

Following the documentation of BackButtonListener:

/// It can be useful for scenarios, in which you create a different state in your
/// screen but don't want to use a new page for that.

https://github.com/flutter/flutter/pull/79642

e.g.

  @override
  Widget build(BuildContext context) {
    return BackButtonListener(
      onBackButtonPressed: () {
        /// todo: close search widget
        if(searchBarController.isClose()){
            return false;
        }else{
            searchBarController.close();
            return Future.value(true);
        }
      },
      child: SearchBar(controller: searchBarController),
    );
  }

Upvotes: 2

Syed Huzaifa
Syed Huzaifa

Reputation: 23

This is the updated code

basically, WillPopScope -> onWillPop works on the future argument we can say as when it happens then ???? so as soon the back button is pressed WillPopScope -> onWillPop gets activated and listens to the argument more specific the back button event to pop it (replace it) Most of the time I use it to show a DialogBox of Future type because it will only appear when it is needed same can be used to navigate to a new screen as well (hope so) preferred to do MaterialPage routing or named routing techniques for navigation, use WillPopScope for the hardware back button event listening (hardware = Android/IOs) to show the exit popup

code is already been given above but does not work for me so I change a little bit

@override
Widget build(BuildContext context) {
  return WillPopScope(
    onWillPop: () async{
      return _moveToScreen2(context);
    },
    child: Scaffold(
      key: _scaffoldKey,
      appBar: AppBar(
        leading: IconButton(
            icon: Icon(Icons.arrow_back),
            onPressed: () {
              _moveToScreen2(context);
            }),
        title: Text("Screen 1"),
      ),
    ),
  );
}

Future<bool>_moveToScreen2(BuildContext context) =>
    Navigator.pushReplacementNamed(context, "screen2");

===============================================

What I do on Exit

@override
Widget build(BuildContext context) {
  return WillPopScope(
    onWillPop: ()=> showExitPopup(context)
    child: Scaffold(
      key: _scaffoldKey,
      appBar: AppBar(
        title: Text("Screen 1"),
      ),
    ),
  );
}

=========================================

On Back button Press created a dart file with the name showExitPopup

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:vexpositions/Servises/ConstantManager.dart';

Future<bool> showExitPopup(context) async{
  return await showDialog<bool>(
      context: context,
      builder: (BuildContext context) {
        return AlertDialog(
          content: SizedBox(
            height: 90,
            child: Column(
              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
               const Text("Want to Exit the app!"),
               const SizedBox(height:20),
               Row(
                  children: [
                    Expanded(
                      child: ElevatedButton(
                        onPressed: () {
                          print('yes selected');
                          exit(0);
                        },
                        style: ElevatedButton.styleFrom(
                            primary: Colors.white),
                        child: const Text("Yes", style: TextStyle(color: 
                       Colors.black)),
                      ),
                    ),
                    const SizedBox(width: 15),
                    Expanded(
                        child: ElevatedButton(
                          onPressed: () {
                            print('no selected');
                            Navigator.of(context).pop();
                          },
                          style: ElevatedButton.styleFrom(
                            primary: Colors.red.shade800,
                          ),
                          child: const Text("No", style: TextStyle(color: 
                          Colors.white)),
                        ))
                  ],
                )
              ],
            ),
          ),
        );
      });
}

Upvotes: 2

Abdelrahman Tareq
Abdelrahman Tareq

Reputation: 2327

You can use back_button_interceptor

it detects hardware back button & will be so useful specially in case of using persistent_bottom_nav_bar

@override
void initState() {
    super.initState();
    BackButtonInterceptor.add(myInterceptor);
  }

  @override
  void dispose() {
    BackButtonInterceptor.remove(myInterceptor);
    super.dispose();
  }

  bool myInterceptor(bool stopDefaultButtonEvent, RouteInfo info) {
    print("BACK BUTTON!"); // Do some stuff.
    return false;// return true if u want to stop back
  }

Upvotes: 7

Thiran Hettiarachchi
Thiran Hettiarachchi

Reputation: 359

Use WillPopScope method and return false

@override
Widget build(BuildContext context) {
  return WillPopScope(
     onWillPop: () async {
        // Do something here
        print("After clicking the Android Back Button");
        return false;
     },
     child: Scaffold(
       appBar: AppBar(
          title: Text("Handling the back button"),
       ),
       body: Center(
          child: Text("Body"),
       ),
    ),
  );
}

Upvotes: 19

mzaink
mzaink

Reputation: 261

Just adding an important point here. Please note that by using WillPopScope, we will lose the back swipe gesture on iOS.

Reference: https://github.com/flutter/flutter/issues/14203

Upvotes: 5

TjerkW
TjerkW

Reputation: 2150

Another way todo this is to implement a NavigatorObserver and link it to the MaterialApp:

https://api.flutter.dev/flutter/widgets/RouteObserver-class.html

You don't have to use RouteAware, you can also implement your own NavigatorObserver.

This is for example how Flutter analytics works to automatically track screen opens/closes:

        MaterialApp(
          ...
          navigatorObservers: [
           FirebaseAnalyticsObserver(analytics: analytics),
          ],
        )

FirebaseAnalyticsObserver extends the RouteObserver which itself implements NavigatorObserver.

However WillPopScope is often the easier solution

Upvotes: 1

蔡旻袁
蔡旻袁

Reputation: 181

This code work for me.

I think there may be two reasons.

  1. Child of WillPopScope is Scaffold
  2. No return in onWillPop

    return new WillPopScope(
      onWillPop: () {
        if (!_isOpened) Navigator.pop(context);
      },
      child: new Scaffold(
        key: SharedService.orderScaffoldKey,
        appBar: appBar,
        body: new Builder(
          builder: (BuildContext context) {
            return page;
          },
        ),
      ),
    );
    

Upvotes: 4

Related Questions