Flutter Back button with return data - flutter

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?

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

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

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

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

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

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

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

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

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

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.

Related

Handle Android system back button in Flutter app with nested navigators

I have a Flutter app composed of "sub-apps" with different theme colors.
In a sub-app, the AppBar's back button is working as expected: it navigates back to the previous sub-app page.
However the Android system's back button is NOT working as expected: it navigates directly to the root app.
The expected back navigation in the following example should be: B3 B2 B1 A3 A2 A1. But it's B3 A3 A2 A1.
In other words, I want the Android back button to work the same way as the Flutter back button.
The same problem happens with iOS "back swipe" gesture (iosPageTransition = true).
Please try the following code on an Android device or emulator and test with the system's back button.
Note that I use multiple MaterialApps to apply a color theme to all screens of a sub-app.
Also note that WillPopScope doesn't work since it's not triggered by the system's back button.
import 'package:flutter/material.dart';
void main() {
runApp(_AppA(_PageA(1)));
}
class _ColoredApp extends StatelessWidget {
final Color color;
final Widget home;
final iosPageTransition = false;
_ColoredApp(this.color, this.home);
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
colorScheme: ColorScheme.light(primary: color),
pageTransitionsTheme: iosPageTransition
? PageTransitionsTheme(
builders: Map.fromIterable(
TargetPlatform.values,
value: (_) => const CupertinoPageTransitionsBuilder(),
),
)
: null,
),
home: home,
);
}
}
class _AppA extends _ColoredApp {
_AppA(Widget home) : super(Colors.red, home);
}
class _AppB extends _ColoredApp {
_AppB(Widget home) : super(Colors.green, home);
}
class _PageA extends StatelessWidget {
final int number;
const _PageA(this.number);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('A$number')),
body: Center(
child: ElevatedButton(
child: Text('Next'),
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) {
if (number > 2) return _AppB(_PageB(1));
return _PageA(number + 1);
}));
},
),
),
);
}
}
class _PageB extends StatelessWidget {
final int number;
const _PageB(this.number);
#override
Widget build(BuildContext context) {
var scaffold = Scaffold(
appBar: AppBar(
leading: number == 1 ? BackButton(onPressed: () => Navigator.of(context, rootNavigator: true).pop()) : null,
title: Text('B$number'),
),
body: Center(
child: ElevatedButton(
child: Text('Next'),
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return _PageB(number + 1);
}));
},
),
),
);
return scaffold;
}
}
First of all you can't set two MaterialApp in single flutter app its wrong way to use it, in your code it initialize 2 times.Just change your theme from the page you want to update,
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
final iosPageTransition = false;
#override
Widget build(BuildContext context) {
return MaterialApp(
home: _PageA(1),
);
}
}
class _PageA extends StatelessWidget {
final int number;
const _PageA(this.number);
#override
Widget build(BuildContext context) {
return
Theme(
data: ThemeData(
colorScheme: ColorScheme.light(primary: Colors.red),
),
child: Builder(
builder: (context) {
return Scaffold(
appBar: AppBar(title: Text('A$number')),
body: Center(
child: ElevatedButton(
child: Text('Next'),
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) {
if (number > 2) return _PageB(1);
return _PageA(number + 1);
}));
},
),
),
);
}
),
);
}
}
class _PageB extends StatelessWidget {
final int number;
const _PageB(this.number);
#override
Widget build(BuildContext context) {
return
Theme(
data: ThemeData(
colorScheme: ColorScheme.light(primary: Colors.blue),
),
child: Builder(
builder: (context) {
return Scaffold(
appBar: AppBar(
leading: BackButton(onPressed: () => Navigator.pop(context)) ,
title: Text('B$number'),
),
body: Center(
child: ElevatedButton(
child: Text('Next'),
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return _PageB(number + 1);
}));
},
),
),
);
}
),
);
}
}
As Dharini said you cannot use 2 material apps, but if you really want to, then here is the workaround for you
Issue: When you try to B(n) it pops the B as it is new material app.
|
|_ A
|_ A1
|_ A2
|_ B
|_ B1
|_B2
Resolution: Pass the BuildContext and Route from B to A and remove route on back tap of icon or android back key.
Navigator.removeRoute(context, route);
Workaround
import 'package:flutter/material.dart';
void main() {
runApp(_AppA(_PageA(1)));
}
class _ColoredApp extends StatelessWidget {
final Color color;
final Widget home;
final iosPageTransition = false;
const _ColoredApp(this.color, this.home);
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
colorScheme: ColorScheme.light(primary: color),
pageTransitionsTheme: iosPageTransition
? PageTransitionsTheme(
builders: Map.fromIterable(
TargetPlatform.values,
value: (_) => const CupertinoPageTransitionsBuilder(),
),
)
: null,
),
home: home,
);
}
}
class _AppA extends _ColoredApp {
const _AppA(Widget home) : super(Colors.red, home);
}
class _AppB extends _ColoredApp {
const _AppB(Widget home) : super(Colors.green, home);
}
class _PageA extends StatelessWidget {
final int number;
_PageA(this.number);
final bRoutes = <BuildContext, Route>{};
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('A$number')),
body: Center(
child: ElevatedButton(
child: const Text('Next'),
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) {
if (number > 2) {
return WillPopScope(
onWillPop: () async {
if (number > 2 && bRoutes.isNotEmpty) {
Navigator.removeRoute(bRoutes.entries.last.key, bRoutes.entries.last.value);
bRoutes.remove(bRoutes.entries.last.key);
return false;
} else {
return true;
}
},
child: _AppB(_PageB(1, bRoutes)),
);
}
return _PageA(number + 1);
}));
},
),
),
);
}
}
class _PageB extends StatelessWidget {
final int number;
final Map<BuildContext, Route> bRoutes;
const _PageB(this.number, this.bRoutes);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
leading: BackButton(onPressed: () {
if (bRoutes.isNotEmpty) {
final context = bRoutes.entries.last.key;
final route = bRoutes.entries.last.value;
bRoutes.remove(bRoutes.entries.last.key);
Navigator.removeRoute(context, route);
}
})
,
title: Text('B$number'),
),
body: Center(
child: ElevatedButton(
child: const Text('Next'),
onPressed: () {
final route = MaterialPageRoute(builder: (context) {
return _PageB(number + 1, bRoutes);
});
bRoutes[context] = route;
Navigator.push(context, route);
},
),
),
);
}
}

