How do I pass a variable by value to Flutter MaterialPageRoute? - flutter

I have a loop index that is creating ~20 ListTiles that tap to a second screen that reference its index. However it looks like it's passing by reference since the value is always the same on the second screen
user defined upper_bound
...
for(int i=0; i<upper_bound;i++)
{
...
Container -> ListTile ->
title: GestureDetector(
onTap: () async {
var returnData = await Navigator.push(
context,
MaterialPageRoute(builder: (context) =>
SecondScreen(
index: i,
))
);}
}
In this situation, the second screen always receives index as upper_bound and not the value I'd expect which is the value at the time of the loop. How can I pass the current value of the index?

in the first page/screen
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("ListTile Example"),
),
body: new ListView(
children: new List.generate(20, (int index) {
return new ListTile(
onTap: () {
Navigator.of(context).push(
PageRouteBuilder(
opaque: false,
pageBuilder: (BuildContext context, _, __) => NextPage(
number: index,
),
),
);
},
title: new Text(
"Index No #$index",
style: new TextStyle(fontWeight: FontWeight.w500, fontSize: 25.0),
),
subtitle: new Text("My subtitle is"),
);
}),
),
);
}
in the next or second page
import 'package:flutter/material.dart';
class NextPage extends StatefulWidget {
final int number;
NextPage({
Key key,
#required this.number,
}) : super(key: key);
#override
_NextPageState createState() => _NextPageState();
}
class _NextPageState extends State<NextPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text(widget.number.toString()),
),
);
}
}

Related

PushNamed issue: Type 'FillData' (a Statefulwidget) is not a subtype of type 'List<Object>'

