62

I have an interface with two buttons that pop and return true or false, like so:

onPressed: () => Navigator.pop(context, false)

I need to adapt the back button in the appbar, so it pops and also returns false. Is there a way to accomplish this?

aksn
  • 2,341
  • 4
  • 17
  • 21

10 Answers10

75

The easier way is to wrap the body in WillPopScope, in this way it will work with the Back Button on the Top AND the Android Back Button on the Bottom.

Here an example where both back buttons return false:

final return = Navigator.of(context).push(MaterialPageRoute<bool>(
    builder: (BuildContext context) {
      return Scaffold(
        appBar: AppBar(
          title: Text("New Page"),
        ),
        body: WillPopScope(
          onWillPop: () async {
             Navigator.pop(context, false);
             return false;
          },
          child: newPageStuff(),
        ),
      );
    },
));

In the other answers they suggested to use:

leading: BackButton(...)

I found that this works on with the Back Button on the Top and not with the Android one.

I include anyway an example:

final return = Navigator.of(context).push(MaterialPageRoute<bool>(
    builder: (BuildContext context) {
      return Scaffold(
        appBar: AppBar(
          leading: BackButton(
            onPressed: () => Navigator.pop(context, false),
          ),
          title: Text("New Page"),
        ),
        body: newPageStuff(),
      );
    },
));
Apoleo
  • 2,045
  • 2
  • 21
  • 37
  • 4
    Thank you for your answer, it really helped me! But I suggest a little improvement in your first solution: the function that you pass as `onWillPop` parameter could be an `async` function, so that at the end of the block you can just `return false`. – Salvatore Jun 19 '20 at 09:33
  • In the docs for `onWillPop` it says "If the callback returns a Future that resolves to false, the enclosing route will not be popped". So should the async callback itself return true? – chanban Oct 21 '21 at 04:48
  • @chanban the `async` function is handling the `true` return. `onWillPop` will return `false` on runtime. – Ryde Oct 25 '21 at 07:50
  • For me, I am sending back a boolean true from second screen, but I am getting null value on first screen. Any idea on this ? – raphire Dec 02 '21 at 07:49
  • @raphire have you try `var value= await Navigator.pushName(context, '\second-screen')` at first screen ? – omega_mi Aug 31 '22 at 02:22
39

The default BackButton takes over the leading property of your AppBar so all you need to do is to override the leading property with your custom back button, for example:

leading: IconButton(
  icon: Icon(Icons.chevron_left),
  onPressed: () => Navigator.pop(context, false),
),
  
Bram
  • 2,515
  • 6
  • 36
  • 58
Shady Aziza
  • 50,824
  • 20
  • 115
  • 113
  • 4
    Good answer!! This helped me a lot. However, we should use BackButtonIcon() in place of the Chevron to ensure sizing is correct. See Documentation: https://docs.flutter.io/flutter/material/BackButton-class.html and https://docs.flutter.io/flutter/material/BackButtonIcon-class.html – Eric Duffett Apr 05 '19 at 00:36
  • 13
    Android has a back button built into the OS. iOS allows navigating back by swiping right. A custom button in the Scaffold's `leading` property will not handle these other ways of navigating back. – M. Leonhard Dec 05 '19 at 05:09
  • 7
    This will catch back click on the AppBar, but it won't catch back if user pressed system back button at the bottom of the Android phone. – mileusna Jan 31 '20 at 18:19
  • 1
    Shorter: `leading: BackButton(onPressed: () => Navigator.pop(context, false),),` – user3563059 Feb 28 '20 at 06:16
36

This may help and work for you

1st screen

void goToSecondScreen()async {
 var result = await Navigator.push(_context, new MaterialPageRoute(
 builder: (BuildContext context) => new SecondScreen(context),
 fullscreenDialog: true,)
);

Scaffold.of(_context).showSnackBar(SnackBar(content: Text("$result"),duration: Duration(seconds: 3),));
}

2nd screen

Navigator.pop(context, "Hello world");
BINAY THAPA MAGAR
  • 4,017
  • 4
  • 16
  • 24
13

To pop the data and pass data back on navigation, you need to use .then() from screen 1. Below is the example.

Screen 2:

class DetailsClassWhichYouWantToPop {
  final String date;
  final String amount;
  DetailsClassWhichYouWantToPop(this.date, this.amount);
}

void getDataAndPop() {
      DetailsClassWhichYouWantToPop detailsClass = new DetailsClassWhichYouWantToPop(dateController.text, amountController.text);
      Navigator.pop(context, detailsClass); //pop happens here
  }

new RaisedButton(
    child: new Text("Edit"),
    color:  UIData.col_button_orange,
    textColor: Colors.white,
    onPressed: getDataAndPop, //calling pop here
  ),

Screen 1:

    class Screen1 extends StatefulWidget {
          //var objectFromEditBill;
          DetailsClassWhichYouWantToPop detailsClass;

          MyBills({Key key, this.detailsClass}) : super(key: key);

          @override
          Screen1State createState() => new Screen1State();
        }

        class Screen1State extends State<Screen1> with TickerProviderStateMixin {


        void getDataFromEdit(DetailsClassWhichYouWantToPop detailClass) {
        print("natureOfExpense Value:::::: " + detailClass.date);
        print("receiptNumber value::::::: " + detailClass.amount);
      }

      void getDataFromEdit(DetailsClassWhichYouWantToPop detailClass) {
        print("natureOfExpense Value:::::: " + detailClass.natureOfExpense);
        print("receiptNumber value::::::: " + detailClass.receiptNumber);
      }

      void pushFilePath(File file) async {
        await Navigator.push(
          context,
          MaterialPageRoute(
            builder: (context) => Screen2(fileObj: file),
          ),
        ).then((val){
          getDataFromScreen2(val); //you get details from screen2 here
        });
      }
   }
Deepak Thakur
  • 3,453
  • 2
  • 37
  • 65
7

The simplest way to achieve this is to :

In your body take a WillPopScope as the parent widget And on its onWillPop : () {} call

Navigator.pop(context, false);

onWillPop of WillPopScope will be triggered automatically when you’ll press the back button on your AppBar

Mohammad Ersan
  • 12,304
  • 8
  • 54
  • 77
Jaswant Singh
  • 9,900
  • 8
  • 29
  • 50
5

While you can override the back button for custom behaviors, don't.

Instead of overriding the button with a custom pop, you should handle the null scenario. There are a few reasons why you don't want to manually override the icon:

  • The icon change on IOS and Android. On IOS it uses arrow_back_ios while android uses arrow_back
  • The icon may automatically disappear if there's no route to go back
  • Physical back button will still return null.

Instead should do the following:

var result = await Navigator.pushNamed<bool>(context, "/");
if (result == null) {
  result = false;
}
Rémi Rousselet
  • 256,336
  • 79
  • 519
  • 432
  • 1
    How do you return data from the page without calling `Navigator.pop` since the user might have pressed the hardware back button? – Kernel James Nov 24 '21 at 03:11
3

Try this:

void _onBackPressed() {
  // Called when the user either presses the back arrow in the AppBar or
  // the dedicated back button.
}

@override
Widget build(BuildContext context) {
  return WillPopScope(
    onWillPop: () {
      _onBackPressed();
      return Future.value(false);
    },
    child: Scaffold(
      appBar: AppBar(
        leading: IconButton(
          icon: Icon(Icons.arrow_back),
          onPressed: _onBackPressed,
        ),
      ),
    ),
  );
}
CopsOnRoad
  • 237,138
  • 77
  • 654
  • 440
1

Use the below code to get result from your activity.

Future _startActivity() async {

Map results = await Navigator.of(context).push(new MaterialPageRoute(builder: (BuildContext context){
  return new StartActivityForResult();
}));

if (results != null && results.containsKey('item')) {
  setState(() {
    stringFromActivity = results['item'];
    print(stringFromActivity);
  });
}
}

Complete Source Code

import 'package:flutter/material.dart';
import 'activity_for_result.dart';
import 'dart:async';
void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
 return new MaterialApp(
   title: 'Flutter Demo',
   theme: new ThemeData(
    primarySwatch: Colors.blue,
  ),
     home: new MyHomePage(title: 'Start Activity For Result'),
  );
 }
}