How to Passing Data from Navigator Pop to Previous Page Where The Data is Used in The Widget Inside the ListView.builder

As stated in the title. How to return data to the previous page where the data is used to list widgets.
I have read this article Flutter Back button with return data or other similar articles. The code works perfectly. But there is a problem if I want to use the data returned to the widget that is in the list.\
Note that I only want to update one ListWidget, I don't want to refresh the state of the entire HomePage like the solution in this article Flutter: Refresh on Navigator pop or go back.
Here is a simple code sample to represent the problem I'm facing.
(check on ListWidget Class and SecondPage Class below)
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HomePage(),
);
}
}
HomePage class
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home'),
),
body: Center(
child: ListView.builder(
itemCount: 4,
itemBuilder: (_, index){
return ListWidget(number: index+1);
},
)
),
);
}
}
ListWidget Class
class ListWidget extends StatelessWidget{
ListWidget({#required this.number});
final int? number;
String? statusOpen;
#override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () async {
statusOpen = await Navigator.of(context, rootNavigator: true)
.push(
MaterialPageRoute(
builder: (BuildContext context) => SecondPage(),
),
);
},
child: Container(
margin: EdgeInsets.all(10),
padding: EdgeInsets.all(20),
color: Colors.amber,
child: Text(statusOpen != null ? '$number $statusOpen' : '$number Unopened'),
//
// I want to change the text here to 'has Opened' when the user returns from SecondPage
//
),
);
}
}
SecondPage Class
class SecondPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Second Page'),
),
body: Center(
child: RaisedButton(
onPressed: () {
Navigator.pop(context, 'has Opened');
// return 'has Opened' to await statusOpen variable
},
child: Text('Go Back'),
),
),
);
}
}
is there any solution to handle this?
If you make your listWidget a stateful widget, then you can get the solution where you just need to call setState when you return to your previous screen. And in this way you will be only changing your single list element and not the full screen.
sample code:
changing this line- class ListWidget extends StatefulWidget
and adding these lines -
onTap: () async {
statusOpen = await Navigator.of(context, rootNavigator: true)
.push(
MaterialPageRoute(
builder: (BuildContext context) => SecondPage(),
),
);
setState(() {
});
},
If you used the data in your listview just call setstate after Navigator.pop like below code
onTap: () async {
statusOpen = await Navigator.of(context, rootNavigator: true)
.push(
MaterialPageRoute(
builder: (BuildContext context) => SecondPage(),
),
).then((value) async {
setState(() {});
});
},

Flutter Retrieve Value from TextField

I'm trying to get the value from the TextField then compare it to a string = '123'
If the value = '123' then alert 'Successful!' or else alert 'Failed!'
Here is my code:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Retrieve Text Input',
home: MyCustomForm(),
);
}
}
class MyCustomForm extends StatefulWidget {
#override
_MyCustomFormState createState() => _MyCustomFormState();
}
class _MyCustomFormState extends State<MyCustomForm> {
TextEditingController myController = new TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Retrieve Text Input'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: TextField(
controller: myController,
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
if(myController == '123')
{
showDialog(
context: context,
builder: (context) {
return AlertDialog(
content: Text('Successful!'),
);
},
);
}
else
{
showDialog(
context: context,
builder: (context) {
return AlertDialog(
content: Text('Failed!'),
);
},
);
}
},
child: Icon(Icons.text_fields),
),
);
}
}
My code always shows failed even I entered '123'. Can someone help me to figure out what I did wrong here? Thank you
To access the text inside a TextField Controller, you need to use myController.text, the myController is just a controller itself so you can't compare it to a String like that.
So, you should change this line
if(myController == '123')
into this
if(myController.text == '123')