I'm new in Flutter. I'm trying to push a List from NewData to FillData screen with pushNamed. But it said:
The following _TypeError was thrown while handling a gesture:
type 'FillData' is not a subtype of type 'List'
If i remove the comment in '/FillData', i receive null data instead. What should i do?
This is my code:
SettingNavigator
class SettingNavigator extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: '/',
routes: {
'/': (context) => Home(),
'/NewData': (context) => NewData(),
// '/FillData': (context) => FillData(), (in comment)
}
onGenerateRoute: (setting) {
if (setting.name == '/FillData') {
final ChartGroupData chartName = setting.arguments;
final List<ChartGroupData> groupNames = setting.arguments;
return MaterialPageRoute(builder: (context) {
return FillData(
chartName: chartName,
gName: groupNames,
);
});
}
return null;
},
);
}
}
NewData
import 'package:flutter/material.dart';
class NewData extends StatefulWidget {
List<ChartGroupData> groupNames;
NewData({Key key, #required this.groupNames}) : super(key: key);
#override
NewDataStage createState() => NewDataStage();
}
class NewDataStage extends State<NewData> {
TextEditingController _nameCtrl = new TextEditingController();
var textFields = <Widget>[];
var groupTECs = <TextEditingController>[];
#override
void initState() {
super.initState();
textFields.add(createCustomTextField());
}
Widget createCustomTextField() {
var groupCtrl = TextEditingController();
groupTECs.add(groupCtrl);
return Container(
padding: EdgeInsets.fromLTRB(0, 5, 0, 0),
child: Row(
children: <Widget>[
Expanded(flex: 3, child: Text("Group ${textFields.length}")),
Container(
constraints: BoxConstraints.tightFor(width: 120, height: 60),
child: TextField(
controller: groupCtrl,
),
),
],
),
);
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Center(child: Text("New Chart")),
),
body: Container(
alignment: AlignmentDirectional.center,
constraints: BoxConstraints.expand(),
child: Column(
children: <Widget>[
Text(
"Your chart name",
style: TextStyle(fontSize: 25, fontWeight: FontWeight.bold),
),
TextField(
style: TextStyle(fontSize: 20),
controller: _nameCtrl,
),
Expanded(
flex: 3,
child: Container(
child: ListView.builder(
shrinkWrap: true,
itemCount: textFields.length,
itemBuilder: (BuildContext context, int index) {
return textFields[index];
},
),
),
),
SizedBox(
height: 60,
width: 120,
child: RaisedButton(
onPressed: _onTapNext,
child: Text("NEXT"),
color: Colors.green,
),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _onTapCreate,
child: Icon(Icons.add, color: Colors.white),
shape: CircleBorder(),
),
),
);
}
void _onTapNext() {
/// Push Groups name to FillData
widget.groupNames = List<ChartGroupData>();
for (int i = 0; i < textFields.length; i++) {
var name = groupTECs[i].text;
widget.groupNames.add(ChartGroupData(name));
}
print(widget.groupNames.toString());
Navigator.pushNamed(context, '/FillData',
arguments: FillData(
gName: widget.groupNames,
chartName: ChartGroupData(_nameCtrl.text),
));
}
void _onTapCreate() {
setState(() {
textFields.add(createCustomTextField());
});
}
}
FillData
class FillData extends StatefulWidget {
final ChartGroupData chartName;
final List<ChartGroupData> gName;
FillData({Key key, #required this.chartName, #required this.gName})
: super(key: key);
#override
FillDataStage createState() => FillDataStage();
}
class FillDataStage extends State<FillData> {
void _showDialog() {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text("Received Data"),
content: Text(widget.chartName.toString()),
);
},
);
}
void _onTapPrintReceivedData() {
print(widget.gName);
print(widget.chartName);
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Center(
child: Text("Fill your Data"),
),
),
body: Center(
child: RaisedButton(
onPressed: () {
_onTapPrintReceivedData();
_showDialog();
},
child: Text("Print Data"),
),
),
));
}
}
Class ChartGroupData
lass ChartGroupData {
final String groupNames;
ChartGroupData(this.groupNames);
#override
String toString() {
return 'Group: $groupNames';
}
}
You have 2 problems with your code:
1- you cant user routes with onGenerateRoute, because now the app doesn't know where to go, to the widget that you didn't pass anything to (inside routes) or to the widget inside the onGenerateRoute.
2- arguments is a general object that you can put whatever you want inside of it, and doing this:
final ChartGroupData chartName = setting.arguments; final
List groupNames = setting.arguments;
passes the same value to two different objects, I solved this by doing the following (it's not the best but will give you a rough idea of what you should do)
created a new object that contains the data to be passed:
class ObjectToPass {
final ChartGroupData chartName;
final List<ChartGroupData> groupNames;
ObjectToPass({this.chartName, this.groupNames});
}
changed FillData implementation:
class FillData extends StatefulWidget {
final ObjectToPass objectToPass;
FillData({Key key, #required this.objectToPass}) : super(key: key);
#override
FillDataStage createState() => FillDataStage();
}
...
void _showDialog() {
showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: Text("Received Data"),
content: Text(widget.objectToPass.chartName.toString()),
);
},
);
}
void _onTapPrintReceivedData() {
print(widget.objectToPass.groupNames);
print(widget.objectToPass.chartName);
}
to navigate to FillData you would:
Navigator.pushNamed(
context,
'/FillData',
arguments: ObjectToPass(
chartName: ChartGroupData(_nameCtrl.text),
groupNames: groupNames,
),
);
finally this is how your MaterialApp should look like:
return MaterialApp(
initialRoute: '/NewData',
onGenerateRoute: (setting) {
if (setting.name == '/FillData') {
return MaterialPageRoute(builder: (context) {
return FillData(
objectToPass: setting.arguments,
);
});
} else if (setting.name == '/NewData') {
return MaterialPageRoute(builder: (_) => NewData());
}
return null;
},
);
you can pass a list instead of the object I created and get your objects from it by it's index.

Return the list of selected items, in the CheckBox, to the TabBar main screen Flutter

