Setstate cannot update the value - flutter

I am a new starter for flutter, Currently I have a problem that Setstate cannot update the value.
If the user after select the date using date picker, it should be update the "_value" and display under "new Text('Current Date')".
It is successful to Setstate if I separate in Widget build, but it cannot update in List.
Where I got wrong?
import 'dart:convert';
import 'package:flutter/material.dart';
class AddFieldDynamicTest extends StatefulWidget {
#override
_AddFieldDynamicTest createState() => _AddFieldDynamicTest();
}
class _AddFieldDynamicTest extends State<AddFieldDynamicTest> {
Map<String, String> _formdata = {};
var _myPets = List<Widget>();
int _index = 1;
String _value = '';
Future _selectDate() async {
DateTime picked = await showDatePicker(
context: context,
initialDate: new DateTime.now(),
firstDate: new DateTime(2000),
lastDate: new DateTime(2100)
);
if(picked != null) setState(() => _value = picked.toString());
}
void _add() {
int keyValue = _index;
_myPets = List.from(_myPets)
..add(
Column(
key: Key("${keyValue}"),
children: <Widget>[
ListTile(
leading: Text('Pet $_index : '),
title: TextField(
onChanged: (val) => _formdata['pet${keyValue - 1}'] = val,
),
),
ListTile(
leading: Text('Name of Pet $_index : '),
title: TextField(
onChanged: (val) {
_formdata['name${keyValue - 1}'] = val;
},
),
),
new Column(
children: <Widget>[
new Text('Current Date'),
new Text(_value),
new RaisedButton(onPressed: _selectDate, child: new Text('Date picker'),)
],
)
],
));
setState(() => ++_index);
}
#override
void initState() {
// TODO: implement initState
super.initState();
_add();
}
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () => print(json.encode(_formdata)),
child: Text('Save'),
),
appBar: AppBar(
title: Text('Add Test 2'),
actions: <Widget>[
FlatButton(
child: Text('Add another'),
onPressed: _add,
),
],
),
body: ListView(
children: _myPets,
),
);
}
}

In your demo, you use a variable _myPets to store the ListView's children, and at initialization time you add a child into _myPets.
When the "Date Picker" of the child is clicked, the _value is updated and setState is performed, but the data in _myPets remains unchanged, so even if the build is redone, the UI will still remain unchanged.
At this point, if you click "Add Another", a new child will be inserted into _myPets, and since _value has a value, when the build is finished, you will see that the date selected by the last child will be displayed on the new child.
I think you should know more about how setState is used.
The best way to implement this scenario is to have your own setState for each child, as shown below (simplifying your code) :
class AddFieldDynamicTest extends StatefulWidget {
#override
_AddFieldDynamicTest createState() => _AddFieldDynamicTest();
}
class _AddFieldDynamicTest extends State<AddFieldDynamicTest> {
Map<String, String> _formdata = {};
var _myPets = List<Widget>();
int _index = 1;
void _add() {
_myPets = List.from(_myPets)
..add(AddFieldDynamicItem(_index));
setState(() {
_index += 1;
});
}
#override
void initState() {
// TODO: implement initState
super.initState();
_add();
}
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () => print(json.encode(_formdata)),
child: Text('Save'),
),
appBar: AppBar(
title: Text('Add Test 2'),
actions: <Widget>[
FlatButton(
child: Text('Add another'),
onPressed: _add,
),
],
),
body: ListView(
children: _myPets,
),
);
}
}
class AddFieldDynamicItem extends StatefulWidget {
AddFieldDynamicItem(this._index);
final int _index;
#override
_AddFieldDynamicItem createState() => _AddFieldDynamicItem(_index);
}
class _AddFieldDynamicItem extends State<AddFieldDynamicItem> {
_AddFieldDynamicItem(this._index);
String _value = '';
final int _index;
Future _selectDate() async {
DateTime picked = await showDatePicker(
context: context,
initialDate: new DateTime.now(),
firstDate: new DateTime(2000),
lastDate: new DateTime(2100)
);
if(picked != null)
setState(() {
_value = picked.toString();
});
}
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
new Column(
children: <Widget>[
new Text('$_index . Current Date'),
new Text(_value),
new RaisedButton(onPressed: _selectDate, child: new Text('Date picker'),)
],
)
],
);
}
}

When setState is called the framework is notified that the internal state of the object has changed and therefore it might be necessary to rebuild the widget tree. Hence the framework schedules a build for the state.
The build function is called to rebuild the widget tree. However your Text widget [Text(_value)] that you wish to redraw is within a function private function _add(). This function is not called during the build.
However as you have correctly found out that when you place the Text widget Text(_value) within the return of build function it is redrawn everytime setState is called.