How can i pass parameters in flutter if the user goes back with the arrow?

I know that if you had a raiseButton you can do
Navigation .... .pop(value);
But what happens if the user goes back and i want to update the value, because result will be null
Navigator.push(context, MaterialPageRoute(builder: (context) {
return GalleryClassOne();
})).then((result) {
if (result != null) {
setState(() {
imagesClas1 = result;
});
}
});
You can override the back button behavior with WillPopScope widget. And manually pop with the data you need. Here is the code:
import 'package:flutter/material.dart';
void main() async {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: Navigator(
onGenerateRoute: (settings) => MaterialPageRoute(
builder: (context) => MyHomePage(),
),
),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
void _onButtonPressed() {
Navigator.of(context)
.push(MaterialPageRoute(builder: (context) => OtherPage()))
.then((value) {
print("returned: $value");
if (value != null) {
setState(() {
// ...
});
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Demo")),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
child: Text("Open another screen"),
onPressed: _onButtonPressed),
],
),
),
);
}
}
class OtherPage extends StatelessWidget {
OtherPage({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
// here you can return anything you need ...
Navigator.of(context).pop("my value");
// cancel default behaviour
return false;
},
child: Scaffold(
appBar: AppBar(title: Text("Other page")),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Click on return button'),
],
),
), // This trailing comma makes auto-formatting nicer for build methods.
),
);
}
}
You should return your data at a variable like this
final result = await Navigator.push(
context,
MaterialPageRoute(builder: (context) => SelectionScreen()),
);
The result variable has your data.
for more info, have a look at the docs

How to do Navigator.popUntil properly when using different animations for each push

I am trying to rebuild iOS app in Flutter, but facing a problem with navigation.
Here what I am trying to do:
List of Added Exchange Pairs with Add button (A screen)
Add button opens Picker with Exchanges (B screen) with transition from bottom to top.
By tapping on exchange it pushes new Picker with Pairs (C
screen) with transition from right to left.
when user taps on pair it closes all pickers at once and deliver result of picking to A screen.
I have tried double pop and popUntil but result always same, I see 2 back transitions (left to right and top to bottom) at same time.
How it looks in iOS native app:
How it looks in Flutter app:
Solved with nested Navigator
Wrapped Screen B with Navigator and used this navigator to push screen C, on screen C used root navigator to pop. Result is below:
Here the example of how I solved it:
import 'package:flutter/material.dart';
void main() {
MaterialPageRoute.debugEnableFadingRoutes = true;
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
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 _result = "--";
void _openSubscreen() {
Navigator.of(context).push<String>(
new MaterialPageRoute(
settings: RouteSettings(name: '/subscreen'),
builder: (context) => SubScreen(),
),
).then((result) => setState((){
_result = result;
}));
}
#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(
'Result from navigator:',
),
new Text(
_result,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.headline,
),
SizedBox(height: 32.0,),
OutlineButton(
onPressed: _openSubscreen,
child: Text('Start flow'),
),
],
),
),
);
}
}
class SubScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: new Navigator(
onGenerateRoute: (routeSettings) {
final path = routeSettings.name;
if (path == '/') {
return new MaterialPageRoute(
settings: routeSettings.copyWith(isInitialRoute: true),
builder: (_) => SubScreenPage1(),
);
} else if (path == '/nexpage') {
return new MaterialPageRoute(
settings: routeSettings,
builder: (_) => SubScreenPage2(),
);
}
},
),
);
}
}
class SubScreenPage1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Center(
child: OutlineButton(
child: Text('Next sub page!'),
onPressed: () {
Navigator.of(context).pushNamed('/nexpage');
},
),
);
}
}
class SubScreenPage2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Center(
child: OutlineButton(
child: Text('Deliver result!'),
onPressed: () {
final date = DateTime.now().toString();
Navigator
.of(context, rootNavigator: true)
.pop('Delivered at $date');
},
),
);
}
}
When you build your MaterialApp by setting home: and routes: you can achieve "pop to root" without hardcoding what route to pop until by;
Navigator.popUntil(
context,
ModalRoute.withName(Navigator.defaultRouteName),
);
Because Navigator.defaultRouteName will be set to whatever you set home: to.
Going a bit off-topic but, this is especially nice if you have "variable" home screen, as in using a FutureBuilder to decide what will be the home screen. For example, if you are showing a splash screen until you are loading the initial state from disk.
home: isUserLoggedIn
? HomePage()
: FutureBuilder(
future: () async {
print('Initializing');
print('Waiting For NoReason');
await Future.delayed(const Duration(seconds: 1));
print('Initialization Complete');
}(),
builder: (_, snap) {
if (snap.connectionState == ConnectionState.waiting) {
return SplashPage();
} else {
return LogInPage();
}
},
),