I have an app with two tabs. One for the "all items" list and second for the "favourite/saved items". The second tab has a FAB and text written "Add your favorite items here" inside the children of the Column widget. So when the FAB is clicked, Navigator.push() works and triggers a second screen for "selecting favorite items" by the use of CheckBox widget. I've made an empty list _saved (its actually a Set to avoid duplicates) to store the items that are to be selected. And in the 'select favorite items screen' there is also a FAB, which when clicked, Navigator.pop() works and SHOULD RETURN THE _saved LIST. And this is the only problem I'm facing. I'm just not able to implement it.
Also as I mentioned above some text is written in the "Saved Items" tab, I want to build something like
"If items selected, just show the items and not the (before mentioned) Text! If none selected anything, just return the Text."
You guys can check the entire code here.
The code where I'm facing issues:
class SecondPage extends StatefulWidget {
#override
_SecondPageState createState() => _SecondPageState();
}
class _SecondPageState extends State<SecondPage> {
#override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'Add Your Favorite Sites Here!❀',
style: TextStyle(color: Colors.white),
),
Container(
child: Icon(Icons.favorite, size: 150, color: Colors.blue[100]),
),
SizedBox(height: 250),
FloatingActionButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => FavoriteList(),
),
);
},
child: Icon(Icons.add),
foregroundColor: Colors.blue,
),
],
);
}
}
//The Favorite List Code:
final Set _saved = Set();
class FavoriteList extends StatefulWidget {
#override
_FavoriteListState createState() => _FavoriteListState();
}
class _FavoriteListState extends State<FavoriteList> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Add to Favorites!'),
centerTitle: true,
backgroundColor: Colors.red),
// backgroundColor: Colors.indigo,
body: SafeArea(
child: ListView.builder(
itemCount: 53,
itemBuilder: (context, index) {
return CheckboxListTile(
activeColor: Colors.red,
checkColor: Colors.white,
// value: _saved.contains(context), // changed
value: _saved.contains(index),
onChanged: (val) {
setState(() {
// isChecked = val; // changed
// if(val == true){ // changed
// _saved.add(context); // changed
// } else{ // changed
// _saved.remove(context); // changed
// } // changed
if (val == true) {
_saved.add(index);
} else {
_saved.remove(index);
}
});
},
title: Row(
children: <Widget>[
Image.asset('lib/images/${images[index]}'),
SizedBox(
width: 10,
),
Text(nameOfSite[index]),
],
),
);
},
),
),
floatingActionButton: FloatingActionButton(
foregroundColor: Colors.red,
child: Icon(Icons.check),
onPressed: () {
Navigator.pop(context, _saved);
},
),
);
}
}
this is demo code, you can make your customize code using below code
class checkModel{
String nameOfSite;
bool isCheck;
checkModel(this.nameOfSite, this.isCheck);
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<checkModel> _list = new List();
#override
void initState() {
// TODO: implement initState
super.initState();
_list.add(checkModel("title1", false));
_list.add(checkModel("title2", false));
}
#override
Widget build(BuildContext context) {
SizeConfig().init(context);
return Scaffold(
body: ListView.builder(
itemCount: _list.length,
itemBuilder: (context, index) {
return CheckboxListTile(
activeColor: Colors.red,
checkColor: Colors.white,
// value: _saved.contains(context), // changed
value: _list[index].isCheck,
onChanged: (val) {
print("object ${val}");
setState(() {
_list[index].isCheck = val;
});
},
title: Text(_list[index].nameOfSite),
);
},
),
);
}
}
Replace:
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => FavoriteList(),
),
);
With:
Navigator.push<Set>(
context,
MaterialPageRoute(
builder: (context) => FavoriteList(),
),
).then((Set _saved){
print(_saved);
});
And see logs, you have the Set of saved items.

How to change state of MaterialPageRoute?