I find a hint from this post:
Flutter - Dynamically adding widget that needs to change state as well
They should have their own state
import 'dart:convert';
import 'package:flutter/material.dart';
class AddFieldDynamicTest extends StatefulWidget {
#override
_AddFieldDynamicTest createState() => _AddFieldDynamicTest();
}
class _AddFieldDynamicTest extends State<AddFieldDynamicTest> {
List<Widget> playButtonList;
void initState() {
playButtonList = <Widget>[];
super.initState();
}
var eue='';
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Speech Aid'),
),
body: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
FlatButton(
child: Text('Add Play Button'),
color: Colors.black26,
onPressed: () {
setState(() {
playButtonList.add(PlayButton());
});
},
),
Expanded(
child: ListView.builder(
itemCount: playButtonList.length,
itemBuilder: (context, index) => playButtonList[index],
),
)
],
),
);
}
}
class PlayButton extends StatefulWidget {
#override
_PlayButtonState createState() => _PlayButtonState();
}
class _PlayButtonState extends State<PlayButton> {
int _index = 0;
#override
void initState() {
super.initState();
}
Map<String, dynamic> _formdata={};
var _value='';
#override
Widget build(BuildContext context) {
int keyValue = _index;
return Column(
key: Key("${keyValue}"),
children: <Widget>[
ListTile(
leading: Text('Pet $_index : '),
title: TextField(
onChanged: (val) => _formdata['pet${keyValue - 1}'] = val,
),
),
ListTile(
leading: Text('Name of Pet $_index : '),
title: TextField(
onChanged: (val) {
_formdata['name${keyValue - 1}'] = val;
},
),
),
new Column(
children: <Widget>[
new Text('Current Date'),
new Text(_value),
new RaisedButton(onPressed: _selectDate, child: new Text('Date picker'),)
],
)
],
);
}
Future _selectDate() async {
DateTime picked = await showDatePicker(
context: context,
initialDate: new DateTime.now(),
firstDate: new DateTime(2000),
lastDate: new DateTime(2100)
);
if(picked != null) setState(() => _value = picked.toString());
}
}

Related

increment counter for a specific list item in Flutter

