Passing data between screens in Flutter - dart

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

Related

Make cards with texts and buttons dynamically

I'm making Notes app. I made cards with text and buttons dynamically (Create by clicking the button). But I have problem with Changing Text on CURRENT card. For example, I have 3 cards with own texts and buttons and I want to change text on 2nd card but text is changing on the 3rd card. How can I solve this problem?
3 cards with texts and buttons
Change Text Page
In the past, I've tried making list to collect texts, but i dont know how to identify current card.
full main.dart
import 'package:flutter/material.dart';
import './changeTextPage.dart';
int count = 0;
String titlecard = '';
String textcard = '';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Notes',
theme: ThemeData(
primarySwatch: Colors.deepPurple
),
home: HomePage(title: 'Notes',),
);
}
}
class HomePage extends StatefulWidget {
HomePage({Key key, this.title}) : super(key: key);
final title;
#override
HomePageState createState() => HomePageState();
}
class HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
List<Widget> cards = new List.generate(count, (int i) => new MyCard());
return Scaffold(
appBar: AppBar(
title: Text('Notes'),
),
body: LayoutBuilder(
builder: (context, constraint) {
return Column(
children: <Widget>[
Container(
height: 650.0,
child: new ListView(
children: cards,
scrollDirection: Axis.vertical,
),
),
],
);
}
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
setState(() {
Navigator.push(context, MaterialPageRoute(
builder: (context) => changeText())
);
});
},
),
);
}
}
class MyCard extends StatefulWidget {
#override
myCard createState() => myCard();
}
class myCard extends State<MyCard> {
int myCount = count;
void click() {
setState(() {
Navigator.push(context, MaterialPageRoute(
builder: (context) => setNewText())
);
});
}
#override
Widget build(BuildContext context) {
return Center(
child: Card(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ListTile(
leading: Icon(Icons.album),
title: Text(titlecard),
subtitle: Text(textcard),
),
ButtonTheme.bar( // make buttons use the appropriate styles for cards
child: ButtonBar(
children: <Widget>[
FlatButton(
child: const Text('Change Text'),
onPressed: click,
),
FlatButton(
child: const Text('LISTEN'),
onPressed: () { /* ... */ },
),
],
),
),
],
),
),
);
}
}
class setNewText extends StatefulWidget {
#override
SetNewText createState() => SetNewText();
}
class SetNewText extends State<setNewText> {
final titleController = TextEditingController();
final textController = TextEditingController();
final formkey = GlobalKey<FormState>();
void _submit() {
setState(() {
if (formkey.currentState.validate()) {
formkey.currentState.save();
Navigator.pop(context);
titlecard = titleController.text;
textcard = textController.text;
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Change Title'),
),
body: Column(
children: <Widget>[
Card(
child: Padding(
padding: EdgeInsets.all(2.0),
child: Form(
key: formkey,
child: Column(
children: <Widget>[
TextFormField(
controller: titleController,
decoration: InputDecoration(
labelText: 'Title'
),
validator: (value) => value.length < 1 ? 'Invalid Title' : null,
onSaved: (value) => value = titleController.text,
),
TextFormField(
controller: textController,
decoration: InputDecoration(
labelText: 'Text'
),
validator: (text) => text.length < 1 ? 'Invalid Text' : null,
onSaved: (text) => text = textController.text,
)
],
),
),
),
),
FlatButton(
textColor: Colors.deepPurple,
child: Text('SUBMIT'),
onPressed: _submit,
),
],
)
);
}
}
changeTextPage.dart
import 'package:flutter/material.dart';
import './main.dart';
class changeText extends StatefulWidget {
#override
ChangeText createState() => ChangeText();
}
class ChangeText extends State<changeText> {
myCard s = myCard();
final titleController = TextEditingController();
final textController = TextEditingController();
final formkey = GlobalKey<FormState>();
void _submit() {
setState(() {
if (formkey.currentState.validate()) {
formkey.currentState.save();
Navigator.pop(context);
count++;
titlecard = titleController.text;
textcard = textController.text;
}
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Change Title'),
),
body: Column(
children: <Widget>[
Card(
child: Padding(
padding: EdgeInsets.all(2.0),
child: Form(
key: formkey,
child: Column(
children: <Widget>[
TextFormField(
controller: titleController,
decoration: InputDecoration(
labelText: 'Title'
),
validator: (value) => value.length < 1 ? 'Invalid Title' : null,
onSaved: (value) => value = titleController.text,
),
TextFormField(
controller: textController,
decoration: InputDecoration(
labelText: 'Text'
),
validator: (text) => text.length < 1 ? 'Invalid Text' : null,
onSaved: (text) => text = textController.text,
)
],
),
),
),
),
FlatButton(
textColor: Colors.deepPurple,
child: Text('SUBMIT'),
onPressed: _submit,
),
],
)
);
}
}
Okay, so you happen to make some common mistakes, one of which is critical.
most importantly don't use global variables! As you do with count, titlecard and textcard.
there is a practice to name stateful widgets with PascalCase and corresponding states just like the widget but prefixed with an underscore (_) to make it private and suffixed by the State word.
The correct approach for this (or one of them) would be to have a widget that would be your screen with a form to edit stuff and it would pop some struct with user values on submit:
class ChangeTextScreen extends StatefulWidget {
_ChangeTextScreenState createState() => _ChangeTextScreenState();
}
class _ChangeTextScreenState extends State<ChangeTextScreen> {
void _submit() {
setState(() {
formkey.currentState.save();
Navigator.pop(ChangeTextResult(title: titleController.text, text: textController.text));
});
}
// Rest of your code...
}
class ChangeTextResult {
final String title;
final String text;
ChangeTextResult({#required this.title, #required this.text});
}
You should also have a place where you store your notes in some kind of a list. Your main screen looks like a good place for it. Once your app will be bigger, think about using scoped_model or Redux or something.
So let's add a Note class and a list with your notes to your main screen:
class Note {
String title;
String text;
Note(this.title, this.text);
}
class HomePageState extends State<HomePage> {
List<Note> _notes = [Note('Test', 'Some test note')];
#override
Widget build(BuildContext context) {
ListView cards = ListView.builder(
itemCount: _notes.length,
itemBuilder: (context, index) => MyCard(
title: _notes[index].title,
text: _notes[index].text,
onEdit: (title, text) => setState(() { // We'll get back to that later
_notes[index].title = title;
_notes[index].text = text;
})
));
// (...)
Your MyCard widget (try to use better names next time) should contain some kind of information about its content, one of the best approaches would be to pass this info to its constructor, just like that:
class MyCard extends StatefulWidget {
final String title;
final String text;
final Function onEdit;
MyCard({Key key, #required this.title, #required this.text, #required this.onEdit}) : super(key: key);
#override
_MyCardState createState() => _MyCardState();
}
Having this Key parameter is a good practice.
And use those parameters in your _MyCardState class (I renamed it from _myCard):
// (...)
children: <Widget>[
ListTile(
leading: Icon(Icons.album),
title: Text(widget.title),
subtitle: Text(widget.text),
),
// (...)
Returning to the moment where you open your ChangeTextScreen, you should assign the result of Navigation.push() to a variable. This is your result, you can deal with it (once we check it for null, the user could have returned from this screen and then the result would be null).
void click() {
setState(() {
final ChangeTextResult result = Navigator.push(context, MaterialPageRoute(
builder: (context) => ChangeTextScreen())
);
if (result != null) {
widget.onEdit(result.title, result.text);
}
});
}
Do you remember that onEdit parameter (I mentioned it in a comment in the code above)? We call that parameter here.
That's it I think. I could have mixed some concepts of your app, but I think you'll manage to get my point anyways.
I quite rewrote all of your code. I think it will be easier for you to start again from scratch and have those tips in mind. Also, try to Google some similar things (like a simple Todo application) or do Getting started from flutter.io with part two! That should give you a nice idea on how to resolve that common problem in Flutter.
And also, read about good practises in Flutter and Dart. Things like correctly formatting your code are really important.
BTW that's my longest answer on Stack Overflow so far. I hope you'll appreciate that.

Flutter BLoC being recreated

I'm discovering Flutter and the bloc pattern and to practice I'm making an app about pizzas.
I am using a BlocProvider to access the blocks. It is from the generic_bloc_provider package. It is a basic implementation using an InheritedWidget combined with a StatelessWidget.
I have a page with two editable textfields, for the name and price of the pizza I want to create. It is backed by a bloc.
Here's the code :
AddPizzaPage.dart :
class AddPizzaPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
print("Building AddPizzaPage");
return Scaffold(
appBar: AppBar(
title: Text("Adding Pizza"),
),
body: BlocProvider(
bloc: AddPizzaBloc(),
child: ModifyPizzaWidget(),
),
);
}
}
ListPage.dart:
class ModifyPizzaWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
final addPizzaBloc = BlocProvider.of<AddPizzaBloc>(context);
return Container(
margin: EdgeInsets.all(16.0),
child: Column(
children: <Widget>[
TextField(
decoration: InputDecoration(hintText: "Nom de la pizza"),
onChanged: (name) {
addPizzaBloc.pizzaNameSink.add(name);
},
),
TextField(
decoration: InputDecoration(hintText: "Prix de la pizza"),
keyboardType: TextInputType.number,
onChanged: (price) {
addPizzaBloc.pizzaPriceSink.add(price);
},
),
IconButton(
icon: Icon(Icons.check),
iconSize: 40,
onPressed: () {
addPizzaBloc.evenSink.add(AddPizzaEvent.VALIDATE);
Navigator.of(context).pop();
},
)
],
),
);
}
}
AddPizzaBloc.dart :
enum AddPizzaEvent {
VALIDATE
}
class AddPizzaBloc extends Bloc {
final _pizza = Pizza.empty();
final _pizzaSubject = BehaviorSubject<Pizza>();
final _repository = PizzaRepository();
Sink<String> get pizzaNameSink => _pizzaNameController.sink;
final _pizzaNameController = StreamController<String>();
Sink<String> get pizzaPriceSink => _pizzaPriceController.sink;
final _pizzaPriceController = StreamController<String>();
Sink<AddPizzaEvent> get evenSink => _eventSink.sink;
final _eventSink = StreamController<AddPizzaEvent>();
AddPizzaBloc() {
print("Created");
_pizzaNameController.stream.listen(_addPizzaName);
_pizzaPriceController.stream.listen(_addPizzaPrice);
_eventSink.stream.listen(_onEventReceived);
}
dispose() {
print("Disposed");
_pizzaSubject.close();
_pizzaNameController.close();
_pizzaPriceController.close();
_eventSink.close();
}
void _addPizzaName(String pizzaName) {
_pizza.name = pizzaName;
print(_pizza);
}
void _addPizzaPrice(String price) {
var pizzaPrice = double.tryParse(price) ?? 0.0;
_pizza.price = pizzaPrice;
print(_pizza);
}
void _onEventReceived(AddPizzaEvent event) {
print("Received $event");
if (event == AddPizzaEvent.VALIDATE) {
print(_pizza);
_repository.addPizza(_pizza);
}
}
}
My issue is that I store the Pizza being built inside the block but the widget is rebuilt, and so the bloc is rebuilt and I lose the state.
The full code is available on gitlab
I don't know how to use the bloc to power the addPizza form.
This happens because you're creating the instance of your BLoC within the build method:
BlocProvider(
bloc: Bloc(),
child: ...
)
The consequence is that any rebuild would not reuse the previous instance (with some awful memory leaks too).
The solution would be to make a StatefulWidget and create that BLoC instance within initState, followed by a dispose override to clean things.
But since you're using a package already, you can use provider instead. It is a popular alternative that does everything listed above.
As such your BlocProvider usage becomes:
StatefulProvider(
valueBuilder: (_) => AddPizzaBloc(),
dispose: (_, bloc) => bloc.dispose(),
child: // ...
),
then obtained as such:
Provider.of<AddPizzaBloc>(context);

Losing data while navigating screens in Flutter

I am new to Flutter and just started to make a tiny little app which takes a list of Top Movies from a server using an async request. and when I tap on top of each one of list items, then it navigates me to another screen to show some details about the movie.
But there is a problem, when I tap on any item to see it's details, inside the details page, when I press back, in the first page, it just loads data again which is not a good user experience. also uses more battery and bandwidth for each request.
I don't know if this is a natural behavior of Flutter to lose data of a Stateful widget after navigating to another screen or there is something wrong with my code.
Can anybody help me with this
This is my code:
import "package:flutter/material.dart";
import "dart:async";
import "dart:convert";
import "package:http/http.dart" as http;
void main() {
runApp(MovieApp());
}
class MovieApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'test',
home: Scaffold(
appBar: AppBar(
backgroundColor: Colors.white,
title: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Text("Top Movies List",
textDirection: TextDirection.rtl,
style: TextStyle(color: Colors.black87))
]
)
),
body: MoviesList()
)
);
}
}
class MoviesList extends StatefulWidget {
#override
MoviesListState createState() => new MoviesListState();
}
class MoviesListState extends State<MoviesList> {
List moviesList = [];
Future<Map> getData() async {
http.Response response = await http.get(
'http://api.themoviedb.org/3/discover/movie?api_key={api_key}'
);
setState(() {
moviesList = json.decode(response.body)['results'];
});
// return json.decode(response.body);
}
#override
Widget build(BuildContext context) {
getData();
if(moviesList == null) {
return Scaffold(
body: Text('Getting data from server')
);
} else {
return ListView.builder(
itemCount: moviesList.length,
itemBuilder: (context, index){
return Container(
child: ListTile(
title: Text(moviesList[index]['title']),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => MovieDetails()),
);
}
)
);
}
);
}
}
}
class MovieDetails extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Details')
),
body: Container(
child: Center(
child: RaisedButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('Go back!'),
),
)
),
);
}
}
Move your getData() method inside the initState() in your State class.
(Remove it from build method)
#override
void initState() {
getData();
super.initState();
}