I was following the tutorial from the Flutter docs where you create a Startup naming app. The app consists in two pages: one where there's an infinite list of randomly generated startup names that you can add to your favorites, and a favorites page where you can see the names you saved.
After completing the tutorial, I tried to add some functionality of my own, I wanted to be able to Unfavorite a name by tapping it on the "Favorites" page. Below is the code that pushes the Favorites page to the navigator:
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (BuildContext context) {
final Iterable<ListTile> tiles = _saved.map(
(WordPair pair) {
return ListTile(
title: Text(
pair.asPascalCase,
style: _biggerFont,
),
// Code I added //
trailing: Icon(Icons.delete),
onTap: () {
setState(() {
_saved.remove(pair);
});
},
// End //
);
},
);
final List<Widget> divided = ListTile
.divideTiles(
context: context,
tiles: tiles,
)
.toList();
return Scaffold(
appBar: AppBar(
title: Text('Saved suggestions'),
),
body: ListView(children: divided),
);
},
),
);
}
But it didn't worked as it should: you can indeed unsave names by tapping them, but the changes will only be shown on the screen after you go back to the main page and then to the favorites page again (or in other words, when Builder is called?).
So how do I fix this? Do I need to create a Stateful widget for the favorites page? If yes, how do I pass the _saved set to my new widget?
If anybody needs the whole code:
https://pastebin.com/asLneaKe
Wrap with StatefulBuilder works fine.
You can see full code and working demo
code snippet
MaterialPageRoute<void>(
builder: (BuildContext context) {
return StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
final Iterable<ListTile> tiles = _saved.map(
working demo
full code
import 'package:english_words/english_words.dart' as prefix0;
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Startup Name Generator',
theme: ThemeData(
primaryColor: Colors.white,
),
home: RandomWords(),
);
}
}
class RandomWords extends StatefulWidget {
#override
RandomWordsState createState() => RandomWordsState();
}
class RandomWordsState extends State<RandomWords> {
final List<WordPair> _suggestions = <WordPair>[];
final Set<WordPair> _saved = Set<WordPair>();
final TextStyle _biggerFont = const TextStyle(fontSize: 18.0);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Startup Name Generator'), actions: <Widget>[
// Icone 3 linhas
IconButton(
icon: Icon(Icons.list),
onPressed: _pushSaved,
),
]),
body: _buildSuggestions(),
);
}
Widget _buildRow(WordPair pair) {
final bool alreadySaved = _saved.contains(pair);
return ListTile(
title: Text(
pair.asPascalCase,
style: _biggerFont,
),
trailing: Icon(
alreadySaved ? Icons.favorite : Icons.favorite_border,
color: alreadySaved ? Colors.red : null,
),
onTap: () {
setState(() {
if (alreadySaved) {
_saved.remove(pair);
} else {
_saved.add(pair);
}
});
});
}
Widget _buildSuggestions() {
return ListView.builder(
padding: const EdgeInsets.all(16.0),
itemBuilder: (context, i) {
if (i.isOdd) return Divider();
final index = i ~/ 2;
if (index >= _suggestions.length) {
_suggestions.addAll(generateWordPairs().take(10));
}
return _buildRow(_suggestions[index]);
},
);
}
void _pushSaved() {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (BuildContext context) {
return StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
final Iterable<ListTile> tiles = _saved.map(
(WordPair pair) {
return ListTile(
title: Text(
pair.asPascalCase,
style: _biggerFont,
),
// Code I added //
trailing: Icon(Icons.delete),
onTap: () {
setState(() {
_saved.remove(pair);
});
},
// End //
);
},
);
final List<Widget> divided = ListTile.divideTiles(
context: context,
tiles: tiles,
).toList();
return Scaffold(
appBar: AppBar(
title: Text('Saved suggestions'),
),
body: ListView(children: divided),
);
});
},
),
);
}
}

Best practices for layouts with AppBar and Drawer: re-use vs "copy/paste"