Good evening, As in my example image, below, I want to increase or decrease the quantity on button click for a single item in the listing. If I increment the counter in setState(), it is incremented in each element of the list. From what I understand you need to find the index, but it doesn't work (I tried different solutions where there is onPressed)
enter image description here
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:db_local/db.dart';
import 'package:db_local/edit_student.dart';
class ListStudents extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _ListStudents();
}
}
class _ListStudents extends State<ListStudents> {
int _itemCount = 0;
List<Map> slist = [];
MyDb mydb = MyDb();
#override
void initState() {
mydb.open();
getdata();
super.initState();
}
getdata() {
Future.delayed(const Duration(milliseconds: 500), () async {
slist = await mydb.db.rawQuery('SELECT * FROM students');
setState(() {});
});
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("List of Students"),
),
body: SingleChildScrollView(
child: Container(
child: slist.isEmpty
? const Text("No any students to show.")
: Column(
children: slist.map((stuone) {
ListView.builder(
itemCount: slist.length,
itemBuilder: (context, index) {
List<int> itemCounts =
List.generate(slist.length, (_) => 0);
return Card(
child: ListTile(
leading: const Icon(Icons.people),
title: Text(stuone["name"] + " " + stuone["ID"]),
subtitle: Text("Numero Carte:" +
stuone["roll_no"].toString() +
", Add: " +
stuone["address"]),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
IconButton(
icon: const Icon(Icons.remove),
onPressed: () =>
setState(() => itemCounts[index]--),
),
Text('$itemCounts'),
IconButton(
icon: const Icon(Icons.add),
onPressed: () =>
setState(() => itemCounts[index]++),
),
],
),
));
},
);
}).toList(),
),
),
),
);
}
}
Update Code
class ListStudents extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _ListStudents();
}
}
class _ListStudents extends State<ListStudents> {
//int _counts = 0;
List<int> _itemCounts;
List<Map> slist = [];
MyDb mydb = MyDb();
//_ListStudents(this._itemCounts);
#override
void initState() {
mydb.open();
getdata();
super.initState();
}
getdata() {
Future.delayed(const Duration(milliseconds: 500), () async {
slist = await mydb.db.rawQuery('SELECT * FROM students');
_itemCounts = List.generate(slist.length, (_) => 0);
setState(() {});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("List of Students"),
),
body: slist.isEmpty
? Text('No any students to show.')
: ListView.builder(
itemCount: slist.length,
itemBuilder: (context, index) {
final stuone = slist[index];
return Card(
child: ListTile(
leading: const Icon(Icons.people),
title: Text(stuone["name"] + " " + stuone["ID"]),
subtitle: Text("Numero Carte:" +
stuone["roll_no"].toString() +
", Add: " +
stuone["address"]),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
IconButton(
icon: const Icon(Icons.remove),
onPressed: () => setState(() => _itemCounts[index]--),
),
Text(_itemCounts[index].toString()),
IconButton(
icon: const Icon(Icons.add),
onPressed: () => setState(() => _itemCounts[index]++),
),
],
),
),
);
},
),
);
}
}
Instead of slist.map((stuone) {...} you could use the ListView.builder() instead which gives you the index for each element that will be displayed.
So something like this:
ListView.builder(
itemCount: slist.length,
itemBuilder: (context, index) {
return Card(
child: ListTile( ... )
),
},
)
There are different options for storing the state. But without to much intervention you could instead of int _itemCount = 0 have a List<int> itemCounts = List.generate(slist.length, (_) => 0); which you set after you've fetched the data and assign to a state variable.
You can then for each button, increment or decrement the value e.g. as: itemCounts[index]++;
Update to clarify:
Obviously not tested, but this should give you directions for how to construct the code and use the widgets. You shouldn't use the ListView as you did within a column within a scrollview.
// under class _ListStudents extends State<ListStudents> {
late final List<int> _itemCounts;
// assign _itemCounts in getData()
getdata() {
Future.delayed(const Duration(milliseconds: 500), () async {
slist = await mydb.db.rawQuery('SELECT * FROM students');
_itemCounts = List.generate(slist.length, (_) => 0);
setState(() {});
});
}
// When building your scaffold:
body: slist.isEmpty ?
Text('No any students to show.') :
ListView.builder(
itemCount: slist.length,
itemBuilder: (context, index) {
final stuone = slist[index];
return Card(
....
);
}
)

Flutter add value to another class list

I want to add the date picker value to the List.
When user after select the date, I will calling function [addToDateList] in _AddFieldDynamicItem.
For this function, it will add to list:
List <DateTime> dateList=[];
Which is in _AddFieldDynamicTest
After click "Add another", I don't know why that it only can record the latest picker value.
Did I need to using another storage method such as sql lite or shared preferences?
import 'dart:convert';
import 'package:flutter/material.dart';
class AddFieldDynamicTest extends StatefulWidget {
#override
_AddFieldDynamicTest createState() => _AddFieldDynamicTest();
}
class _AddFieldDynamicTest extends State<AddFieldDynamicTest> {
Map<String, String> _formdata = {};
var _myPets = List<Widget>();
List <DateTime> dateList=[];
int _index = 1;
void _add() {
_myPets = List.from(_myPets)
..add(AddFieldDynamicItem(_index));
setState(() {
_index += 1;
});
}
#override
void initState() {
// TODO: implement initState
super.initState();
_add();
}
addToDateList(DateTime d){
dateList.add(d);
}
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () => getDateList(),
child: Text('Save'),
),
appBar: AppBar(
title: Text('Add Test 2'),
actions: <Widget>[
FlatButton(
child: Text('Add another'),
onPressed: _add,
),
],
),
body: ListView(
children: _myPets,
),
);
}
getDateList(){
print(dateList.length);
}
}
class AddFieldDynamicItem extends StatefulWidget {
AddFieldDynamicItem(this._index);
final int _index;
#override
_AddFieldDynamicItem createState() => _AddFieldDynamicItem(_index);
}
class _AddFieldDynamicItem extends State<AddFieldDynamicItem> {
_AddFieldDynamicItem(this._index);
String _value = '';
final int _index;
List<DateTime> d=[];
Future _selectDate() async {
DateTime picked = await showDatePicker(
context: context,
initialDate: new DateTime.now(),
firstDate: new DateTime(2000),
lastDate: new DateTime(2100)
);
if(picked != null)
_AddFieldDynamicTest().addToDateList(picked);
setState(() {
_value = picked.toString();
});
}
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
new Column(
children: <Widget>[
new Text('$_index . Current Date'),
new Text(_value),
new RaisedButton(onPressed: _selectDate, child: new Text('Date picker'),)
],
)
],
);
}
}
"_AddFieldDynamicTest().addToDateList(picked);" is make new instance.
So, fix like this.
from
_myPets = List.from(_myPets)
..add(AddFieldDynamicItem(_index));
to
_myPets = List.from(_myPets)
..add(AddFieldDynamicItem(_index, addToDateList));
from
AddFieldDynamicItem(this._index);
final int _index;
to
AddFieldDynamicItem(this._index, this.addToDateList);
final int _index;
final Function(DateTime) addToDateList;
from
if(picked != null)
_AddFieldDynamicTest().addToDateList(picked);
to
if (picked != null) widget.addToDateList(picked);

FutureBuilder and async function in flutter