Maintain state for StatefulWidget during build in Flutter

I have a list of stateful widgets where the user can add, remove, and interact with items in the list. Removing items from the list causes subsequent items in the list to rebuild as they shift to fill the deleted row. This results in a loss of state data for these widgets - though they should remain unaltered other than their location on the screen. I want to be able to maintain state for the remaining items in the list even as their position changes.
Below is a simplified version of my app which consists primarily of a list of StatefulWidgets. The user can add items to the list ("tasks" in my app) via the floating action button or remove them by swiping. Any item in the list can be highlighted by tapping the item, which changes the state of the background color of the item. If multiple items are highlighted in the list, and an item (other than the last item in the list) is removed, the items that shift to replace the removed item lose their state data (i.e. the background color resets to transparent). I suspect this is because _taskList rebuilds since I call setState() to update the display after a task is removed. I want to know if there is a clean way to maintain state data for the remaining tasks after a task is removed from _taskList.
void main() => runApp(new TimeTrackApp());
class TimeTrackApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Time Tracker',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new TimeTrackHome(title: 'Task List'),
);
}
}
class TimeTrackHome extends StatefulWidget {
TimeTrackHome({Key key, this.title}) : super(key: key);
final String title;
#override
_TimeTrackHomeState createState() => new _TimeTrackHomeState();
}
class _TimeTrackHomeState extends State<TimeTrackHome> {
TextEditingController _textController;
List<TaskItem> _taskList = new List<TaskItem>();
void _addTaskDialog() async {
_textController = TextEditingController();
await showDialog(
context: context,
builder: (_) => new AlertDialog(
title: new Text("Add A New Task"),
content: new TextField(
controller: _textController,
decoration: InputDecoration(
border: InputBorder.none, hintText: 'Enter the task name'),
),
actions: <Widget>[
new FlatButton(
onPressed: () => Navigator.pop(context),
child: const Text("CANCEL")),
new FlatButton(
onPressed: (() {
Navigator.pop(context);
_addTask(_textController.text);
}),
child: const Text("ADD"))
],
));
}
void _addTask(String title) {
setState(() {
// add the new task
_taskList.add(TaskItem(
name: title,
));
});
}
#override
void initState() {
_taskList = List<TaskItem>();
super.initState();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Align(
alignment: Alignment.topCenter,
child: ListView.builder(
padding: EdgeInsets.all(0.0),
itemExtent: 60.0,
itemCount: _taskList.length,
itemBuilder: (BuildContext context, int index) {
if (index < _taskList.length) {
return Dismissible(
key: ObjectKey(_taskList[index]),
onDismissed: (direction) {
if(this.mounted) {
setState(() {
_taskList.removeAt(index);
});
}
},
child: _taskList[index],
);
}
}),
),
floatingActionButton: new FloatingActionButton(
onPressed: _addTaskDialog,
tooltip: 'Click to add a new task',
child: new Icon(Icons.add),
),
);
}
}
class TaskItem extends StatefulWidget {
final String name;
TaskItem({Key key, this.name}) : super(key: key);
TaskItem.from(TaskItem other) : name = other.name;
#override
State<StatefulWidget> createState() => new _TaskState();
}
class _TaskState extends State<TaskItem> {
static final _taskFont =
const TextStyle(fontSize: 26.0, fontWeight: FontWeight.bold);
Color _color = Colors.transparent;
void _highlightTask() {
setState(() {
if(_color == Colors.transparent) {
_color = Colors.greenAccent;
}
else {
_color = Colors.transparent;
}
});
}
#override
Widget build(BuildContext context) {
return Column(children: <Widget>[
Material(
color: _color,
child: ListTile(
title: Text(
widget.name,
style: _taskFont,
textAlign: TextAlign.center,
),
onTap: () {
_highlightTask();
},
),
),
Divider(
height: 0.0,
),
]);
}
}
I ended up solving the problem by creating an intermediate class which contains a reference to the StatefulWidget and transferred over all the state variables. The State class accesses the state variables through a reference to the intermediate class. The higher level widget that contained and managed a List of the StatefulWidget now access the StatefulWidget through this intermediate class. I'm not entirely confident in the "correctness" of my solution as I haven't found any other examples of this, so I am still open to suggestions.
My intermediate class is as follows:
class TaskItemData {
// StatefulWidget reference
TaskItem widget;
Color _color = Colors.transparent;
TaskItemData({String name: "",}) {
_color = Colors.transparent;
widget = TaskItem(name: name, stateData: this,);
}
}
My StatefulWidget and its corresponding State classes are nearly unchanged, except that the state variables no longer reside in the State class. I also added a reference to the intermediate class inside my StatefulWidget which gets initialized in the constructor. Previous uses of state variables in my State class now get accessed through the reference to the intermediate class. The modified StatefulWidget and State classes is as follows:
class TaskItem extends StatefulWidget {
final String name;
// intermediate class reference
final TaskItemData stateData;
TaskItem({Key key, this.name, this.stateData}) : super(key: key);
#override
State<StatefulWidget> createState() => new _TaskItemState();
}
class _TaskItemState extends State<TaskItem> {
static final _taskFont =
const TextStyle(fontSize: 26.0, fontWeight: FontWeight.bold);
void _highlightTask() {
setState(() {
if(widget.stateData._color == Colors.transparent) {
widget.stateData._color = Colors.greenAccent;
}
else {
widget.stateData._color = Colors.transparent;
}
});
}
#override
Widget build(BuildContext context) {
return Column(children: <Widget>[
Material(
color: widget.stateData._color,
child: ListTile(
title: Text(
widget.name,
style: _taskFont,
textAlign: TextAlign.center,
),
onTap: () {
_highlightTask();
},
),
),
Divider(
height: 0.0,
),
]);
}
}
The widget containing the List of TaskItem objects has been replaced with a List of TaskItemData. The ListViewBuilder child now accesses the TaskItem widget through the intermediate class (i.e. child: _taskList[index], has changed to child: _taskList[index].widget,). It is as follows:
class _TimeTrackHomeState extends State<TimeTrackHome> {
TextEditingController _textController;
List<TaskItemData> _taskList = new List<TaskItemData>();
void _addTaskDialog() async {
_textController = TextEditingController();
await showDialog(
context: context,
builder: (_) => new AlertDialog(
title: new Text("Add A New Task"),
content: new TextField(
controller: _textController,
decoration: InputDecoration(
border: InputBorder.none, hintText: 'Enter the task name'),
),
actions: <Widget>[
new FlatButton(
onPressed: () => Navigator.pop(context),
child: const Text("CANCEL")),
new FlatButton(
onPressed: (() {
Navigator.pop(context);
_addTask(_textController.text);
}),
child: const Text("ADD"))
],
));
}
void _addTask(String title) {
setState(() {
// add the new task
_taskList.add(TaskItemData(
name: title,
));
});
}
#override
void initState() {
_taskList = List<TaskItemData>();
super.initState();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Align(
alignment: Alignment.topCenter,
child: ListView.builder(
padding: EdgeInsets.all(0.0),
itemExtent: 60.0,
itemCount: _taskList.length,
itemBuilder: (BuildContext context, int index) {
if (index < _taskList.length) {
return Dismissible(
key: ObjectKey(_taskList[index]),
onDismissed: (direction) {
if(this.mounted) {
setState(() {
_taskList.removeAt(index);
});
}
},
child: _taskList[index].widget,
);
}
}),
),
floatingActionButton: new FloatingActionButton(
onPressed: _addTaskDialog,
tooltip: 'Click to add a new task',
child: new Icon(Icons.add),
),
);
}
}