I'm pretty new to Flutter and am looking for some "best practice" advice when it comes to building page layouts. I come from a Java background where I've always re-used as much as possible, but I'm not sure that's really the best approach here. I have several pages that will all have an Appbar but with their own actions. Each of these pages will share a common Drawer. Initially, I started going down the path of creating a common root page Widget where, when selecting an item in the drawer, the body of the common page changes, like this:
class HomePage extends StatefulWidget {
final BaseAuth auth;
final Function onSignedOut;
const HomePage({Key key, this.auth, this.onSignedOut}) : super(key: key);
#override
State<StatefulWidget> createState() => new _HomePageState();
}
class _HomePageState extends State<HomePage> {
final drawerItems = [
new DrawerItem("Home", Icons.home),
new DrawerItem("Pantry", Icons.store),
new DrawerItem("Barcode Scanner", Icons.scanner)
];
int _selectedDrawerIndex = 0;
bool _isEmailVerified;
_getDrawerItemWidget(int pos) {
switch (pos) {
case 0:
return new HomePageFragment();
case 1:
return new UserPantryFragment();
case 2:
return new BarcodeScannerFragment();
default:
return new Text("Error");
}
}
_onSelectItem(int index) {
setState(() => _selectedDrawerIndex = index);
Navigator.of(context).pop(); // close the drawer
}
#override
Widget build(BuildContext context) {
var drawerOptions = <Widget>[];
for (var i = 0; i < drawerItems.length; i++) {
var d = drawerItems[i];
drawerOptions.add(new ListTile(
leading: new Icon(d.icon),
title: new Text(d.title),
selected: i == _selectedDrawerIndex,
onTap: () => _onSelectItem(i),
));
}
AuthenticationContext authenticationContext =
AuthenticationContext.of(context);
return new FutureBuilder<FirebaseUser>(
future: authenticationContext.auth.getCurrentUser(),
initialData: null,
builder: (BuildContext context, AsyncSnapshot<FirebaseUser> data) {
var name = data.data != null ? data.data.displayName : "";
var email = data.data != null ? data.data.email : " ";
var photoUrl = data.data != null && data.data.photoUrl != null
? data.data.photoUrl
: null;
return new Scaffold(
appBar: new AppBar(
title: new Text(drawerItems[_selectedDrawerIndex].title),
actions: <Widget>[
IconButton(
icon: Icon(Icons.search),
onPressed: () {
},
),
// overflow menu
PopupMenuButton<String>(
// onSelected: _signOut,
itemBuilder: (BuildContext context) {
return ['Logout'].map((String choice) {
return PopupMenuItem<String>(
value: choice,
child: Text(choice),
);
}).toList();
},
)
]),
drawer: new Drawer(
child: new Column(
children: <Widget>[
UserAccountsDrawerHeader(
accountName: Text(name != null ? name : ""),
accountEmail: Text(email),
currentAccountPicture: CircleAvatar(
// backgroundImage: FadeInImage.memoryNetwork(
// placeholder: kTransparentImage,
// image: photoUrl != null ? photoUrl : "",
// ).image,
child: new Text(
photoUrl == null ? email[0].toUpperCase() : ""),
),
),
new Column(children: drawerOptions)
],
),
),
body: _getDrawerItemWidget(_selectedDrawerIndex));
});
}
However, I'm now wondering if it would be better to just create the Scaffold from scratch in each screen and not try to use a shared root page as I'm running into issues with easily customizing the AppBar for each page. I was initially thinking I could just create some "buildAppBar" function on each of the page Widgets and have the root page use that, but that does not seem to be an easily achievable solution...at least not in an elegant way that I can find.
You could extend StatelessWidget to add custom params to the class and return a customized Scaffold in the build method. Something along the lines of:
class MyScaffold extends StatelessWidget {
final Widget option1;
final Widget option2;
final Widget body;
const MyScaffold({
this.option1,
this.option2,
this.body,
Key key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: option1,
drawer: option2,
body: body,
);
}
}
You can also copy other properties from the Scaffold class and add them as members to MyScaffold (remember to initialize them in the constructor like the body and options params).
Another option to pass state (read: variables) down the widget tree is InheritedWidget
Create a separate widget for the drawer and just use in anywhere you need to.
Manage the Drawer State with a Provider to Manage State
class DrawerStateInfo with ChangeNotifier {
int _currentDrawer = 0;
int get getCurrentDrawer => _currentDrawer;
void setCurrentDrawer(int drawer) {
_currentDrawer = drawer;
notifyListeners();
}
void increment() {
notifyListeners();
}
}
Adding State Management to the Widget tree
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MultiProvider(
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.teal,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
),
providers: <SingleChildCloneableWidget>[
ChangeNotifierProvider<DrawerStateInfo>(
builder: (_) => DrawerStateInfo()),
],
);
}
}
Creating The Drawer Widget for reuse in application
class MyDrawer extends StatelessWidget {
MyDrawer(this.currentPage);
final String currentPage;
#override
Widget build(BuildContext context) {
var currentDrawer = Provider.of<DrawerStateInfo>(context).getCurrentDrawer;
return Drawer(
child: ListView(
children: <Widget>[
ListTile(
title: Text(
"Home",
style: currentDrawer == 0
? TextStyle(fontWeight: FontWeight.bold)
: TextStyle(fontWeight: FontWeight.normal),
),
trailing: Icon(Icons.arrow_forward),
onTap: () {
Navigator.of(context).pop();
if (this.currentPage == "Home") return;
Provider.of<DrawerStateInfo>(context).setCurrentDrawer(0);
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (BuildContext context) =>
MyHomePage(title: "Home")));
},
),
ListTile(
title: Text(
"About",
style: currentDrawer == 1
? TextStyle(fontWeight: FontWeight.bold)
: TextStyle(fontWeight: FontWeight.normal),
),
trailing: Icon(Icons.arrow_forward),
onTap: () {
Navigator.of(context).pop();
if (this.currentPage == "About") return;
Provider.of<DrawerStateInfo>(context).setCurrentDrawer(1);
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (BuildContext context) => MyAboutPage()));
},
),
],
),
);
}
}
Use of Drawer in one of your pages
class MyAboutPage extends StatefulWidget {
#override
_MyAboutPageState createState() => _MyAboutPageState();
}
class _MyAboutPageState extends State<MyAboutPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('About Page'),
),
drawer: MyDrawer("About"),
);
}
}

Flutter Back button with return data

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.