class MyHomePage extends StatefulWidget {
 MyHomePage({Key key, this.title}) : super(key: key);
 final String title;

 @override
 _MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
 String stringFromActivity = 'Start Activity To Change Me \n';
 @override
 Widget build(BuildContext context) {
 return new Scaffold(
  appBar: new AppBar(
    title: new Text(widget.title),
  ),
  body: new Center(
    child: new Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        new Text(
          stringFromActivity, style: new TextStyle(fontSize: 20.0), textAlign: TextAlign.center,
        ),
        new Container(height: 20.0,),
        new RaisedButton(child: new Text('Start Activity'),
          onPressed: () => _startActivity(),)
      ],
    ),
  ),
);
}

Future _startActivity() async {

Map results = await Navigator.of(context).push(new MaterialPageRoute(builder: (BuildContext context){
  return new StartActivityForResult();
}));

if (results != null && results.containsKey('item')) {
  setState(() {
    stringFromActivity = results['item'];
    print(stringFromActivity);
  });
 }
 }
}

import 'package:flutter/material.dart';

class StartActivityForResult extends StatelessWidget{

 List<String>list = ['','','','','','','','','',];

 @override
 Widget build(BuildContext context) {
// TODO: implement build

  return new Scaffold(
    appBar: new AppBar(
    title: new Text('Selecte Smily'),
  ),
  body: new ListView.builder(itemBuilder: (context, i){
    return new ListTile(title: new Text(list[i]),
      onTap: (){
        Navigator.of(context).pop({'item': list[i]});
      },
    );
  }, itemCount: list.length,),
  );
 }
}

get complete running example of how to work this from here

Sher Ali
  • 5,513
  • 2
  • 27
  • 29
1

You can pass data/arguments from one screen to other,

consider this example:

screen1.dart:

import 'package:flutter/material.dart';
import 'screen2.dart';

class Screen1 extends StatelessWidget {
  Screen1(this.indx);

  final int indx;

  @override
  Widget build(BuildContext context) {
    return new S1(indx: indx,);
  }
}

class S1 extends StatefulWidget {
  S1({Key key, this.indx}) : super(key: key);

  final int indx;

  @override
  S1State createState() => new S1State(indx);
}

class S1State extends State<VD> {

    int indx = 5;

  @override
  Widget build(BuildContext context) {
   return new Scaffold(
      appBar: new AppBar(
        leading: new IconButton(icon: const Icon(Icons.iconName), onPressed: () {
          Navigator.pushReplacement(context, new MaterialPageRoute(
            builder: (BuildContext context) => new Screen2(indx),
         ));
        }),
    ),
  );
 }
}

Screen 2:

import 'package:flutter/material.dart';
import 'screen2.dart';

class Screen2 extends StatelessWidget {
 Screen2(this.indx);

 final int indx;

 @override
 Widget build(BuildContext context) {
       return new S2(indx: indx,);
    }
 }

 class S2 extends StatefulWidget {
  S2({Key key, this.indx}) : super(key: key);

  final int indx;

  @override
  S2State createState() => new S2State(indx);
  }

 class S2State extends State<VD> {

 int indx = 1;

  @override
      Widget build(BuildContext context) {
       return new Scaffold(
          appBar: new AppBar(
            leading: new IconButton(icon: const Icon(Icons.Icons.arrow_back), onPressed: () {
              Navigator.pushReplacement(context, new MaterialPageRoute(
                builder: (BuildContext context) => new Screen1(indx),
             ));
            }),
        ),
      );
     }
    }

To pass data between Screens, pass the argument/data to the Screens constructor in Navigator.pushReplacement().You can pass as many argument as you want.

This line

Navigator.pushReplacement(context, new MaterialPageRoute(
                    builder: (BuildContext context) => new Screen1(indx),
                 ));

will go to Screen1 and call initState and build method of Screen1 so that you can get updated values.

ArgaPK
  • 455
  • 1
  • 9
  • 22
0

First, Remove the automatically appended back button (see this answer)

Then, create your own back button. like this:

IconButton(
    onPressed: () => Navigator.pop(context, false),
    icon: Icon(Icons.arrow_back),
    )
Yamin
  • 2,868
  • 1
  • 19
  • 22