I have a problem with FutureBuilder, it refresh and execute code again when there is a change like when i change value of radio button, i click on radio and it reloads all futurebuilder it seems.
EDIT : i have corrected the problem and here is my solution, i am not sure it works all time
My full code is :
import 'package:flutter/material.dart';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'dart:async';
// Create a Form widget.
class Affiche_grille extends StatefulWidget {
#override
_Affiche_grille_State createState() {
return _Affiche_grille_State();
}
}
// Create a corresponding State class.
// This class holds data related to the form.
class _Affiche_grille_State extends State<Affiche_grille> {
#override
final _formKey = GlobalKey<FormState>();
List<String> radioValues = [];
Future<List<Match>> grid;
Future <List<Match>> Grille_display() async {
// SERVER LOGIN API URL
var url = 'http://www.axis-medias.fr/game_app/display_grid.php';
// Store all data with Param Name.
var data = {'id_grille': 1};
// Starting Web API Call.
var response = await http.post(url, body: json.encode(data));
// Getting Server response into variable.
var jsondata = json.decode(response.body);
List<Match> Matchs = [];
for (var u in jsondata) {
Match match = Match(u["equipe1"],u["equipe2"],u["type_prono"]);
Matchs.add(match);
radioValues.add("N");
}
return Matchs;
}
void initState() {
grid = Grille_display();
super.initState();
}
#override
Widget build(BuildContext context) {
final appTitle = 'MONEYFREE';
return MaterialApp(
title: appTitle,
home: Scaffold(
appBar: AppBar(
title: Text(appTitle),
),
body: Container(
child:
FutureBuilder(
future: grid,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.data == null) {
return Container (
child: Center(
child: Text("Chargement en cours...")
)
);
}
else {
List<Match> values = snapshot.data;
return Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
DataTable(
columnSpacing: 20,
columns: [
DataColumn(
label: Text("Libelle Match"),
numeric: false,
tooltip: "",
),
DataColumn(
label: Text("1"),
numeric: false,
tooltip: "",
),
DataColumn(
label: Text("N"),
numeric: false,
tooltip: "",
),
DataColumn(
label: Text("2"),
numeric: false,
tooltip: "",
),
],
rows:
List.generate(values.length, (index) {
return DataRow(
cells: [
DataCell(
Text(values[index].equipe1.toString() + " - " + values[index].equipe2.toString()),
),
DataCell(
Radio(
value: "1",
groupValue: radioValues[index],
onChanged: (val) {
setState(() {
radioValues[index] = val;
print('Change 1');
print(radioValues);
});
},
),
),
DataCell(
Radio(
value: "N",
groupValue: radioValues[index],
onChanged: (val) {
setState(() {
radioValues[index] = val;
print('Change N');
print(radioValues);
});
},
),
),
DataCell(
Radio(
value: "2",
groupValue: radioValues[index],
onChanged: (val) {
setState(() {
radioValues[index] = val;
print('Change 2');
print(radioValues);
});
},
),
),
]
);
}).toList(),
),
Center(
child: RaisedButton(
color: Colors.green,
textColor: Colors.white,
padding: EdgeInsets.fromLTRB(9, 9, 9, 9),
child: Text('VALIDER VOTRE GRILLE'),
onPressed: () {
Valide_grille();
},
),
),
],
)
);
};
},
),
),
),
);
}
Future Valide_grille() async{
// For CircularProgressIndicator.
bool visible = false ;
// Showing CircularProgressIndicator.
setState(() {
visible = true ;
});
// SERVER LOGIN API URL
var url = 'http://www.axis-medias.fr/game_app/valide_grid.php';
var concatenate='';
radioValues.forEach((item){
concatenate=concatenate+item;
});
// Store all data with Param Name.
var data = {'id_membre':1, 'id_grille':1,'result':concatenate};
print (data);
var grille_encode=jsonEncode(data);
print(grille_encode);
// Starting Web API Call.
var response = await http.post(url, body: grille_encode);
print(response.body);
// Getting Server response into variable.
var message = json.decode(response.body);
// If the Response Message is Matched.
if(message == 'OK')
{
print('VALIDATION DE LA GRILLE OK');
// Hiding the CircularProgressIndicator.
setState(() {
visible = false;
});
}else{
// If Email or Password did not Matched.
// Hiding the CircularProgressIndicator.
setState(() {
visible = false;
});
// Showing Alert Dialog with Response JSON Message.
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: new Text(message),
actions: <Widget>[
FlatButton(
child: new Text("OK"),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
}
}
class Match {
final String equipe1;
final String equipe2;
final String typeprono;
const Match(this.equipe1, this.equipe2, this.typeprono);
}
You can copy paste run full code below
Reason
didUpdateWidget of the FutureBuilder state is being called every time a rebuild is issued. This function checks if the old future object is different from the new one, and if so, refires the FutureBuilder.
https://github.com/flutter/flutter/issues/11426#issuecomment-414047398
Solution
Future _future;
#override
void initState() {
// TODO: implement initState
_future = Grille_display();
}
...
child: FutureBuilder(
future: _future,
working demo
full code
import 'package:flutter/material.dart';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'dart:async';
// Create a Form widget.
class Affiche_grille extends StatefulWidget {
#override
_Affiche_grille_State createState() {
return _Affiche_grille_State();
}
}
// Create a corresponding State class.
// This class holds data related to the form.
class _Affiche_grille_State extends State<Affiche_grille> {
#override
final _formKey = GlobalKey<FormState>();
List<String> radioValues = [];
Future<List<Match>> Grille_display() async {
// SERVER LOGIN API URL
var url = 'http://www.axis-medias.fr/game_app/display_grid.php';
// Store all data with Param Name.
var data = {'id_grille': 1};
// Starting Web API Call.
var response = await http.post(url, body: json.encode(data));
// Getting Server response into variable.
var jsondata = json.decode(response.body);
List<Match> Matchs = [];
for (var u in jsondata) {
Match match = Match(u["equipe1"], u["equipe2"], u["type_prono"]);
Matchs.add(match);
}
return Matchs;
}
Future _future;
#override
void initState() {
// TODO: implement initState
_future = Grille_display();
}
#override
Widget build(BuildContext context) {
final appTitle = 'MONEYFREE';
return MaterialApp(
title: appTitle,
home: Scaffold(
appBar: AppBar(
title: Text(appTitle),
),
body: Container(
child: FutureBuilder(
future: _future,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.data == null) {
return Container(
child: Center(child: Text("Chargement en cours...")));
} else {
List<Match> values = snapshot.data;
values.forEach((m) {
radioValues.add("N");
//like N or something
});
print('valeur radio après initialisation');
print(radioValues);
return Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
DataTable(
columnSpacing: 20,
columns: [
DataColumn(
label: Text("Libelle Match"),
numeric: false,
tooltip: "",
),
DataColumn(
label: Text("1"),
numeric: false,
tooltip: "",
),
DataColumn(
label: Text("N"),
numeric: false,
tooltip: "",
),
DataColumn(
label: Text("2"),
numeric: false,
tooltip: "",
),
],
rows: List.generate(values.length, (index) {
return DataRow(cells: [
DataCell(
Text(values[index].equipe1.toString() +
" - " +
values[index].equipe2.toString()),
),
DataCell(
Radio(
value: "1",
groupValue: radioValues[index],
onChanged: (val) {
setState(() {
radioValues[index] = val;
print('Change 1');
print(radioValues);
});
},
),
),
DataCell(
Radio(
value: "N",
groupValue: radioValues[index],
onChanged: (val) {
setState(() {
radioValues[index] = val;
print(radioValues);
});
},
),
),
DataCell(
Radio(
value: "2",
groupValue: radioValues[index],
onChanged: (val) {
setState(() {
radioValues[index] = val;
print(radioValues);
});
},
),
),
]);
}).toList(),
),
Center(
child: RaisedButton(
color: Colors.green,
textColor: Colors.white,
padding: EdgeInsets.fromLTRB(9, 9, 9, 9),
child: Text('VALIDER VOTRE GRILLE'),
onPressed: () {
Valide_grille();
},
),
),
],
));
}
;
},
),
),
),
);
}
Future Valide_grille() async {
// For CircularProgressIndicator.
bool visible = false;
// Showing CircularProgressIndicator.
setState(() {
visible = true;
});
// SERVER LOGIN API URL
var url = 'http://www.axis-medias.fr/game_app/valide_grid.php';
// Store all data with Param Name.
var data = jsonEncode(radioValues);
print(radioValues);
// Starting Web API Call.
var response = await http.post(url, body: json.encode(data));
// Getting Server response into variable.
var message = json.decode(response.body);
// If the Response Message is Matched.
if (message == 'OK') {
print('VALIDATION DE LA GRILLE OK');
// Hiding the CircularProgressIndicator.
setState(() {
visible = false;
});
} else {
// If Email or Password did not Matched.
// Hiding the CircularProgressIndicator.
setState(() {
visible = false;
});
// Showing Alert Dialog with Response JSON Message.
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: new Text(message),
actions: <Widget>[
FlatButton(
child: new Text("OK"),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
},
);
}
}
}
class Match {
final String equipe1;
final String equipe2;
final String typeprono;
const Match(this.equipe1, this.equipe2, this.typeprono);
}
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Affiche_grille(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}

