import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class Todo {
String title;
String description;
Todo(this.title, this.description);
}
class TextEditingControllerWorkaroud extends TextEditingController {
TextEditingControllerWorkaroud({String text}) : super(text: text);
void setTextAndPosition(String newText, int caretPosition) {
int offset = caretPosition != null ? caretPosition : newText.length;
value = value.copyWith(
text: newText,
selection: TextSelection.collapsed(offset: offset),
composing: TextRange.empty);
}
}
void main() {
runApp(MaterialApp(
title: 'Passing Data',
home: TodosScreen(
todos: List.generate(
20,
(i) => Todo(
'Todo $i',
'A description of what needs to be done for Todo $i',
),
),
),
));
}
class TodosScreen extends StatelessWidget {
final List<Todo> todos;
TodosScreen({Key key, #required this.todos}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Todos'),
),
body: ListView.builder(
itemCount: todos.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(todos[index].title),
onTap: () async {
Map results = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailScreen1(todo: todos[index]),
),
);
if (results["new"]!=results["old"] || results["newTitle"]!=results["oldTitle"]){
todos[index].description = results["new"];
todos[index].title = results["oldTitle"];
final snackBar = SnackBar(duration: Duration(milliseconds: 2000),
content: Text('Todo Saved Succesfully'),
action: SnackBarAction(
label: 'Undo',
onPressed: () {
print("go back to old");
todos[index].description = results["old"];
todos[index].title = results["oldTitle"];
},
),
);
// Find the Scaffold in the Widget tree and use it to show a SnackBar!
Scaffold.of(context).hideCurrentSnackBar();
Scaffold.of(context).showSnackBar(snackBar);
}
},
);
},
),
);
}
}
class DetailScreen1 extends StatefulWidget {
final Todo todo;
DetailScreen1({Key key, #required this.todo}) : super(key: key);
#override
DetailScreen1State createState() => DetailScreen1State();
}
class DetailScreen1State extends State<DetailScreen1> {
var descriptionTextContent = "";
var titleTextContent = "";
#override
void initState() {
super.initState();
print("intialized");
descriptionTextContent = widget.todo.description;
titleTextContent = widget.todo.title;
}
#override
Widget build(BuildContext context) {
TextEditingControllerWorkaroud descriptionEditWidgetController =
TextEditingControllerWorkaroud(text: descriptionTextContent);
TextEditingControllerWorkaroud titleEditWidgetController =
TextEditingControllerWorkaroud(text: titleTextContent);
TextField descriptionEditWidget = new TextField(
maxLines: 4,
keyboardType: TextInputType.multiline,
controller: descriptionEditWidgetController,
onChanged: (value) {
handleCurrentText(value, descriptionEditWidgetController);
},
);
TextField titleEditWidget = new TextField(
maxLines: 1,
keyboardType: TextInputType.text,
controller: titleEditWidgetController,
onChanged: (value) {
handleCurrentTitle(value, titleEditWidgetController);
},
);
descriptionEditWidgetController.setTextAndPosition(
descriptionTextContent, descriptionTextContent.length);
titleEditWidgetController.setTextAndPosition(
titleTextContent, titleTextContent.length);
return WillPopScope(
child: Scaffold(
appBar: AppBar(
title: Text(widget.todo.title),
leading: new IconButton(
icon: new Icon(Icons.arrow_back),
onPressed: () {
SystemChannels.textInput.invokeMethod('TextInput.hide');
Navigator.pop(context, {
'new': descriptionTextContent,
"old": widget.todo.description,
"newTitle": titleTextContent,
"oldTitle": widget.todo.title,
},
);
},
),
),
body: Padding(
padding: EdgeInsets.all(16.0), child: Column(children: <Widget>[titleEditWidget, descriptionEditWidget],)),
),
onWillPop: () {
Navigator.pop(context, {
'new': descriptionTextContent,
"old": widget.todo.description,
"newTitle": titleTextContent,
"oldTitle": widget.todo.title,
},
);
},
);
}
handleCurrentText(String value,
TextEditingControllerWorkaroud descriptionEditWidgetController) {
setState(() {
descriptionTextContent = value;
print("value is " + value);
});
}
void handleCurrentTitle(String value, TextEditingControllerWorkaroud titleEditWidgetController) {
setState(() {
titleTextContent = value;
});
}
}
The code above is properly functioning code that can be run directly. I have the problem that is TextField has property maxlines. If its null then it auto adjust as the text size grows/shrinks. And if we set it to constant as soon as we increase textContent it acts like scrollable in the widget. But what I want is something called "minLines" that is We start with default no of lines(like if we set max lines to constant) and then we can adjust the size of TextField if text grows(like if we set max lines to null). Also when the text content is beyond the range under the screen it becomes scrollable.
I would have handled that easily if I would be allowed to change maxLines attribute at runtime. I would have simply set a listener on textChange and managed the limits. But its final so I can edit that as well. What can I do?
In recent versions of Flutter, there is actually minLines parameter of TextField and it acts as you described.
First of all the very first mistake that I think was that there was no use of changing state continuously as the user enters the text.My work around was to change state only when I want maxlines to edit
So I set maxlines to a variable defined in the class and that variable I modify as soon as I wanted it (when text exceeds the no of characters)
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class Todo {
String title;
String description;
Todo(this.title, this.description);
}
class TextEditingControllerWorkaroud extends TextEditingController {
TextEditingControllerWorkaroud({String text}) : super(text: text);
void setTextAndPosition(String newText, int caretPosition) {
int offset = caretPosition != null ? caretPosition : newText.length;
value = value.copyWith(
text: newText,
selection: TextSelection.collapsed(offset: offset),
composing: TextRange.empty);
}
}
void main() {
runApp(MaterialApp(
title: 'Passing Data',
home: TodoScreen(
todos: List.generate(
5,
(i) => Todo(
'Todo $i',
'A description of what needs to be done for Todo $i',
),
),
),
));
}
class TodoScreenState extends State<TodoScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Todos'),
),
floatingActionButton: new FloatingActionButton(
onPressed: () async {
setState(() {
print("pressed");
Todo newTodo = Todo("todo", "");
widget.todos.insert(widget.todos.length, newTodo);
});
int index = widget.todos.length - 1;
Map results = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailScreen1(todo: widget.todos[index]),
),
);
if (results["new"] != results["old"] ||
results["newTitle"] != results["oldTitle"]) {
widget.todos[index].description = results["new"];
widget.todos[index].title = results["newTitle"];
final snackBar = SnackBar(
duration: Duration(milliseconds: 2000),
content: Text('Todo Saved Succesfully'),
action: SnackBarAction(
label: 'Undo',
onPressed: () {
setState(() {
widget.todos[index].description = results["old"];
widget.todos[index].title = results["oldTitle"];
});
},
),
);
// Find the Scaffold in the Widget tree and use it to show a SnackBar!
Scaffold.of(context).hideCurrentSnackBar();
Scaffold.of(context).showSnackBar(snackBar);
}
},
child: Icon(Icons.add),
),
body: ListView.builder(
itemCount: widget.todos.length,
itemBuilder: (context, index) {
return Dismissible(
background: Container(color: Colors.green[700]),
key: Key(widget.todos[index].title),
onDismissed: (direction) {
print(direction);
Todo currentTodo = widget.todos[index];
setState(() {
widget.todos.removeAt(index);
});
final snackBar = SnackBar(
duration: Duration(milliseconds: 2000),
content: Text('Todo Deleted Succesfully'),
action: SnackBarAction(
label: 'Undo',
onPressed: () async {
setState(() {
widget.todos.insert(index, currentTodo);
});
},
),
);
// Find the Scaffold in the Widget tree and use it to show a SnackBar!
Scaffold.of(context).hideCurrentSnackBar();
Scaffold.of(context).showSnackBar(snackBar);
},
child: ListTile(
title: Text(widget.todos[index].title),
onTap: () async {
Map results = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
DetailScreen1(todo: widget.todos[index]),
),
);
if (results["new"] != results["old"] ||
results["newTitle"] != results["oldTitle"]) {
widget.todos[index].description = results["new"];
widget.todos[index].title = results["newTitle"];
final snackBar = SnackBar(
duration: Duration(milliseconds: 2000),
content: Text('Todo Saved Succesfully'),
action: SnackBarAction(
label: 'Undo',
onPressed: () {
setState(() {
widget.todos[index].description = results["old"];
widget.todos[index].title = results["oldTitle"];
});
},
),
);
// Find the Scaffold in the Widget tree and use it to show a SnackBar!
Scaffold.of(context).hideCurrentSnackBar();
Scaffold.of(context).showSnackBar(snackBar);
}
},
),
);
},
),
);
}
}
class TodoScreen extends StatefulWidget {
final List<Todo> todos;
TodoScreen({Key key, #required this.todos}) : super(key: key);
#override
TodoScreenState createState() => TodoScreenState();
}
class DetailScreen1 extends StatefulWidget {
final Todo todo;
DetailScreen1({Key key, #required this.todo}) : super(key: key);
#override
DetailScreen1State createState() => DetailScreen1State();
}
class DetailScreen1State extends State<DetailScreen1> {
var descriptionTextContent = "";
var titleTextContent = "";
var size = 3;
var currentSize="fixed";
#override
void initState() {
super.initState();
print("intialized");
descriptionTextContent = widget.todo.description;
titleTextContent = widget.todo.title;
if (descriptionTextContent.length>=100){
size=null;
currentSize="variable";
}
}
#override
Widget build(BuildContext context) {
TextEditingControllerWorkaroud descriptionEditWidgetController =
TextEditingControllerWorkaroud(text: descriptionTextContent);
TextEditingControllerWorkaroud titleEditWidgetController =
TextEditingControllerWorkaroud(text: titleTextContent);
TextField descriptionEditWidget = new TextField(
decoration: new InputDecoration(hintText: 'Description'),
maxLines: size,
keyboardType: TextInputType.multiline,
controller: descriptionEditWidgetController,
onChanged: (value) {
handleCurrentText(value, descriptionEditWidgetController);
},
);
TextField titleEditWidget = new TextField(
decoration: new InputDecoration(hintText: 'Title'),
maxLines: 1,
keyboardType: TextInputType.text,
controller: titleEditWidgetController,
onChanged: (value) {
handleCurrentTitle(value, titleEditWidgetController);
},
);
descriptionEditWidgetController.setTextAndPosition(
descriptionTextContent, descriptionTextContent.length);
titleEditWidgetController.setTextAndPosition(
titleTextContent, titleTextContent.length);
return WillPopScope(
child: Scaffold(
appBar: AppBar(
title: Text(widget.todo.title),
leading: new IconButton(
icon: new Icon(Icons.arrow_back),
onPressed: () {
SystemChannels.textInput.invokeMethod('TextInput.hide');
Navigator.pop(
context,
{
'new': descriptionTextContent,
"old": widget.todo.description,
"newTitle": titleTextContent,
"oldTitle": widget.todo.title,
},
);
},
),
),
body: Padding(
padding: EdgeInsets.all(16.0),
child: Column(
children: <Widget>[titleEditWidget, descriptionEditWidget],
)),
),
onWillPop: () {
Navigator.pop(
context,
{
'new': descriptionTextContent,
"old": widget.todo.description,
"newTitle": titleTextContent,
"oldTitle": widget.todo.title,
},
);
},
);
}
handleCurrentText(String value,
TextEditingControllerWorkaroud descriptionEditWidgetController) {
descriptionTextContent = value;
if (descriptionTextContent.length>100 && currentSize=="fixed"){
setState(() {
print("called");
size = null;
currentSize="variable";
});
}
else if (descriptionTextContent.length<=100&¤tSize=="variable")
{
setState(() {
print("called");
size = 3;
currentSize="fixed";
});
}
}
void handleCurrentTitle(
String value, TextEditingControllerWorkaroud titleEditWidgetController) {
titleTextContent = value;
}
}
The function to note is handleTextChange of descripionTextField
Related
So i am a beginner in flutter and am trying to learn via tutorials, so here I am trying to make todo app using sqflite and everything is perfect and no error is shown in the editor but on clicking floating action button in notelist file it shows this error-
The following _TypeError was thrown building Builder:
type '_Type' is not a subtype of type 'String'
heres my main.dart file
void main() {
runApp(MaterialApp(
home: NoteList(),
));
}
here notelist
class NoteList extends StatefulWidget {
const NoteList({Key? key}) : super(key: key);
#override
_NoteListState createState() => _NoteListState();
}
class _NoteListState extends State<NoteList> {
int count = 0;
DatabaseHelper databaseHelper = DatabaseHelper();
late List<Note> noteList;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Note List'),
),
body: getNoteListView(),
floatingActionButton: FloatingActionButton(
onPressed: () {
debugPrint('fab clicked');
navigateToDetail(Note('', '', 2 ,''),'Add Note');
},
child: Icon(Icons.add),
),
);
}
ListView getNoteListView(){
return ListView.builder(
itemCount: count,
itemBuilder: (context, index){
return Card(
color: Colors.white,
elevation: 2.0,
child: ListTile(
leading: CircleAvatar(
backgroundColor: getPriorityColor(this.noteList[index].priority),
child: getPriorityIcon(this.noteList[index].priority),
),
title: Text(this.noteList[index].title!,),
subtitle: Text(this.noteList[index].date!),
trailing: IconButton(onPressed: (){
_delete(context, noteList[index]);
},
icon: Icon(Icons.delete),
),
onTap: (){
debugPrint('tapped');
navigateToDetail(noteList[index],'Edit Note');
},
),
);
}
);
}
void navigateToDetail(Note note, String title) async{
bool result = await Navigator.push(context, MaterialPageRoute(builder: (context) {
return NoteDetail(appBarTitle: Title, note: note);
}));
if (result == true) {
updateListView();
}
}
// Returns the priority color
Color getPriorityColor(int? priority) {
switch (priority) {
case 1:
return Colors.red;
break;
case 2:
return Colors.yellow;
break;
default:
return Colors.yellow;
}
}
// Returns the priority icon
Icon getPriorityIcon(int? priority) {
switch (priority) {
case 1:
return Icon(Icons.play_arrow);
break;
case 2:
return Icon(Icons.keyboard_arrow_right);
break;
default:
return Icon(Icons.keyboard_arrow_right);
}
}
void _delete(BuildContext context, Note note) async {
int? result = await databaseHelper.deleteNote(note.id);
if (result != 0) {
_showSnackBar(context, 'Note Deleted Successfully');
updateListView();
}
}
void _showSnackBar(BuildContext context, String message) {
final snackBar = SnackBar(content: Text(message));
Scaffold.of(context).showSnackBar(snackBar);
}
void updateListView() {
final Future<Database> dbFuture = databaseHelper.initializeDatabase();
dbFuture.then((database) {
Future<List<Note>> noteListFuture = databaseHelper.getNoteList();
noteListFuture.then((noteList) {
setState(() {
this.noteList = noteList;
this.count = noteList.length;
});
});
});
}
}
and heres notedetail file
class NoteDetail extends StatefulWidget {
final Note note;
final appBarTitle;
NoteDetail( {Key? key,required this.appBarTitle, required this.note}) : super(key: key);
#override
_NoteDetailState createState() => _NoteDetailState(this.note, this.appBarTitle);
}
class _NoteDetailState extends State<NoteDetail> {
static var _priorities = ['High', 'Low'];
DatabaseHelper helper = DatabaseHelper();
TextEditingController titleController = TextEditingController();
TextEditingController descController = TextEditingController();
String appBarTitle;
Note note;
_NoteDetailState(this.note , this.appBarTitle);
#override
Widget build(BuildContext context) {
titleController.text = note.title!;
descController.text = note.description!;
return Scaffold(
appBar: AppBar(
title: Text(appBarTitle),
),
body: Container(
padding: EdgeInsets.all(10),
child: ListView(
children: [
ListTile(
title: DropdownButton(
items: _priorities.map((dropDownStringItem) {
return DropdownMenuItem (
value: dropDownStringItem,
child: Text(dropDownStringItem),
);
}).toList(),
value: getPriorityAsString(note.priority),
onChanged: (valueSelectedByUser) {
setState(() {
debugPrint('User selected $valueSelectedByUser');
updatePriorityAsInt(valueSelectedByUser);
});
}
),
),
SizedBox(height: 10,),
Container(
child: TextField(
controller: titleController,
onChanged: (value) {
debugPrint('something changed in the title textfield ');
updateTitle();
},
decoration: InputDecoration(
labelText: 'Title',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(5.0),
)
),
),
),
SizedBox(height: 10,),
Container(
child: TextField(
controller: descController,
onChanged: (value) {
debugPrint('something changed in the description textfield ');
updateDescription();
},
decoration: InputDecoration(
labelText: 'Description',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(5.0),
)
),
),
),
Container(
padding: EdgeInsets.all(10),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 120,
height: 50,
padding: EdgeInsets.all(5),
child: ElevatedButton(onPressed: (){
debugPrint('add button clicked');
_save();
}, child: Text('Save',
style: TextStyle(
fontSize: 18
),
)
),
),
Container(
width: 120,
height: 50,
padding: EdgeInsets.all(5),
child: ElevatedButton(onPressed: (){
_delete();
debugPrint('Delete button clicked');
}, child: Text('Delete',
style: TextStyle(
fontSize: 18
),)),
),
],
),
)
],
),
),
);
}
// Convert int priority to String priority and display it to user in DropDown
String getPriorityAsString(int? value) {
String priority = '';
switch (value) {
case 1:
priority = _priorities[0]; // 'High'
break;
case 2:
priority = _priorities[1]; // 'Low'
break;
}
return priority;
}
// Convert the String priority in the form of integer before saving it to Database
void updatePriorityAsInt(var value) {
switch (value) {
case 'High':
note.priority = 1;
break;
case 'Low':
note.priority = 2;
break;
}
}
// Update the title of Note object
void updateTitle(){
note.title = titleController.text;
}
// Update the description of Note object
void updateDescription() {
note.description = descController.text;
}
void _delete() async {
moveToLastScreen();
// Case 1: If user is trying to delete the NEW NOTE i.e. he has come to
// the detail page by pressing the FAB of NoteList page.
if (note.id == null) {
_showAlertDialog('Status', 'No Note was deleted');
return;
}
// Case 2: User is trying to delete the old note that already has a valid ID.
int? result = await helper.deleteNote(note.id);
if (result != 0) {
_showAlertDialog('Status', 'Note Deleted Successfully');
} else {
_showAlertDialog('Status', 'Error Occured while Deleting Note');
}
}
void moveToLastScreen() {
Navigator.pop(context, true);
}
void _showAlertDialog(String title, String message) {
AlertDialog alertDialog = AlertDialog(
title: Text(title),
content: Text(message),
);
showDialog(
context: context,
builder: (_) => alertDialog
);
}
// Save data to database
void _save() async {
moveToLastScreen();
note.date = DateFormat.yMMMd().format(DateTime.now());
int? result;
if (note.id != null) { // Case 1: Update operation
result = await helper.updateNote(note);
} else { // Case 2: Insert Operation
result = await helper.insertNote(note);
}
if (result != 0) { // Success
_showAlertDialog('Status', 'Note Saved Successfully');
} else { // Failure
_showAlertDialog('Status', 'Problem Saving Note');
}
}
}
This looks like a spelling mistake.
void navigateToDetail(Note note, String title) async{
...
// change Title into title
return NoteDetail(appBarTitle: title, note: note);
...
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),
),
);
}
}
Here I have two pages first is called BSP_signup_terms page and the second is Bsp_Service_page. when I am on BSP_signup_terms on that page I have to select some checkbox based on the selected checkbox it will show me some data. but problem is that it will show me the complete data but when I get back to the BSP_signup_terms from Bsp_signup_page and I am changing the checkbox and then again when I am pressing next button it will not change the result it same as the previous result.
Here is the Image of Output Page
In this image I've attached both screen output when I am selecting only one checkbox it will render some value in service page and when I am back to the Terms and Condition page and select one more checkbox then it will not updating service page
Here is the code I've tried.
BSP_Signup_Terms_Page
class BspLicensedSignupTermsPage extends StatefulWidget {
static const String routeName = "/bspLicensedSignupTerms";
final BspSignupCommonModel bspSignupCommonModel;
BspLicensedSignupTermsPage({
Key key,
#required this.bspSignupCommonModel,
}) : super(key: key);
#override
_BspLicensedSignupTermsPageState createState() =>
_BspLicensedSignupTermsPageState();
}
class _BspLicensedSignupTermsPageState
extends State<BspLicensedSignupTermsPage> {
#override
void initState() {
super.initState();
}
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
bool _isWalkIn = false;
bool _isHome = false;
bool _isOnDemand = false;
Widget _buildselectcheckbox() {
return Text(
AppConstantsValue.appConst['bsplicensedsignupterms']['selectcheck']
['translation'],
);
}
// Walkin
_onCustomerWalkin(value) {
setState(() {
_isWalkIn = value;
});
}
Widget _buildCustomerWalkIn() {
return TudoConditionWidget(
text: AppConstantsValue.appConst['bsplicensedsignupterms']
['CustomerWalkIn']['translation'],
onChanged: (value) {
print(value);
_onCustomerWalkin(value);
},
validate: false,
);
}
// Home
_onCustomerInHome(value) {
setState(() {
_isHome = value;
});
}
Widget _buildCustomerInHome() {
return TudoConditionWidget(
text: AppConstantsValue.appConst['bsplicensedsignupterms']
['CustomerInHome']['translation'],
onChanged: (value) {
_onCustomerInHome(value);
},
validate: false,
);
}
Widget _buildCustomerInHomeHelp() {
return Text(
AppConstantsValue.appConst['bsplicensedsignupterms']['businesscheckhelp']
['translation'],
);
}
// On Demand
_onCustomerOnDemand(value) {
setState(() {
_isOnDemand = value;
});
}
Widget _buildBusinessOnDemand() {
return TudoConditionWidget(
text: AppConstantsValue.appConst['bsplicensedsignupterms']
['BusinessOnDemand']['translation'],
onChanged: (value) {
_onCustomerOnDemand(value);
},
validate: false,
);
}
Widget _buildBusinessOnDemandHelp() {
return Text(AppConstantsValue.appConst['bsplicensedsignupterms']
['businessprovidehelp']['translation']);
}
#override
Widget build(BuildContext context) {
final appBar = AppBar(
title: Text("Bsp Licensed Signup Terms and Condition"),
leading: IconButton(
icon: Icon(Icons.arrow_back_ios),
onPressed: () {
NavigationHelper.navigatetoBack(context);
},
),
centerTitle: true,
);
final bottomNavigationBar = Container(
height: 56,
//margin: EdgeInsets.symmetric(vertical: 24, horizontal: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
new FlatButton.icon(
icon: Icon(Icons.close),
label: Text('Clear'),
color: Colors.redAccent,
textColor: Colors.black,
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 30),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(7),
),
onPressed: () {
_formKey.currentState.reset();
},
),
new FlatButton.icon(
icon: Icon(FontAwesomeIcons.arrowCircleRight),
label: Text('Next'),
color: colorStyles["primary"],
textColor: Colors.white,
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 30),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(7),
),
onPressed: () {
if (_formKey.currentState.validate()) {
if (_isHome == false &&
_isOnDemand == false &&
_isWalkIn == false) {
showDialog(
barrierDismissible: false,
context: context,
builder: (context) => ShowErrorDialog(
title: Text('Select Service'),
content: Text(
'Please select atleast one service type to proceed next',
),
));
} else {
BspSignupCommonModel model = widget.bspSignupCommonModel;
model.isWalkin = _isWalkIn;
model.isHome = _isHome;
model.isOnDemand = _isOnDemand;
print(model.toJson());
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
BspServicePage(bspSignupCommonModel: model),
),
);
}
}
},
),
],
),
);
return new Scaffold(
appBar: appBar,
bottomNavigationBar: bottomNavigationBar,
body: Container(
height: double.infinity,
width: double.infinity,
child: Stack(
children: <Widget>[
SingleChildScrollView(
child: SafeArea(
child: Form(
autovalidate: true,
key: _formKey,
child: Scrollbar(
child: SingleChildScrollView(
dragStartBehavior: DragStartBehavior.down,
padding: const EdgeInsets.symmetric(horizontal: 10.0),
child: new Container(
decoration: BoxDecoration(
borderRadius: new BorderRadius.circular(25)),
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
_buildselectcheckbox(),
_buildCustomerWalkIn(),
_buildCustomerInHome(),
_buildCustomerInHomeHelp(),
_buildBusinessOnDemand(),
_buildBusinessOnDemandHelp(),
],
),
),
),
),
),
),
),
],
),
),
);
}
}
BSP_Service_Page
class BspServicePage extends StatefulWidget {
static const String routeName = "/bspService";
final BspSignupCommonModel bspSignupCommonModel;
BspServicePage({
Key key,
#required this.bspSignupCommonModel,
}) : super(key: key);
#override
_BspServicePageState createState() => _BspServicePageState();
}
class _BspServicePageState extends State<BspServicePage> {
List<int> servicesIds = [];
Map<String, bool> selection = {};
List<BspServices.Service> selectedServices = [];
SearchBarController _controller = new SearchBarController();
String _searchText = '';
bool refreshservices = true;
#override
void initState() {
super.initState();
}
void _showErrorDialog(String message) {
showDialog(
barrierDismissible: false,
context: context,
builder: (context) => ShowErrorDialog(
title: Text('An Error Occurred!'),
content: Text(message),
),
);
}
void refresh() {
setState(() {
refreshservices = !refreshservices;
});
}
#override
Widget build(BuildContext context) {
var _bspServiceBloc = new BspServiceBloc();
final appBar = SearchBar(
controller: _controller,
onQueryChanged: (String query) {
print('Search Query $query');
setState(() {
_searchText = query;
});
},
defaultBar: AppBar(
centerTitle: true,
leading: IconButton(
icon: Icon(Icons.arrow_back_ios),
onPressed: () {
refresh();
NavigationHelper.navigatetoBack(context);
}),
title: Text('Select Services'),
),
);
final bottomNavigationBar = Container(
height: 56,
// margin: EdgeInsets.symmetric(vertical: 24, horizontal: 12),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
new FlatButton.icon(
icon: Icon(Icons.close),
label: Text('Clear'),
color: Colors.redAccent,
textColor: Colors.black,
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 30),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(7),
),
onPressed: () {
print('reseting the state');
setState(() {
selection = {};
servicesIds = [];
});
},
),
new FlatButton.icon(
icon: Icon(FontAwesomeIcons.arrowCircleRight),
label: Text('Next'),
color: colorStyles["primary"],
textColor: Colors.white,
padding: EdgeInsets.symmetric(vertical: 10, horizontal: 30),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(7),
),
onPressed: () {
BspSignupCommonModel model = widget.bspSignupCommonModel;
model.servicesIds = servicesIds;
model.services = selectedServices;
print('servicesIds at the next button');
print(servicesIds);
print(model.toJson());
if (servicesIds.length == 0) {
_showErrorDialog(
'You need to select at least one service to proceed next!');
} else {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => BusinessProfilePage(
bspSignupCommonModel: model,
),
),
);
}
},
),
],
),
);
return new Scaffold(
appBar: appBar,
bottomNavigationBar: bottomNavigationBar,
body: new BspServiceScreen(
bspServiceBloc: _bspServiceBloc,
bspSignupCommonModel: widget.bspSignupCommonModel,
servicesIds: servicesIds,
selection: selection,
searchQuery: _searchText,
selectedServices: selectedServices,
refresh: refresh,
),
);
}
}
Bsp_service_screen
class BspServiceScreen extends StatefulWidget {
final BspServiceBloc _bspServiceBloc;
final String searchQuery;
final List<int> servicesIds;
final Map<String, bool> selection;
final BspSignupCommonModel bspSignupCommonModel;
final List<BspServices.Service> selectedServices;
final Function refresh;
const BspServiceScreen({
Key key,
#required BspServiceBloc bspServiceBloc,
#required this.bspSignupCommonModel,
#required this.servicesIds,
#required this.selection,
#required this.selectedServices,
#required this.refresh,
this.searchQuery,
}) : _bspServiceBloc = bspServiceBloc,
super(key: key);
#override
BspServiceScreenState createState() {
return new BspServiceScreenState(_bspServiceBloc);
}
}
class BspServiceScreenState extends State<BspServiceScreen> {
final BspServiceBloc _bspServiceBloc;
BspServiceScreenState(this._bspServiceBloc);
// Map<String, bool> _selection = {};
#override
void initState() {
super.initState();
bool isHome = widget.bspSignupCommonModel.isHome;
bool isWalkIn = widget.bspSignupCommonModel.isWalkin;
bool isOnDemand = widget.bspSignupCommonModel.isOnDemand;
this._bspServiceBloc.dispatch(LoadBspServiceEvent(
countryId: 1,
isHome: isHome,
isOnDemand: isOnDemand,
isWalkin: isWalkIn,
));
}
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
return BlocBuilder<BspServiceBloc, BspServiceState>(
bloc: widget._bspServiceBloc,
builder: (
BuildContext context,
BspServiceState currentState,
) {
if (currentState is UnBspServiceState) {
return Center(child: CircularProgressIndicator());
}
if (currentState is ErrorBspServiceState) {
return new Container(
child: new Center(
child: new Text(currentState.errorMessage ?? 'Error'),
),
);
}
if (currentState is InBspServiceState) {
// print(
// 'in bsp service state, ${currentState.bspServices.servicesByCountry.length}');
if (currentState.bspServices.servicesByCountry.length == 0) {
return Container(
child: Center(
child: Text("No Services available for this combination"),
),
);
} else {
return new Container(
child:
_renderServices(currentState.bspServices.servicesByCountry),
);
}
}
return Container();
},
);
}
List<ServicesByCountry> finalList = new List();
ListView _renderServices(List<ServicesByCountry> lovCountryServices) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (widget.searchQuery != '') {
finalList.clear();
lovCountryServices.forEach((ServicesByCountry data) {
if (data.name
.toLowerCase()
.contains(widget.searchQuery.toLowerCase())) {
setState(() {
finalList.add(data);
});
} else {
data.services.forEach((ServiceList.Service services) {
if (services.name
.toLowerCase()
.contains(widget.searchQuery.toLowerCase())) {
setState(() {
finalList.add(data);
});
}
});
}
});
} else {
setState(() {
finalList.clear();
finalList.addAll(lovCountryServices);
});
}
});
return ListView.builder(
shrinkWrap: true,
padding: const EdgeInsets.all(8.0),
itemCount: finalList.length,
itemBuilder: (BuildContext context, int index) {
ServicesByCountry item = finalList[index];
List itemsList = item.services;
return ExpansionTile(
title: Text(item.name),
children: List.generate(itemsList.length, (i) {
widget.selection[itemsList[i].name] =
widget.selection[itemsList[i].name] ?? itemsList[i].isSelected;
return CheckboxListTile(
title: Text(itemsList[i].name),
value: widget.selection[itemsList[i].name],
onChanged: (val) {
setState(() {
widget.selection[itemsList[i].name] = val;
if (val) {
widget.servicesIds.add(itemsList[i].id);
List<BspServices.Service> services =
widget.selectedServices.where((service) {
return service.mainCategory == item.name;
}).toList();
SubCategory subService = new SubCategory(
id: itemsList[i].id,
name: itemsList[i].name,
);
List<SubCategory> subCategories = [];
if (services.length == 0) {
subCategories.add(subService);
widget.selectedServices.add(
new BspServices.Service(
mainCategory: item.name,
mainCategoryId: item.id,
subCategory: subCategories,
),
);
} else {
print('services in else');
print(services[0].subCategory);
subCategories = services[0].subCategory;
subCategories.add(subService);
}
} else {
widget.servicesIds.removeWhere((service) {
return service == itemsList[i].id;
});
List<BspServices.Service> services =
widget.selectedServices.where((service) {
return service.mainCategory == item.name;
}).toList();
services[0].subCategory.removeWhere((subService) {
return subService.id == itemsList[i].id;
});
}
});
print('widget.servicesIds after set state');
print(widget.servicesIds);
},
);
}),
);
},
);
}
}
You can use setState() after return to the first page:
Navigator.push(context, MaterialPageRoute(builder: (context) => Page2())).then((value) {
setState(() {
// refresh state
});
});
Please try below code:-
First you add one method async method:-
void redirectToNextScreen() async {
final Route route = MaterialPageRoute(
builder: (context) => BspServicePage(bspSignupCommonModel: model));
final result = await Navigator.push(mContext, route);
try {
if (result != null) {
if (result) {
//Return callback here.
}
}
} catch (e) {
print(e.toString());
}
}
Then Next you can call this method in "BSP_Signup_Terms_Page" on Next button Pressed event.
Second you can add below line in "BspServicePage" screen Next and Cancel Events.
Navigator.pop(mContext, true); //true means refresh back page and false means not refresh.
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();
}
}
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.
},
),
);
}