Related
As I'm learning Flutter I've come to navigation. I want to pass data between screens similarly to passing data between Activities in Android and passing data between View Controllers in iOS. How do I do it in Flutter?
Related questions:
The best way to passing data between widgets in Flutter
Flutter pass data between widgets?
Flutter/ How to pass and get data between Statefulwidget
This answer will cover both passing data forward and passing data back. Unlike Android Activities and iOS ViewControllers, different screens in Flutter are just widgets. Navigating between them involves creating something called a route and using the Navigator to push and pop the routes on and off the stack.
Passing data forward to the next screen
To send data to the next screen you do the following things:
Make the SecondScreen constructor take a parameter for the type of data that you want to send to it. In this particular example, the data is defined to be a String value and is set here with this.text.
class SecondScreen extends StatelessWidget {
final String text;
SecondScreen({Key key, #required this.text}) : super(key: key);
...
Then use the Navigator in the FirstScreen widget to push a route to the SecondScreen widget. You put the data that you want to send as a parameter in its constructor.
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SecondScreen(text: 'Hello',),
));
The full code for main.dart is here:
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
title: 'Flutter',
home: FirstScreen(),
));
}
class FirstScreen extends StatefulWidget {
#override
_FirstScreenState createState() {
return _FirstScreenState();
}
}
class _FirstScreenState extends State<FirstScreen> {
// this allows us to access the TextField text
TextEditingController textFieldController = TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('First screen')),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.all(32.0),
child: TextField(
controller: textFieldController,
style: TextStyle(
fontSize: 24,
color: Colors.black,
),
),
),
RaisedButton(
child: Text(
'Go to second screen',
style: TextStyle(fontSize: 24),
),
onPressed: () {
_sendDataToSecondScreen(context);
},
)
],
),
);
}
// get the text in the TextField and start the Second Screen
void _sendDataToSecondScreen(BuildContext context) {
String textToSend = textFieldController.text;
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SecondScreen(text: textToSend,),
));
}
}
class SecondScreen extends StatelessWidget {
final String text;
// receive data from the FirstScreen as a parameter
SecondScreen({Key key, #required this.text}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Second screen')),
body: Center(
child: Text(
text,
style: TextStyle(fontSize: 24),
),
),
);
}
}
Passing data back to the previous screen
When passing data back you need to do the following things:
In the FirstScreen, use the Navigator to push (start) the SecondScreen in an async method and wait for the result that it will return when it finishes.
final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SecondScreen(),
));
In the SecondScreen, include the data that you want to pass back as a parameter when you pop the Navigator.
Navigator.pop(context, 'Hello');
Then in the FirstScreen the await will finish and you can use the result.
setState(() {
text = result;
});
Here is the complete code for main.dart for your reference.
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
title: 'Flutter',
home: FirstScreen(),
));
}
class FirstScreen extends StatefulWidget {
#override
_FirstScreenState createState() {
return _FirstScreenState();
}
}
class _FirstScreenState extends State<FirstScreen> {
String text = 'Text';
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('First screen')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.all(32.0),
child: Text(
text,
style: TextStyle(fontSize: 24),
),
),
RaisedButton(
child: Text(
'Go to second screen',
style: TextStyle(fontSize: 24),
),
onPressed: () {
_awaitReturnValueFromSecondScreen(context);
},
)
],
),
),
);
}
void _awaitReturnValueFromSecondScreen(BuildContext context) async {
// start the SecondScreen and wait for it to finish with a result
final result = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SecondScreen(),
));
// after the SecondScreen result comes back update the Text widget with it
setState(() {
text = result;
});
}
}
class SecondScreen extends StatefulWidget {
#override
_SecondScreenState createState() {
return _SecondScreenState();
}
}
class _SecondScreenState extends State<SecondScreen> {
// this allows us to access the TextField text
TextEditingController textFieldController = TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Second screen')),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.all(32.0),
child: TextField(
controller: textFieldController,
style: TextStyle(
fontSize: 24,
color: Colors.black,
),
),
),
RaisedButton(
child: Text(
'Send text back',
style: TextStyle(fontSize: 24),
),
onPressed: () {
_sendDataBack(context);
},
)
],
),
);
}
// get the text in the TextField and send it back to the FirstScreen
void _sendDataBack(BuildContext context) {
String textToSendBack = textFieldController.text;
Navigator.pop(context, textToSendBack);
}
}
This solution is very easy by passing variables in constructor:
first page:
Navigator.of(context).push(MaterialPageRoute(builder:(context)=>SecondPage('something')));
second page:
class SecondPage extends StatefulWidget {
String something;
SecondPage(this.something);
#override
State<StatefulWidget> createState() {
return SecondPageState(this.something);
}
}
class SecondPageState extends State<SecondPage> {
String something;
SecondPageState(this.something);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
//now you have passing variable
title: Text(something),
),
...
}
Get Perfect Solution :
From 1st Screen navigate to others as:
Navigator.pushNamed(context, "second",arguments: {"name" :
"Bijendra", "rollNo": 65210});
},
On Second Screen in build method get as :
#override
Widget build(BuildContext context) {
final Map<String, Object>rcvdData = ModalRoute.of(context).settings.arguments;
print("rcvd fdata ${rcvdData['name']}");
print("rcvd fdata ${rcvdData}");
return Scaffold(appBar: AppBar(title: Text("Second")),
body: Container(child: Column(children: <Widget>[
Text("Second"),
],),),);
}
Easiest way
FirstPage.dart
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PasswordRoute(usernameController)));
//usernameController is String value,If you want to pass multiple values add all
SecondPage.dart
class PasswordRoute extends StatefulWidget {
final String usernameController;//if you have multiple values add here
PasswordRoute(this.usernameController, {Key key}): super(key: key);//add also..example this.abc,this...
#override
State<StatefulWidget> createState() => _PasswordPageState();
}
class _PasswordPageState extends State<PasswordRoute> {
#override
Widget build(BuildContext context) {
...child: Text(widget.usernameController);
}
}
Answers above are useful for a small app, but if you want to remove the headache of continuously worrying about a widgets state, Google presented the Provider package.
https://pub.dev/packages/provider
Have a look into that one, or watch these videos from Andrea Bizzotto:
https://www.youtube.com/watch?v=MkFjtCov62g // Provider: The Essential Guide
https://www.youtube.com/watch?v=O71rYKcxUgA&t=258s // Provider: Introduction
Learn how to use the Provider package, and you are set for life :)
First Screen :
//send data to second screen
Navigator.push(context, MaterialPageRoute(builder: (context) {
return WelcomeUser(usernameController.text);
}));
Second Screen :
//fetch data from first screen
final String username;
WelcomeUser(this.username);
//use data to display
body: Container(
child: Center(
child: Text("Welcome "+widget.username,
textAlign: TextAlign.center,
),
),
),
Navigators in Flutter are similar to the Intent in Android.
There are two classes we are dealing with FirstScreen and SecondScreen.
In order to pass the data between the first screen to second do the following:
First of all add parameter in the SecondScreen class constructor
Now in the FirstScreen class provide the parameter
Navigator.push(context, MaterialPageRoute(builder: (context)=>SecondScreen(key_name:"Desired Data"));
So in the above line the "key_name" is the name of the parameter given in the SecondScreen class.
The "Desired Data" is data should be passed through the key to the SecondScreen class.
That's it you are done!!!
Passing Data to back screen flutter
Home Page
import 'package:flutter/material.dart';
import 'package:flutter/src/widgets/container.dart';
import 'package:flutter/src/widgets/framework.dart';
import 'package:sqflite_offline/View/Add_data.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
#override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
List<Method> items = []; // => List of items that come form next page.
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Hello"),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.of(context)
.push<Method>(MaterialPageRoute(builder: (_) => AddData()))
// fetching data form next page.
.then((value) => setState(() {
if (value?.title_Ctr != "" && value?.desc_Ctr != "") {
items.add(Method(
title_Ctr: value!.title_Ctr,
desc_Ctr: value.desc_Ctr));
}
}));
},
child: Icon(Icons.add),
),
body: items.isNotEmpty
? Column(children: [
Expanded(
child: ListView.builder(
itemCount: items.length,
itemBuilder: ((context, index) {
return Container(
margin:
EdgeInsets.only(top: 10, left: 10, right: 10),
padding: EdgeInsets.only(left: 10, right: 10),
height: 80,
decoration: BoxDecoration(
color: Colors.pinkAccent,
borderRadius: BorderRadius.circular(10)),
child: Center(
child: ListTile(
title: Text(items[index].title_Ctr),
subtitle: Text(items[index].desc_Ctr),
leading: Icon(Icons.emoji_people),
),
),
);
})))
])
: Center(
child: Text("No Record Found"),
));
}
}
Add List Page
import 'package:flutter/material.dart';
import 'package:flutter/src/widgets/container.dart';
import 'package:flutter/src/widgets/framework.dart';
class AddData extends StatefulWidget {
const AddData({super.key});
#override
State<AddData> createState() => _AddDataState();
}
// Creating a Class and constructor.
class Method {
late String title_Ctr;
late String desc_Ctr;
Method({required this.title_Ctr, required this.desc_Ctr});
}
class _AddDataState extends State<AddData> {
// Creating a TextEditingController for two Fiends,
//one is for title TextField and second is for Description TextField.
TextEditingController titleCtr = TextEditingController();
TextEditingController descCtr = TextEditingController();
// Creating a Method for Passing a data to back page.
OnPressed(BuildContext context) {
var data = Method(title_Ctr: titleCtr.text, desc_Ctr: descCtr.text);
Navigator.pop(context, data);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Add Data")),
body: Form(child: Builder(builder: (context) {
return Column(children: [
TextFormField(
controller: titleCtr,
decoration: InputDecoration(hintText: "title"),
validator: (value) {
var newValue = value ?? "";
if (newValue.isEmpty) {
return 'title is Required';
}
return null;
},
),
TextFormField(
controller: descCtr,
decoration: InputDecoration(hintText: "Description"),
validator: (value) {
var newValue = value ?? "";
if (newValue.isEmpty) {
return 'Discription is Required';
}
return null;
},
),
MaterialButton(
color: Colors.red,
onPressed: () {
if (Form.of(context)?.validate() ?? false) {
OnPressed(context);
}
},
child: Text("Save"),
)
]);
})));
}
}
screenshot
1) From where you want to push :
onPressed: () async {
await Navigator.pushNamed(context, '/edit',
arguments: userData);
setState(() {
userData = userData;
});}
2) From Where you want to pop :
void updateData() async{
WorldTime instance = locations;
await instance.getData();
Navigator.pop(context, userData);
}
If you use get package then try this . passing data with get package
check get package package link
Here's another approach.
Nothing wrong with the other answers. I've tried all of the methods mentioned using global wide widgets like provider, third-party solutions, Navigator arguments, etc. This approach differs by allowing one to chain calls and pass precise data of any type required to the widget using it. We can also gain access to a completion handler event and can use this technique without being constrained to Navigator objects.
Here's the tldr:
tldr; We have to turn our thinking on its head a bit. Data can be
passed to the called widget when you navigate to it by using final
arguments with default values in the destination widget. Using an
optional function you can get data back from the 'child' (destination)
widget.
The complete explanation can be found using this SO answer., (Gist)
I just want to be here to help that 1% who might go through what I did Lol
Don't forget to put an "await" infront of "Navigator.push" in the first page,
otherwise no data will be returned to the first page when you pop from the second page...
Passing Data to back screen flutter
First Screen
final result = await Navigator.of(context).push(MaterialPageRoute(builder: (context)=>const PaymentScreen()));
Second Screen
String selected = "Credit/Debit";
Navigator.pop(context,selected);
Ok I'm pretty new to flutter/ dart so go easy on me. I'm just trying to make a very simple app where when you press a button some text updates telling you how many times you have pressed the button. I have no idea why this code doesn't work. The button appears but nothing happens when you press it.
import 'package:flutter/material.dart';
class Homepage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Column(
children: <Widget>[],
);
}
}
class Buttonz extends StatefulWidget {
#override
_ButtonBeingPressed createState() => new _ButtonBeingPressed();
}
class _ButtonBeingPressed extends State<Buttonz> {
int _timesPressed = 0;
_buttonWasPressed() {
setState(() {
_timesPressed++;
});
}
#override
Widget build(BuildContext context) {
return new Column(children: <Widget>[
new Center(
child: new Row(
children: <Widget>[
new Text(
'The button was pressed ' + _timesPressed.toString() + "
times"),
new RaisedButton(
onPressed: _buttonWasPressed(),
child: new Row(
children: <Widget>[new Text("Press meh")],
),
),
],
))
]);
}
}
Your problem is that you didn't pass a callback to RaisedButton, you invoked your callback.
new RaisedButton(
onPressed: _buttonWasPressed(), // invokes function
child: new Row(children: <Widget>[new Text("Press meh")]),
);
To pass a callback to another widget you have two choices:
Pass a tear-off
new RaisedButton(
onPressed: _buttonWasPressed, // no `()`,
child: ...
)
Pass a closure
new RaisedButton(
onPressed: () {
// do something.
},
..
)
in some cases, this can occur with a widget in the stack.
It is possible that the Widget is overwritten with another Widget, so it cannot be clicked.
Added a Material App and rewired the RaisedButton a little. I think it was how you had onPressed wired up.
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(home: new Buttonz());
}
}
class Buttonz extends StatefulWidget {
#override
_ButtonBeingPressed createState() => new _ButtonBeingPressed();
}
class _ButtonBeingPressed extends State<Buttonz> {
int _timesPressed = 0;
_buttonWasPressed() {
setState(() {
_timesPressed++;
});
}
#override
Widget build(BuildContext context) {
return new Column(
children: <Widget>[
new Text(
'The button was pressed $_timesPressed times'),
new RaisedButton(
child: const Text('Press meh'),
onPressed: () {
_buttonWasPressed();
},
),
],
);
}
}
Your button should be like this.:
new RaisedButton(
child: const Text('Press meh'),
onPressed: _buttonWasPressed,
),
If this doesn't work, then try to clean your flutter project with flutter clean and then reinstalling the app on debug device.
I'm trying to create a widget that has a button and whenever that button is pressed, a list opens up underneath it filling in all of the space under the button. I implemented it with a simple Column, something like this:
class _MyCoolWidgetState extends State<MyCoolWidget> {
...
#override
Widget build(BuildContext context) {
return new Column(
children: <Widget>[
new MyButton(...),
isPressed ? new Expanded(
child: new SizedBox(
width: MediaQuery.of(context).size.width,
child: new MyList()
)
) : new Container()
]
)
}
}
This works totally fine in a lot of cases, but not all.
The problem I'm having with creating this widget is that if a MyCoolWidget is placed inside a Row for example with other widgets, lets say other MyCoolWidgets, the list is constrained by the width that the Row implies on it.
I tried fixing this with an OverflowBox, but with no luck unfortunately.
This widget is different from tabs in the sense that they can be placed anywhere in the widget tree and when the button is pressed, the list will fill up all the space under the button even if this means neglecting constraints.
The following image is a representation of what I'm trying to achieve in which "BUTTON1" and "BUTTON2" or both MyCoolWidgets in a Row:
Edit: Snippet of the actual code
class _MyCoolWidgetState extends State<MyCoolWidget> {
bool isTapped = false;
#override
Widget build(BuildContext context) {
return new Column(
children: <Widget>[
new SizedBox(
height: 20.0,
width: 55.0,
child: new Material(
color: Colors.red,
child: new InkWell(
onTap: () => setState(() => isTapped = !isTapped),
child: new Text("Surprise"),
),
),
),
bottomList()
],
);
}
Widget comboList() {
if (isTapped) {
return new Expanded(
child: new OverflowBox(
child: new Container(
color: Colors.orange,
width: MediaQuery.of(context).size.width,
child: new ListView( // Random list
children: <Widget>[
new Text("ok"),
new Text("ok"),
new Text("ok"),
new Text("ok"),
new Text("ok"),
new Text("ok"),
new Text("ok"),
new Text("ok"),
new Text("ok"),
new Text("ok"),
new Text("ok"),
new Text("ok"),
new Text("ok"),
],
)
)
),
);
} else {
return new Container();
}
}
}
I'm using it as follows:
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Row(
children: <Widget>[
new Expanded(child: new MyCoolWidget()),
new Expanded(child: new MyCoolWidget()),
]
)
}
}
Here is a screenshot of what the code is actually doing:
From the comments, it was clarified that what the OP wants is this:
Making a popup that covers everything and goes from wherever the button is on the screen to the bottom of the screen, while also filling it horizontally, regardless of where the button is on the screen. It would also toggle open/closed when the button is pressed.
There are a few options for how this could be done; the most basic would be to use a Dialog & showDialog, except that it has some issues around SafeArea that make that difficult. Also, the OP is asking for the button to toggle rather than pressing anywhere not the dialog (which is what dialog does - either that or blocks touches behind the dialog).
This is a working example of how to do something like this. Full disclaimer - I'm not stating that this is a good thing to do, or even a good way to do it... but it is a way to do it.
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
// We're extending PopupRoute as it (and ModalRoute) do a lot of things
// that we don't want to have to re-create. Unfortunately ModalRoute also
// adds a modal barrier which we don't want, so we have to do a slightly messy
// workaround for that. And this has a few properties we don't really care about.
class NoBarrierPopupRoute<T> extends PopupRoute<T> {
NoBarrierPopupRoute({#required this.builder});
final WidgetBuilder builder;
#override
Color barrierColor;
#override
bool barrierDismissible = true;
#override
String barrierLabel;
#override
Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
return new Builder(builder: builder);
}
#override
Duration get transitionDuration => const Duration(milliseconds: 100);
#override
Iterable<OverlayEntry> createOverlayEntries() sync* {
// modalRoute creates two overlays - the modal barrier, then the
// actual one we want that displays our page. We simply don't
// return the modal barrier.
// Note that if you want a tap anywhere that isn't the dialog (list)
// to close it, then you could delete this override.
yield super.createOverlayEntries().last;
}
#override
Widget buildTransitions(
BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation, Widget child) {
// if you don't want a transition, remove this and set transitionDuration to 0.
return new FadeTransition(opacity: new CurvedAnimation(parent: animation, curve: Curves.easeOut), child: child);
}
}
class PopupButton extends StatefulWidget {
final String text;
final WidgetBuilder popupBuilder;
PopupButton({#required this.text, #required this.popupBuilder});
#override
State<StatefulWidget> createState() => PopupButtonState();
}
class PopupButtonState extends State<PopupButton> {
bool _active = false;
#override
Widget build(BuildContext context) {
return new FlatButton(
onPressed: () {
if (_active) {
Navigator.of(context).pop();
} else {
RenderBox renderbox = context.findRenderObject();
Offset globalCoord = renderbox.localToGlobal(new Offset(0.0, context.size.height));
setState(() => _active = true);
Navigator
.of(context, rootNavigator: true)
.push(
new NoBarrierPopupRoute(
builder: (context) => new Padding(
padding: new EdgeInsets.only(top: globalCoord.dy),
child: new Builder(builder: widget.popupBuilder),
),
),
)
.then((val) => setState(() => _active = false));
}
},
child: new Text(widget.text),
);
}
}
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() => MyAppState();
}
class MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new SafeArea(
child: new Container(
color: Colors.white,
child: new Column(children: [
new PopupButton(
text: "one",
popupBuilder: (context) => new Container(
color: Colors.blue,
),
),
new PopupButton(
text: "two",
popupBuilder: (context) => new Container(color: Colors.red),
)
]),
),
),
);
}
}
For even more outlandish suggestions, you can take the finding the location part of this and look at this answer which describes how to create a child that isn't constrained by it's parent's position.
However you end up doing this, it's probably best that the list not to be a direct child of the button as a lot of things in flutter depend on a child's sizing and making it be able to expand to the full screen size could quite easily cause problems.
Problem: Shared preference bool value is null on startup even though I have given it a value if prefs.getBool('myBool') returns null (though my shared preferences value should already be set and saved). It does, however, work by the time I press a button (I assume because it has finished running the async code).
Question: How can I force shared preferences to load on startup (so my value is not null) without having to press the print button?
Example Code:
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() => runApp(new MyApp());
class MyApp extends StatefulWidget {
MyApp({Key key}) : super(key: key);
#override
createState() => new MyAppState();
}
class MyAppState extends State<MyApp> {
final padding = const EdgeInsets.all(50.0);
#override
void initState() {
super.initState();
MySharedPreferences.load();
MySharedPreferences.printMyBool();
}
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
body: new Padding(
padding: padding,
child: new Column(
children: <Widget>[
new Padding(
padding: padding,
child: new RaisedButton(
child: new Text('Save True'),
onPressed: () => MySharedPreferences.save(myBool: true),
),
),
new Padding(
padding: padding,
child: new RaisedButton(
child: new Text('Save False'),
onPressed: () => MySharedPreferences.save(myBool: false),
),
),
new Padding(
padding: padding,
child: new RaisedButton(
child: new Text('Print myBool'),
onPressed: () => MySharedPreferences.printMyBool(),
),
),
],
),
),
),
);
}
}
class MySharedPreferences {
static bool _myBool;
static void load() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
_myBool = prefs.getBool('myBool') ?? false;
}
static void save({myBool: bool}) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
_myBool = myBool;
await prefs.setBool('myBool', _myBool);
}
static void printMyBool() {
print('myBool: ${_myBool.toString()}');
}
}
Results:
On startup, myBool: null is printed. Once the button is pressed, myBool: false/true is then printed.
Your problem is that you call load() and printMyBool() in quick succession. Because load() is async calling it hasn't executed any of its code, it has only scheduled it. So, printMyBool executes before the body of load.
There's no need to put static functions in a class - just declare them as top level functions. Also, you don't really want _myBool to be a global - it should be part of a Widget's state. That way when you update it, Flutter knows what parts of your tree to redraw.
I've restructured your code to remove the redundant statics.
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() => runApp(new MyApp());
class MyApp extends StatefulWidget {
MyApp({Key key}) : super(key: key);
#override
createState() => new MyAppState();
}
const EdgeInsets pad20 = const EdgeInsets.all(20.0);
const String spKey = 'myBool';
class MyAppState extends State<MyApp> {
SharedPreferences sharedPreferences;
bool _testValue;
#override
void initState() {
super.initState();
SharedPreferences.getInstance().then((SharedPreferences sp) {
sharedPreferences = sp;
_testValue = sharedPreferences.getBool(spKey);
// will be null if never previously saved
if (_testValue == null) {
_testValue = false;
persist(_testValue); // set an initial value
}
setState(() {});
});
}
void persist(bool value) {
setState(() {
_testValue = value;
});
sharedPreferences?.setBool(spKey, value);
}
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
body: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Padding(
padding: pad20,
child: new Text(
_testValue == null ? 'not ready' : _testValue.toString()),
),
new Padding(
padding: pad20,
child: new RaisedButton(
child: new Text('Save True'),
onPressed: () => persist(true),
),
),
new Padding(
padding: pad20,
child: new RaisedButton(
child: new Text('Save False'),
onPressed: () => persist(false),
),
),
new Padding(
padding: pad20,
child: new RaisedButton(
child: new Text('Print myBool'),
onPressed: () => print(_testValue),
),
),
],
),
),
),
);
}
}
Add condition ?? when you get value from preference.
int intValue = prefs.getInt('intValue') ?? 0;
Use conditional operator(??) to assign values if shared preference returning null
bool _testValue;
#override
void initState() {
super.initState();
SharedPreferences.getInstance().then((prefValue) => {
setState(() {
_name = prefValue.getString('name')?? false;
_controller = new TextEditingController(text: _name);
})
});
}
For any one still experiencing this issue, it's because there is still a race condition in the accepted answer.
To fix it, use this package to wait for the layout to load first
You can use FutureBuilder to make async operations.
How could I make the name() function run whenever the Page1 page appeared?
In the code below before going to Page2 I execute the dispose()
Already inside Page2 if I click the back button or the physical button of Android the function name() is not executed, but if I click the 'go to Page1' button the function name() is executed.
Could you help me to always execute the name() function when Page1 appears?
import 'package:flutter/material.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new MyHomePage(),
routes: <String, WidgetBuilder> {
'/page2': (BuildContext context) => new Page2(),
},
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String nameScreen;
String name() {
return 'foo1';
}
#override
void initState() {
super.initState();
this.nameScreen = name();
}
#override
void dispose() {
this.nameScreen = '';
super.dispose();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Page 1'),
backgroundColor: new Color(0xFF26C6DA),
),
body: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new RaisedButton(
child: const Text('go to Page2'),
onPressed: () async {
dispose();
bool isLoggedIn = await Navigator.of(context).pushNamed('/page2');
if (isLoggedIn) {
setState((){
this.nameScreen = name();
});
}
},
),
new Text(
'$nameScreen',
),
],
),
),
);
}
}
class Page2 extends StatelessWidget{
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Page 2'),
backgroundColor: new Color(0xFFE57373)
),
body: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new RaisedButton(
child: const Text('go back to Page1'),
onPressed: () {
Navigator.pop(context, true);
}
),
],
),
),
);
}
}
There is no need to call dispose at all when you are willing to pop and change State later, since dispose will remove the current object from the tree, which does not translate to the logic you are trying to develop.
You can indeed override the BackButton and pass the same call of Navigator.pop(context, result) to it. Check the following example I have tweaked your code a little bit to show you the difference between each State of your nameScreen field. I hope this helps you.
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String nameScreen = "";
String name() {
return 'foo1';
}
#override
void initState() {
super.initState();
this.nameScreen = "From initState";
}
#override
void dipose(){
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Page 1'),
backgroundColor: Color(0xFF26C6DA),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
child: const Text('go to Page2'),
onPressed: () async {
//dispose(); ///No need for dispose
String result = await Navigator.of(context).pushNamed('/page2');
setState((){
this.nameScreen = result;
});
},
),
Text(
'$nameScreen',
),
],
),
),
);
}
}
class Page2 extends StatelessWidget{
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(icon: Icon(Icons.arrow_back), onPressed: ()async{
Navigator.pop(context,"From BackButton");
}),
title: const Text('Page 2'),
backgroundColor: Color(0xFFE57373)
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
child: const Text('go back to Page1'),
onPressed: () {
Navigator.pop(context, "From RaisedButton");
}
),
],
),
),
);
}
One way of doing this is to use the .whenComplete() method on the Navigator widget.
Suppose you are going to the second page from the first page. Here you have to pass the functionThatSetsTheState as a pointer to the navigation part of your code.
The function looks like this and should be in a Stateful Widget.
void functionThatSetsTheState(){
setState(() {});
}
Your navigation code for OnPressed, OnTap, OnLongPress, etc.
Navigator.of(context)
.push(
MaterialPageRoute(builder: (BuildContext context) => SecondPage()))
.whenComplete(() => {functionThatSetsTheState()});
You can override the back button on the second screen. And instead of system closing, do
WillPopScope(
onWillPop: () {
print('back pressed');
Navigator.pop(context, "From BackButton");
return true;
},
child: Scaffold(...)
You can use RouteObserves if you want to execute some function whenever your page appears, you will have to implement RouteAware on the page where you want to run execute the function whenever the screens appears, you're gonna have to do something like this on ur Page1
final RouteObserver<PageRoute> routeObserver = RouteObserver<PageRoute>(); // add this on your main class
void main() {
runApp(MaterialApp(
home: Container(),
navigatorObservers: [routeObserver], // add observer here;
));
}
// your page where func should run whenever this page appears
class MyHomePage extends StatefulWidget with RouteAware {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String nameScreen = "";
String name() {
return 'foo1';
}
#override
void initState() {
super.initState();
this.nameScreen = "From initState";
}
#override
void didChangeDependencies() {
super.didChangeDependencies();
routeObserver.subscribe(this, ModalRoute.of(context));
}
#override
void dispose() {
routeObserver.unsubscribe(this);
super.dispose();
}
// implementing RouteAware method
void didPush() {
// Route was pushed onto navigator and is now topmost route.
name(); // your func goes here
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Page 1'),
backgroundColor: Color(0xFF26C6DA),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
child: const Text('go to Page2'),
onPressed: () async {
//dispose(); ///No need for dispose
String result = await Navigator.of(context).pushNamed('/page2');
setState((){
this.nameScreen = result;
});
},
),
Text(
'$nameScreen',
),
],
),
),
);
}
}
you can head over to this link for more explanation
https://api.flutter.dev/flutter/widgets/RouteObserver-class.html
Say you want to navigate from page 1 to page 2 and immediately after page 2 loads execute a function in page 2 (useful for showing a dialog immediately when page 2 loads) :
You can do this by adding in initState or didChangeDependencies of page 2 :
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
// Function to execute
});
If you want to add some logic to put a condition before executing the function, simply push an argument in your page 1 :
Navigator.of(context).pushNamed("/page-2", arguments : true)
Finnaly the code in page 2 becomes:
_functionToExecute(){
print("done");
}
#override
void didChangeDependencies() {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
if(ModalRoute.of(context).settings.arguments)
_functionToExecute()
});
}