Dynamic list of check box tile in alert dialog not working

There is no clear answer on how to implement a checkbox tile in a dialog and set the state to work.
A print statement is working in setting the state of the checkbox is not changing, but other statements are working. Where can I find the answer?
I am using a dialog with multiple check boxes for multi select. Is there another of implementing multiselect in Flutter?
child: TextFormField(
decoration: InputDecoration(
labelText: 'Team Leader',
labelStyle: TextStyle(color: Colors.black)),
controller: teamLeaderController,
enabled: false,
style: TextStyle(color: Colors.black),
),
onTap: () {
showDialog(
context: context,
barrierDismissible: true,
builder: (BuildContext context) {
return CheckBoxDialog(context, teamLeader,
"Choose Team Leader", teamLeaderController, onSubmit);
});
}),
class CheckBoxState extends State<CheckBoxDialog> {
BuildContext context;
List<String> places;
String title;
TextEditingController con;
bool state;
CheckBoxState(this.context, this.places, this.title, this.con);
#override
void initState() {
super.initState();
state = false;
}
#override
Widget build(BuildContext context) {
return new AlertDialog(
title: new Text(title),
content:
Column(children: getMultiSelectOption(context, places, con, state)),
actions: <Widget>[
FlatButton(
child: Text('Cancel'),
onPressed: () {
Navigator.of(context).pop();
}),
FlatButton(
child: Text('Ok'),
onPressed: () {
widget.onSubmit("");
Navigator.of(context).pop();
})
],
);
}
List<Widget> getMultiSelectOption(BuildContext context, List<String> places,
TextEditingController con, bool state) {
List<Widget> options = [];
List<String> selectedList = [];
for (int i = 0; i < places.length; i++) {
options.add(CheckboxListTile(
title: Text(places[i]),
value: selectedList.contains(places[i]),
onChanged: (bool value) {
print("on change: $value title: ${places[i]}");
setState(() {
if (value) {
selectedList.add(places[i]);
} else {
selectedList.remove(places[i]);
}
print("contains: ${selectedList.contains(places[i])}");
print("status: $value");
});
}));
}
return options;
}
}
Suppose you have a Dialog with some Widgets such as RadioListTile, DropdowButton… or anything that might need to be updated WHILE the dialog remains visible, how to do it?
Look at this example here.
https://www.didierboelens.com/2018/05/hint-5-how-to-refresh-the-content-of-a-dialog-via-setstate/
Suppose you have a Dialog with some Widgets such as RadioListTile, DropdowButton… or anything that might need to be updated WHILE the dialog remains visible, how to do it?
Difficulty: Beginner
Background
Lately I had to display a Dialog to let the user select an item from a list and I wanted to display a list of RadioListTile.
I had no problem to show the Dialog and display the list, via the following source code:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
class Sample extends StatefulWidget {
#override
_SampleState createState() => new _SampleState();
}
class _SampleState extends State<Sample> {
List<String> countries = <String>['Belgium','France','Italy','Germany','Spain','Portugal'];
int _selectedCountryIndex = 0;
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_){_showDialog();});
}
_buildList(){
if (countries.length == 0){
return new Container();
}
return new Column(
children: new List<RadioListTile<int>>.generate(
countries.length,
(int index){
return new RadioListTile<int>(
value: index,
groupValue: _selectedCountryIndex,
title: new Text(countries[index]),
onChanged: (int value) {
setState((){
_selectedCountryIndex = value;
});
},
);
}
)
);
}
_showDialog() async{
await showDialog<String>(
context: context,
builder: (BuildContext context){
return new CupertinoAlertDialog(
title: new Text('Please select'),
actions: <Widget>[
new CupertinoDialogAction(
isDestructiveAction: true,
onPressed: (){Navigator.of(context).pop('Cancel');},
child: new Text('Cancel'),
),
new CupertinoDialogAction(
isDestructiveAction: true,
onPressed: (){Navigator.of(context).pop('Accept');},
child: new Text('Accept'),
),
],
content: new SingleChildScrollView(
child: new Material(
child: _buildList(),
),
),
);
},
barrierDismissible: false,
);
}
#override
Widget build(BuildContext context) {
return new Container();
}
}
I was surprised to see that despite the setState in lines #34-36, the selected RadioListTile was not refreshed when the user tapped one of the items.
Explanation
After some investigation, I realized that the setState() refers to the stateful widget in which the setState is invoked. In this example, any call to the setState() rebuilds the view of the Sample Widget, and not the one of the content of the dialog. Therefore, how to do?
Solution
A very simple solution is to create another stateful widget that renders the content of the dialog. Then, any invocation of the setState will rebuild the content of the dialog.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
class Sample extends StatefulWidget {
#override
_SampleState createState() => new _SampleState();
}
class _SampleState extends State<Sample> {
List<String> countries = <String>['Belgium','France','Italy','Germany','Spain','Portugal'];
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_){_showDialog();});
}
_showDialog() async{
await showDialog<String>(
context: context,
builder: (BuildContext context){
return new CupertinoAlertDialog(
title: new Text('Please select'),
actions: <Widget>[
new CupertinoDialogAction(
isDestructiveAction: true,
onPressed: (){Navigator.of(context).pop('Cancel');},
child: new Text('Cancel'),
),
new CupertinoDialogAction(
isDestructiveAction: true,
onPressed: (){Navigator.of(context).pop('Accept');},
child: new Text('Accept'),
),
],
content: new SingleChildScrollView(
child: new Material(
child: new MyDialogContent(countries: countries),
),
),
);
},
barrierDismissible: false,
);
}
#override
Widget build(BuildContext context) {
return new Container();
}
}
class MyDialogContent extends StatefulWidget {
MyDialogContent({
Key key,
this.countries,
}): super(key: key);
final List<String> countries;
#override
_MyDialogContentState createState() => new _MyDialogContentState();
}
class _MyDialogContentState extends State<MyDialogContent> {
int _selectedIndex = 0;
#override
void initState(){
super.initState();
}
_getContent(){
if (widget.countries.length == 0){
return new Container();
}
return new Column(
children: new List<RadioListTile<int>>.generate(
widget.countries.length,
(int index){
return new RadioListTile<int>(
value: index,
groupValue: _selectedIndex,
title: new Text(widget.countries[index]),
onChanged: (int value) {
setState((){
_selectedIndex = value;
});
},
);
}
)
);
}
#override
Widget build(BuildContext context) {
return _getContent();
}
}