How to pass data from child widget to its parent

I've the below custom widget that make a Switch and reads its status (true/false)
Then I add this one to my main app widget (parent), how can I make the parent knows the value of the switch!
import 'package:flutter/material.dart';
class Switchy extends StatefulWidget{
Switchy({Key key}) : super(key: key);
#override
State<StatefulWidget> createState() => new _SwitchyState();
}
class _SwitchyState extends State<Switchy> {
var myvalue = true;
void onchange(bool value) {
setState(() {
this.myvalue = value; // I need the parent to receive this one!
print('value is: $value');
});
}
#override
Widget build(BuildContext context) {
return
new Card(
child: new Container(
child: new Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
new Text("Enable/Disable the app in the background",
textAlign: TextAlign.left,
textDirection: TextDirection.ltr,),
new Switch(value: myvalue, onChanged: (bool value) => onchange(value)),
],
),
),
);
}
}
In the main.dart (parent) file, I started with this:
import 'widgets.dart';
import 'package:flutter/material.dart';
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.deepOrange,
),
home: new MyHomePage(title: 'My App settup'),
);
}
}
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> {
Widget e = new Switchy();
//...
}
The first possibility is to pass a callback into your child, and the second is to use the of pattern for your stateful widget. See below.
import 'package:flutter/material.dart';
class MyStatefulWidget extends StatefulWidget {
#override
State<StatefulWidget> createState() => new MyStatefulWidgetState();
// note: updated as context.ancestorStateOfType is now deprecated
static MyStatefulWidgetState of(BuildContext context) =>
context.findAncestorStateOfType<MyStatefulWidgetState>();
}
class MyStatefulWidgetState extends State<MyStatefulWidget> {
String _string = "Not set yet";
set string(String value) => setState(() => _string = value);
#override
Widget build(BuildContext context) {
return new Column(
children: <Widget>[
new Text(_string),
new MyChildClass(callback: (val) => setState(() => _string = val))
],
);
}
}
typedef void StringCallback(String val);
class MyChildClass extends StatelessWidget {
final StringCallback callback;
MyChildClass({this.callback});
#override
Widget build(BuildContext context) {
return new Column(
children: <Widget>[
new FlatButton(
onPressed: () {
callback("String from method 1");
},
child: new Text("Method 1"),
),
new FlatButton(
onPressed: () {
MyStatefulWidget.of(context).string = "String from method 2";
},
child: new Text("Method 2"),
)
],
);
}
}
void main() => runApp(
new MaterialApp(
builder: (context, child) => new SafeArea(child: new Material(color: Colors.white, child: child)),
home: new MyStatefulWidget(),
),
);
There is also the alternative of using an InheritedWidget instead of a StatefulWidget; this is particularly useful if you want your child widgets to rebuild if the parent widget's data changes and the parent isn't a direct parent. See the inherited widget documentation
In 2020, the function in the highest voted answer is marked deprecated. So here is the modified solution based on that answer.
import 'package:flutter/material.dart';
class MyStatefulWidget extends StatefulWidget {
#override
State<StatefulWidget> createState() => new MyStatefulWidgetState();
// --> NOTE this! <--
static MyStatefulWidgetState of(BuildContext context) =>
context.findAncestorStateOfType<MyStatefulWidgetState>();
}
class MyStatefulWidgetState extends State<MyStatefulWidget> {
String _string = "Not set yet";
set string(String value) => setState(() => _string = value);
#override
Widget build(BuildContext context) {
return new Column(
children: <Widget>[
new Text(_string),
new MyChildClass(callback: (val) => setState(() => _string = val))
],
);
}
}
typedef void StringCallback(String val);
class MyChildClass extends StatelessWidget {
final StringCallback callback;
MyChildClass({this.callback});
#override
Widget build(BuildContext context) {
return new Column(
children: <Widget>[
new FlatButton(
onPressed: () {
callback("String from method 1");
},
child: new Text("Method 1"),
),
new FlatButton(
onPressed: () {
MyStatefulWidget.of(context).string = "String from method 2";
},
child: new Text("Method 2"),
)
],
);
}
}
void main() => runApp(
new MaterialApp(
builder: (context, child) => new SafeArea(child: new Material(color: Colors.white, child: child)),
home: new MyStatefulWidget(),
),
);
However, the methods mentioned in the answers of this question has a drawback. From doc:
In general, though, consider using a callback that triggers a stateful change in the ancestor rather than using the imperative style implied by this method. This will usually lead to more maintainable and reusable code since it decouples widgets from each other.
Calling this method is relatively expensive (O(N) in the depth of the tree). Only call this method if the distance from this widget to the desired ancestor is known to be small and bounded.
I think notifications are quite a civilized solution and they allow for a very clean communication without variable juggling and they bubble up if you need them to:
Define a notification:
class SwitchChanged extends Notification {
final bool val
SwitchChanged(this.val);
}
Raise notification in your child's event handler:
onPressed: () {
SwitchChanged(true).dispatch(context);
}
Finally, wrap your parent with notification listener:
NotificationListener<SwitchChanged>(
child: YourParent(...),
onNotification: (n) {
setState(() {
// Trigger action on parent via setState or do whatever you like.
});
return true;
}
)
You can pass a callback defined in the parent widget to the child widget and as soon as an action is performed in the child widget, the callback gets invoked.
class ParentWidget extends StatelessWidget {
// This gets called when the button is pressed in the ChildWidget.
void _onData(String data) {
print(data); // Hello World
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: ChildWidget(onData: _onData),
);
}
}
class ChildWidget extends StatelessWidget {
final void Function(String) onData;
ChildWidget({
super.key,
required this.onData,
});
#override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: () {
// Pass 'Hello World' to parent widget.
onData('Hello World');
},
child: Text('Button'),
);
}
}
Use InheritedWidget - https://api.flutter.dev/flutter/widgets/InheritedWidget-class.html
This lets you access data of the parent in all the children
I found a way to do this which was fairly simple, I'm a flutter noob so maybe it isn't the best way. If someone sees something wrong with it, feel free to leave a comment. Basically state is set in parent widget, child widget updates the state of the parent, and any child widgets of the parents which use the state values are redrawn when the value is updated.
Parent widget:
class MyWidget extends StatefulWidget {
const MyWidget({Key? key}) : super(key: key);
#override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
String _stringToChange = ""; // the string you want to update in child
// function to update state with changes to term
_updateStringToChange(String stringToChange) {
setState(() {
_stringToChange = stringToChange;
// Other logic you might want to do as string value changes
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'title',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: Scaffold(
appBar: AppBar(
title: const Center(
child: Text("app bar title"),
),
),
body: Column(children: <Widget>[
ChildWhichMakesChanges(
updateStringToChange: _updateStringToChange,
),
Expanded(
child: Container(
padding: const EdgeInsets.fromLTRB(20, 10, 0, 10),
child: ChildWhichUsesChanges(
stringToChange: _stringToChange,
)))
]),
));
}
}
ChildWhichMakesChanges (this example uses a text box to enter input):
class ChildWhichMakesChanges extends StatefulWidget {
final ValueChanged<String> updateStringToChange;
const ChildWhichMakesChanges({Key? key, required this.updateStringToChange}) : super(key: key);
#override
_TextInputState createState() => _TextInputState();
}
class _TextInputState extends State<ChildWhichMakesChanges> {
#override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 25),
child: TextField(
decoration: const InputDecoration(
border: OutlineInputBorder(),
hintText: 'Enter text',
),
onChanged: (String stringToChange) {
widget.updateStringToChange(stringToChange);
})),
]);
}
}
Using the changed string value in ChildWhichUsesChanges:
class ChildWhichUsesChanges extends StatelessWidget {
final String stringToChange;
const ChildWhichUsesChanges(
{Key? key,
required this.stringToChange})
: super(key: key);
#override
Widget build(BuildContext context) {
return Text(stringToChange)
}
}
2022 Solution:
A simple one.
Make it work like interface.
You can make your own custom CallBack Function just by defining typedef. It will just work as an interface between child to parent widget.
This is an IMP function:
typedef void GetColor(Color? color, String? string);
Following is Parent Widget:
import 'package:flutter/material.dart';
typedef void GetColor(Color? color, String? string);
class NavigationDialog extends StatefulWidget {
const NavigationDialog({Key? key}) : super(key: key);
#override
_NavigationDialogState createState() => _NavigationDialogState();
}
class _NavigationDialogState extends State<NavigationDialog> {
Color? color = Colors.blue[700];
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: color,
appBar: AppBar(
title: const Text('Navigation Dialog Screen'),
),
body: Center(
child: ElevatedButton(
child: const Text('Change Color'),
onPressed: () {
_showColorDialog(context, (value, string) {
setState(() {
color = value;
print(string);
});
});
}),
),
);
}
And Following is a child Widget Code:
_showColorDialog(BuildContext context, Function getColor) async {
color = null;
await showDialog(
barrierDismissible: false,
context: context,
builder: (_) {
return AlertDialog(
title: const Text('Very important question'),
content: const Text('Please choose a color'),
actions: <Widget>[
TextButton(
child: const Text('Red'),
onPressed: () {
color = Colors.red[700];
getColor(color, 'Red');// This line of action wil send your data back to parent
Navigator.pop(context, color);
}),
TextButton(
child: const Text('Green'),
onPressed: () {
color = Colors.green[700];
getColor(color, 'Green');// This line of action wil send your data back to parent
Navigator.pop(context, color);
}),
TextButton(
child: const Text('Blue'),
onPressed: () {
color = Colors.blue[700];
getColor(color, 'Blue');// This line of action wil send your data back to parent
Navigator.pop(context, color);
}),
],
);
},
);
}
}
In this example, We are selecting a color from Child Alert Dialog widget and pass to Parent widget.
Store the value in that child widget in shared preference, then access that shared preference value in the parent widget.

Resources