Flutter Stateful Widget State not Initializing

I'm making a command and control application using Flutter, and have come across an odd problem. The main status page of the app shows a list of stateful widgets, which each own a WebSocket connection that streams state data from a connected robotic platform. This worked well when the robots themselves were hardcoded in. However now that I'm adding them dynamically (via barcode scans), only the first widget is showing status.
Further investigation using the debugger shows that this is due to the fact that a state is only getting created for the first widget in the list. Subsequently added widgets are successfully getting constructed, but are not getting a state. Meaning that createState is not getting called for anything other than the very first widget added. I checked that the widgets themselves are indeed being added to the list and that they each have unique hash codes. Also, the IOWebSocketChannel's have unique hash codes, and all widget data is correct and unique for the different elements in the list.
Any ideas as to what could be causing this problem?
Code for the HomePageState:
class HomePageState extends State<HomePage> {
String submittedString = "";
StateContainerState container;
List<RobotSummary> robotList = [];
List<String> robotIps = [];
final GlobalKey<ScaffoldState> scaffoldKey = new GlobalKey<ScaffoldState>();
void addRobotToList(String ipAddress) {
var channel = new IOWebSocketChannel.connect('ws://' + container.slsData.slsIpAddress + ':' + container.slsData.wsPort);
channel.sink.add("http://" + ipAddress);
var newConnection = new RobotSummary(key: new UniqueKey(), channel: channel, ipAddress: ipAddress, state: -1, fullAddress: 'http://' + container.slsData.slsIpAddress + ':' + container.slsData.wsPort,);
scaffoldKey.currentState.showSnackBar(new SnackBar(
content: new Text("Adding robot..."), duration: Duration(seconds: 2),));
setState(() {
robotList.add(newConnection);
robotIps.add(ipAddress);
submittedString = ipAddress;
});
}
void _onSubmit(String val) {
// Determine the scan data that was entered
if(Validator.isIP(val)) {
if(ModalRoute.of(context).settings.name == '/') {
if (!robotIps.contains(val)) {
addRobotToList(val);
}
else {
scaffoldKey.currentState.showSnackBar(new SnackBar(
content: new Text("Robot already added..."), duration: Duration(seconds: 5),));
}
}
else {
setState(() {
_showSnackbar("Robot scanned. Go to page?", '/');
});
}
}
else if(Validator.isSlotId(val)) {
setState(() {
_showSnackbar("Slot scanned. Go to page?", '/slots');
});
}
else if(Validator.isUPC(val)) {
setState(() {
_showSnackbar("Product scanned. Go to page?", '/products');
});
}
else if (Validator.isToteId(val)) {
}
}
#override
Widget build(BuildContext context) {
container = StateContainer.of(context);
return new Scaffold (
key: scaffoldKey,
drawer: Drawer(
child: CategoryRoute(),
),
appBar: AppBar(
title: Text(widget.topText),
),
bottomNavigationBar: BottomAppBar(
child: new Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
IconButton(icon: Icon(Icons.camera_alt), onPressed: scan,),
IconButton(icon: Icon(Icons.search), onPressed: _showModalSheet,),
],
),
),
body: robotList.length > 0 ? ListView(children: robotList) : Center(child: Text("Please scan a robot.", style: TextStyle(fontSize: 24.0, color: Colors.blue),),),
);
}
void _showModalSheet() {
showModalBottomSheet(
context: context,
builder: (builder) {
return _searchBar(context);
});
}
void _showSnackbar(String message, String route) {
scaffoldKey.currentState.showSnackBar(new SnackBar(
content: new Text(message),
action: SnackBarAction(
label: 'Go?',
onPressed: () {
if (route == '/') {
Navigator.popUntil(context,ModalRoute.withName('/'));
}
else {
Navigator.of(context).pushNamed(route);
}
},),
duration: Duration(seconds: 5),));
}
Widget _searchBar(BuildContext context) {
return new Scaffold(
body: Container(
height: 75.0,
color: iam_blue,
child: Center(
child: TextField(
style: TextStyle (color: Colors.white, fontSize: 18.0),
autofocus: true,
keyboardType: TextInputType.number,
onSubmitted: (String submittedStr) {
Navigator.pop(context);
_onSubmit(submittedStr);
},
decoration: new InputDecoration(
border: InputBorder.none,
hintText: 'Scan a tote, robot, UPC, or slot',
hintStyle: TextStyle(color: Colors.white70),
icon: const Icon(Icons.search, color: Colors.white70,)),
),
)));
}
Future scan() async {
try {
String barcode = await BarcodeScanner.scan();
setState(() => this._onSubmit(barcode));
} on PlatformException catch (e) {
if (e.code == BarcodeScanner.CameraAccessDenied) {
setState(() {
print('The user did not grant the camera permission!');
});
} else {
setState(() => print('Unknown error: $e'));
}
} on FormatException{
setState(() => print('null (User returned using the "back"-button before scanning anything. Result)'));
} catch (e) {
setState(() => print('Unknown error: $e'));
}
}
}
Code snippet for the RobotSummary class:
import 'package:flutter/material.dart';
import 'package:meta/meta.dart';
import 'package:test_app/genericStateSummary_static.dart';
import 'dart:convert';
import 'package:web_socket_channel/web_socket_channel.dart';
import 'package:test_app/StateDecodeJsonFull.dart';
import 'dart:async';
import 'package:test_app/dataValidation.dart';
class RobotSummary extends StatefulWidget {
final String ipAddress;
final String _port = '5000';
final int state;
final String fullAddress;
final WebSocketChannel channel;
RobotSummary({
Key key,
#required this.ipAddress,
#required this.channel,
this.state = -1,
this.fullAddress = "http://10.1.10.200:5000",
}) : assert(Validator.isIP(ipAddress)),
super(key: key);
#override
_RobotSummaryState createState() => new _RobotSummaryState();
}
class _RobotSummaryState extends State<RobotSummary> {
StreamController<StateDecodeJsonFull> streamController;
#override
void initState() {
super.initState();
streamController = StreamController.broadcast();
}
#override
Widget build(BuildContext context) {
return new Padding(
padding: const EdgeInsets.all(20.0),
child: new StreamBuilder(
stream: widget.channel.stream,
builder: (context, snapshot) {
//streamController.sink.add('{"autonomyControllerState" : 3, "pickCurrentListName" : "69152", "plannerExecutionProgress" : 82, "pickUpcCode" : "00814638", "robotName" : "Adam"}');
return getStateWidget(snapshot);
},
),
);
}
#override
void dispose() {
streamController.sink.close();
super.dispose();
}
}
Based on what Jacob said in his initial comments, I came up with a solution that works and is a combination of his suggestions. The code solution he proposed above can't be implemented (see my comment), but perhaps a modification can be attempted that takes elements of it. For the solution I'm working with now, the builder call for HomePageState becomes as follows:
Widget build(BuildContext context) {
List<RobotSummary> tempList = [];
if (robotList.length > 0) {
tempList.addAll(robotList);
}
container = StateContainer.of(context);
return new Scaffold (
key: scaffoldKey,
drawer: Drawer(
child: CategoryRoute(),
),
appBar: AppBar(
title: Text(widget.topText),
),
bottomNavigationBar: BottomAppBar(
child: new Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
IconButton(icon: Icon(Icons.camera_alt), onPressed: scan,),
IconButton(icon: Icon(Icons.search), onPressed: _showModalSheet,),
],
),
),
body: robotList.length > 0 ? ListView(children: tempList) : Center(child: Text("Please scan a robot.", style: TextStyle(fontSize: 24.0, color: iam_blue),),),
);
}
The problem is you are holding on to the StatefulWidgets between build calls, so their state is always the same. Try separating RobotSummary business logic from the view logic. Something like
class RobotSummary {
final String ipAddress;
final String _port = '5000';
final int state;
final String fullAddress;
final WebSocketChannel channel;
StreamController<StateDecodeJsonFull> streamController;
RobotSummary({
#required this.ipAddress,
#required this.channel,
this.state = -1,
this.fullAddress = "http://10.1.10.200:5000",
}) : assert(Validator.isIP(ipAddress));
void init() => streamController = StreamController.broadcast();
void dispose() => streamController.sink.close();
}
And then in your Scaffold body:
...
body: ListView.builder(itemCount: robotList.length, itemBuilder: _buildItem)
...
Widget _buildItem(BuildContext context, int index) {
return new Padding(
padding: const EdgeInsets.all(20.0),
child: new StreamBuilder(
stream: robotList[index].channel.stream,
builder: (context, snapshot) {
//streamController.sink.add('{"autonomyControllerState" : 3, "pickCurrentListName" : "69152", "plannerExecutionProgress" : 82, "pickUpcCode" : "00814638", "robotName" : "Adam"}');
return getStateWidget(snapshot); // not sure how to change this.
},
),
);
}