Related
I have an empty page lets call it "class list" which the user can create a new "class" in the listview by clicking a button and giving it a name and when you click on that "class" it opens a new page with that name on the app bar, lets call this new page the "students lists", now you can create another list in it, and I used hive to store the data.
now the problem is when you create a list of students in the "student list" page when you come back and go to "class lists" and click on another "class", the student list you created earlier will appear for every "class" page.
the reason for this is because I have created a class(the syntax) for student list page and it will show for every "class".
since its the user who creates "classes" and I haven't putted any limitation on it, I cant create infinite classes(the syntax) for the pages created so what can I do?
class list page
inside class 1
inside class 2
here is the codes:
the "list class" codes:
import 'package:attendance/data/database.dart';
import 'package:flutter/material.dart';
import 'package:flutter_speed_dial/flutter_speed_dial.dart';
import 'package:attendance/insideList.dart';
import 'package:hive_flutter/hive_flutter.dart';
class lists extends StatefulWidget {
const lists({super.key});
#override
State<lists> createState() => _listsState();
}
class _listsState extends State<lists> {
final _myBox = Hive.box('mybox');
ListDataBase db = ListDataBase();
late TextEditingController _textController;
#override
void initState() {
if (_myBox.get("NAMES") == null) {
db.InitialData();
} else {
db.LoadData();
}
super.initState();
_textController = TextEditingController();
}
#override
void dispose() {
_textController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
db.Items.sort();
return Scaffold(
body: db.Items.length > 0
? ListView.separated(
itemCount: db.Items.length,
itemBuilder: (_, index) {
return ListTile(
leading: const Icon(Icons.school),
trailing: const Icon(Icons.arrow_forward),
title: Center(child: Text(db.Items[index])),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: ((context) =>
InsideList(db.Items[index]))));
},
onLongPress: (() async {
await showDialog(
context: context,
builder: ((context) {
return AlertDialog(
title: const Text(
"Are you sure you want to delete this class?",
style: TextStyle(fontSize: 15),
),
actions: [
TextButton(
child: Text("cancel"),
onPressed: (() {
Navigator.pop(context);
})),
TextButton(
child: Text('Delete'),
onPressed: () {
setState(() {
db.Items.removeAt(index);
db.UpdateDataBase();
Navigator.pop(context);
});
},
),
],
);
}));
}),
);
},
separatorBuilder: (BuildContext context, int index) =>
const Divider(
color: Colors.black,
),
)
: const Center(
child: Text("You currently have no classes. Add from below."),
),
floatingActionButton: SpeedDial(
animatedIcon: AnimatedIcons.menu_arrow,
spacing: 6,
spaceBetweenChildren: 6,
backgroundColor: const Color.fromARGB(255, 22, 37, 50),
foregroundColor: const Color.fromARGB(255, 255, 255, 255),
children: [
SpeedDialChild(
child: const Icon(Icons.school),
label: "add class",
onTap: () async {
final result = await showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('Add a new class'),
content: TextField(
controller: _textController,
autofocus: true,
decoration: const InputDecoration(
hintText: "Enter the name of the class."),
),
actions: [
TextButton(
child: Text('Cancel'),
onPressed: () {
Navigator.pop(context);
},
),
TextButton(
child: Text('Add'),
onPressed: () {
Navigator.pop(context, _textController.text);
db.UpdateDataBase();
_textController.clear();
},
),
],
);
},
);
if (result != null) {
result as String;
setState(() {
db.Items.add(result);
});
}
},
)
],
),
);
}
}
the student page codes:
import 'package:attendance/data/StudentsDatabase.dart';
import 'package:flutter/material.dart';
import 'package:flutter_speed_dial/flutter_speed_dial.dart';
import 'package:hive_flutter/hive_flutter.dart';
class InsideList extends StatefulWidget {
final String name;
InsideList(this.name);
#override
State<InsideList> createState() => _InsideListState();
}
class _InsideListState extends State<InsideList> {
final _myBox = Hive.box('mybox2');
StudentsDatabase db = StudentsDatabase();
late TextEditingController _textController;
#override
void initState() {
if (_myBox.get("NAMES") == null) {
db.InitialData();
} else {
db.LoadData();
}
super.initState();
_textController = TextEditingController();
}
void _selectRadio(int index, int? val) {
setState(() {
db.SelectedRadio[index] = val ?? 0;
});
}
#override
void dispose() {
_textController.dispose();
super.dispose();
}
Widget build(BuildContext context) {
db.Students.sort();
return Scaffold(
appBar: AppBar(
title: Text(widget.name),
centerTitle: true,
backgroundColor: const Color.fromARGB(255, 22, 37, 50),
toolbarHeight: 65,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
bottom: Radius.circular(30),
),
),
),
body: db.Students.length > 0
? ListView.separated(
itemCount: db.Students.length,
itemBuilder: (_, index) {
return ListTile(
leading: const Icon(Icons.person),
trailing: FittedBox(
fit: BoxFit.fill,
child: Row(
children: [
Radio(
activeColor: Colors.green,
value: 0,
groupValue: db.SelectedRadio[index],
onChanged: (val) {
_selectRadio(index, val);
db.UpdateDataBase();
}),
Radio(
activeColor: Colors.red,
value: 1,
groupValue: db.SelectedRadio[index],
onChanged: (val) {
_selectRadio(index, val);
db.UpdateDataBase();
}),
],
),
),
title: Center(child: Text(db.Students[index])),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: ((context) =>
InsideList(db.Students[index]))));
},
onLongPress: (() async {
await showDialog(
context: context,
builder: ((context) {
return AlertDialog(
title: const Text(
"Are you sure you want to delete this student?",
style: TextStyle(fontSize: 15),
),
actions: [
TextButton(
child: Text("cancel"),
onPressed: (() {
Navigator.pop(context);
})),
TextButton(
child: Text('Delete'),
onPressed: () {
setState(() {
db.Students.removeAt(index);
db.UpdateDataBase();
Navigator.pop(context);
});
},
),
],
);
}));
}),
);
},
separatorBuilder: (BuildContext context, int index) =>
const Divider(
color: Colors.black,
),
)
: const Center(
child: Text("You currently have no students. Add from below."),
),
floatingActionButton: SpeedDial(
animatedIcon: AnimatedIcons.menu_arrow,
spacing: 6,
spaceBetweenChildren: 6,
backgroundColor: const Color.fromARGB(255, 22, 37, 50),
foregroundColor: const Color.fromARGB(255, 255, 255, 255),
children: [
SpeedDialChild(
child: const Icon(Icons.group_add),
label: "add student",
onTap: () async {
final result = await showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('Add a new student'),
content: TextField(
controller: _textController,
autofocus: true,
decoration: const InputDecoration(
hintText: "Enter the name of the student."),
),
actions: [
TextButton(
child: Text('Cancel'),
onPressed: () {
Navigator.pop(context);
},
),
TextButton(
child: Text('Add'),
onPressed: () {
Navigator.pop(context, _textController.text);
db.UpdateDataBase();
_textController.clear();
},
),
],
);
},
);
if (result != null) {
result as String;
setState(() {
db.Students.add(result);
db.SelectedRadio.add(0);
});
}
},
),
],
),
);
}
}
From what i am understanding, You will need to connect the student list with every single class separately. To show a quick example.
List<ClassRoom> firstPageList = [];
class ClassRoom {
ClassRoom({required this.name});
final String name;
List<Student> students = [];
}
class Student {
Student({required this.studentName});
final String studentName;
}
Kinda like this. Then when a new ClassRoom class added to the list, Show the name on the list. And pass that ClassRoom to the next page.
And on the next page, Use that ClassRoom instance, and add all the students inside the list of that ClassRoom.
Now for every page you will have a different class and for every class, you will have it's seperate Student.
After that, all you need to do is, handle the states of every class properly. And setup a way to store them properly.
I have a page that the user can add students to the list by entering their name in the listtile in the listview, i wanted to have 2 specific radio buttons for each name one green one red for their presence or absence. I have created my version of it already but when you click on radio button it changes all in that column. is there any other way that this can be done?
1
2
my code:
import 'package:flutter/material.dart';
import 'package:flutter_speed_dial/flutter_speed_dial.dart';
class InsideList extends StatefulWidget {
final String name;
InsideList(this.name);
#override
State<InsideList> createState() => _InsideListState();
}
class _InsideListState extends State<InsideList> {
List<String> _students = [];
late int selectedRadio;
late TextEditingController _textController;
#override
void initState() {
super.initState();
_textController = TextEditingController();
selectedRadio = 0;
}
SetselectedRadio(int? val) {
setState(() {
selectedRadio = val!;
});
}
#override
void dispose() {
_textController.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.name),
centerTitle: true,
backgroundColor: const Color.fromARGB(255, 22, 37, 50),
toolbarHeight: 65,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
bottom: Radius.circular(30),
),
),
),
body: _students.length > 0
? ListView.separated(
itemCount: _students.length,
itemBuilder: (_, index) {
return ListTile(
leading: const Icon(Icons.person),
trailing: FittedBox(
fit: BoxFit.fill,
child: Row(
children: [
Radio(
activeColor: Colors.green,
value: 0,
groupValue: selectedRadio,
onChanged: (val) {
SetselectedRadio(val);
}),
Radio(
activeColor: Colors.red,
value: 1,
groupValue: selectedRadio,
onChanged: (val) {
SetselectedRadio(val);
},
)
],
),
),
title: Center(child: Text(_students[index])),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: ((context) =>
InsideList(_students[index]))));
},
onLongPress: (() async {
await showDialog(
context: context,
builder: ((context) {
return AlertDialog(
title: const Text(
"Are you sure you want to delete this student?",
style: TextStyle(fontSize: 15),
),
actions: [
TextButton(
child: Text("cancel"),
onPressed: (() {
Navigator.pop(context);
})),
TextButton(
child: Text('Delete'),
onPressed: () {
setState(() {
_students.removeAt(index);
Navigator.pop(context);
});
},
),
],
);
}));
}),
);
},
separatorBuilder: (BuildContext context, int index) =>
const Divider(
color: Colors.black,
),
)
: const Center(
child: Text("You currently have no students. Add from below."),
),
floatingActionButton: SpeedDial(
animatedIcon: AnimatedIcons.menu_arrow,
spacing: 6,
spaceBetweenChildren: 6,
backgroundColor: const Color.fromARGB(255, 22, 37, 50),
foregroundColor: const Color.fromARGB(255, 255, 255, 255),
children: [
SpeedDialChild(
child: const Icon(Icons.group_add),
label: "add student",
onTap: () async {
final result = await showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('Add a new student'),
content: TextField(
controller: _textController,
autofocus: true,
decoration: const InputDecoration(
hintText: "Enter the name of the student."),
),
actions: [
TextButton(
child: Text('Cancel'),
onPressed: () {
Navigator.pop(context);
},
),
TextButton(
child: Text('Add'),
onPressed: () {
Navigator.pop(context, _textController.text);
_textController.clear();
},
),
],
);
},
);
if (result != null) {
result as String;
setState(() {
_students.add(result);
});
}
},
),
],
),
);
}
}
It's because basically you are assigning same values for each Radio Button Group. There is a better way but I just have modified your code a bit to show you how to do it.
First, you assign a list for radio values along with students.
List<String> _students = [];
List<int> _selectedRadio = [];
And for assigning a value to a radio button, you need index of the radio button as well.
void _selectRadio(int index, int? val) {
setState(() {
_selectedRadio[index] = val ?? 0;
});
}
Then for Radio Buttons, assign a group value with index.
Radio(
activeColor: Colors.green,
value: 0,
groupValue: _selectedRadio[index],
onChanged: (val) {
_selectRadio(index, val);
},
),
Radio(
activeColor: Colors.red,
value: 1,
groupValue: _selectedRadio[index],
onChanged: (val) {
_selectRadio(index, val);
},
)
Then finally, when you create a student, you add a radio button value to the list of radio button value.
if (result != null) {
result as String;
setState(() {
_students.add(result);
_selectedRadio.add(0);
});
}
And below is the full working code. Hope this helps.
class InsideList extends StatefulWidget {
final String name;
InsideList(this.name);
#override
State<InsideList> createState() => _InsideListState();
}
class _InsideListState extends State<InsideList> {
List<String> _students = [];
List<int> _selectedRadio = [];
late TextEditingController _textController;
#override
void initState() {
super.initState();
_textController = TextEditingController();
}
void _selectRadio(int index, int? val) {
setState(() {
_selectedRadio[index] = val ?? 0;
});
}
#override
void dispose() {
_textController.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.name),
centerTitle: true,
backgroundColor: const Color.fromARGB(255, 22, 37, 50),
toolbarHeight: 65,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
bottom: Radius.circular(30),
),
),
),
body: _students.length > 0
? ListView.separated(
itemCount: _students.length,
itemBuilder: (_, index) {
return ListTile(
leading: const Icon(Icons.person),
trailing: FittedBox(
fit: BoxFit.fill,
child: Row(
children: [
Radio(
activeColor: Colors.green,
value: 0,
groupValue: _selectedRadio[index],
onChanged: (val) {
_selectRadio(index, val);
}),
Radio(
activeColor: Colors.red,
value: 1,
groupValue: _selectedRadio[index],
onChanged: (val) {
_selectRadio(index, val);
},
)
],
),
),
title: Center(child: Text(_students[index])),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: ((context) =>
InsideList(_students[index]))));
},
onLongPress: (() async {
await showDialog(
context: context,
builder: ((context) {
return AlertDialog(
title: const Text(
"Are you sure you want to delete this student?",
style: TextStyle(fontSize: 15),
),
actions: [
TextButton(
child: Text("cancel"),
onPressed: (() {
Navigator.pop(context);
})),
TextButton(
child: Text('Delete'),
onPressed: () {
setState(() {
_students.removeAt(index);
_selectedRadio.removeAt(index);
Navigator.pop(context);
});
},
),
],
);
}));
}),
);
},
separatorBuilder: (BuildContext context, int index) =>
const Divider(
color: Colors.black,
),
)
: const Center(
child: Text("You currently have no students. Add from below."),
),
floatingActionButton: SpeedDial(
animatedIcon: AnimatedIcons.menu_arrow,
spacing: 6,
spaceBetweenChildren: 6,
backgroundColor: const Color.fromARGB(255, 22, 37, 50),
foregroundColor: const Color.fromARGB(255, 255, 255, 255),
children: [
SpeedDialChild(
child: const Icon(Icons.group_add),
label: "add student",
onTap: () async {
final result = await showDialog(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('Add a new student'),
content: TextField(
controller: _textController,
autofocus: true,
decoration: const InputDecoration(
hintText: "Enter the name of the student."),
),
actions: [
TextButton(
child: Text('Cancel'),
onPressed: () {
Navigator.pop(context);
},
),
TextButton(
child: Text('Add'),
onPressed: () {
Navigator.pop(context, _textController.text);
_textController.clear();
},
),
],
);
},
);
if (result != null) {
result as String;
setState(() {
_students.add(result);
_selectedRadio.add(0);
});
}
},
),
],
),
);
}
}
You have to create List < int > SelectedRadio , which will always has your students list length. Next in method SetSelectedRadio you have to change value in SelectedRadio[student_index]
You have done it wrong you have given the radioButtons a single variable which all the radioButtons are referring to this cause them to share the same value and change accordingly(meaning all the radioButtons with corresponding values will change).
You can use various methods to pass this FOR EXAMPLE :
You can generate a secondary list that will hold all the bool values for each and every list item you can use list.generate() to generate the list depending on the length of the _student list.
You can create a model class where you save both name and the int value for the radio buttons (Most preferred as it gives more flexibility for future changes) I have mentioned the same below
Full code
// Here I have created the model class to create a list.
// do not make the arguments final as they will not change as we need them to change.
class student {
String nameOfStudent;
int isPresent;
student({
required this.nameOfStudent,
required this.isPresent,
});
}
class InsideList extends StatefulWidget {
final String name;
InsideList(this.name);
#override
State<InsideList> createState() => _InsideListState();
}
class _InsideListState extends State<InsideList> {
// As this list is not final one can change the values dynamically.
// You can add the items using _students.add(student(
// nameOfStudent: "Name",
// isPresent: 0,
// ));
List<student> _students = [];
late TextEditingController _textController;
#override
void initState() {
super.initState();
_textController = TextEditingController();
}
#override
void dispose() {
_textController.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.name),
centerTitle: true,
backgroundColor: const Color.fromARGB(255, 22, 37, 50),
toolbarHeight: 65,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(
bottom: Radius.circular(30),
),
),
),
body: _students.length > 0
? ListView.separated(
itemCount: _students.length,
itemBuilder: (_, index) {
return ListTile(
leading: const Icon(Icons.person),
trailing: FittedBox(
fit: BoxFit.fill,
child: Row(
children: [
Radio(
activeColor: Colors.green,
value: 0,
groupValue: _students[index].isPresent,
onChanged: (val) {
setState(() {
_students[index].isPresent = val!;
});
}),
Radio(
activeColor: Colors.red,
value: 1,
// this will go to the list with the idex and fetch the value
groupValue: _students[index].isPresent,
onChanged: (val) {
// this will assign a new value to the item with the corresponding index
// this will give each and every item its own radioButton variable resulting in proper value change for each item in the list.
setState(() {
_students[index].isPresent = val!;
});
},
)
],
),
),
title: Center(child: Text(_students[index].nameOfStudent)),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: ((context) =>
InsideList(_students[index]))));
},
onLongPress: (() async {
await showDialog(
context: context,
builder: ((context) {
return AlertDialog(
title: const Text(
"Are you sure you want to delete this student?",
style: TextStyle(fontSize: 15),
),
actions: [
TextButton(
child: Text("cancel"),
onPressed: (() {
Navigator.pop(context);
})),
TextButton(
child: Text('Delete'),
onPressed: () {
setState(() {
_students.removeAt(index);
Navigator.pop(context);
});
},
),
],
);
}));
}),
);
},
separatorBuilder: (BuildContext context, int index) =>
const Divider(
color: Colors.black,
),
)
: const Center(
child: Text("You currently have no students. Add from below."),
),
);
}
}
As I have mentioned there are many more ways to do the same (using Map as well) Hope this is help full and keep in mind about making variables final as it will not change will the application is running.
I was coding my flutter project when I fall under this error. I do not understand because I have tried all the available solution online and still it is not working. The craziest thing is I have the exact same code on the other page and it is working perfectly.
And If I restart the app it works. The first problem was a Material app which is the main function and even if I wrap the column with a scaffold it still doesn't work
Below is the error
The following assertion was thrown during performLayout():
RenderCustomMultiChildLayoutBox object was given an infinite size during layout.
This probably means that it is a render object that tries to be as big as possible, but it was put inside another render object that allows its children to pick their own size.
The nearest ancestor providing an unbounded height constraint is: _RenderSingleChildViewport#20590 relayoutBoundary=up10 NEEDS-LAYOUT NEEDS-PAINT NEEDS-COMPOSITING-BITS-UPDATE
... needs compositing
... parentData: <none> (can use size)
... constraints: BoxConstraints(w=358.0, 0.0<=h<=731.0)
... layer: OffsetLayer#17148
... engine layer: OffsetEngineLayer#48e1c
... handles: 2
... offset: Offset(16.0, 63.0)
... size: Size(358.0, 731.0)
The constraints that applied to the RenderCustomMultiChildLayoutBox were: BoxConstraints(0.0<=w<=358.0, 0.0<=h<=Infinity)
The exact size it was given was: Size(358.0, Infinity)
Below this I have form that receives data from another one
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
import 'package:loading_overlay/loading_overlay.dart';
import 'package:tembea/components/rounded_button.dart';
import 'package:tembea/components/show_toast.dart';
import 'package:tembea/components/time_pick.dart';
import 'package:tembea/screens/admin/admin_screens/restaurant/dashboard_restaurant.dart';
import '../../../../../constants.dart';
import 'package:carousel_slider/carousel_slider.dart';
import 'package:tembea/components/square_button.dart';
import 'dart:io' ;
import 'package:firebase_storage/firebase_storage.dart';
import 'add_images.dart';
class RestaurantSecondaryForm extends StatefulWidget {
const RestaurantSecondaryForm({
Key? key,
required this.name,
required this.location,
required this.price,
required this.description,
}) : super(key: key);
final String name;
final String location;
final String price;
final String description;
#override
_RestaurantSecondaryFormState createState() => _RestaurantSecondaryFormState();
}
class _RestaurantSecondaryFormState extends State<RestaurantSecondaryForm> {
bool showSpinner = false;
String selectedOpeningTime = '8:00 AM';
String selectedClosingTime = '17:00 PM';
String ? selectedTime;
final List<String> defaultImageList = [
'assets/images/image.png',
'assets/images/image.png',
'assets/images/image.png',
];
List<String> imageList =[];
List<File> files = [];
List<String> ? imageUrl;
TaskSnapshot? snap;
String ? photoUrl;
String ? photoUrl2;
String ? photoUrl3;
String selectedType = 'Restaurant';
#override
Widget build(BuildContext context) {
Future<void> openTimePicker(context) async {
final TimeOfDay? t =
await showTimePicker(
context: context, initialTime: TimeOfDay.now());
if(t != null){
setState(() {
selectedTime = t.format(context);
});
}
}
goToAddImage() async{
List<String> imgUrl = await Navigator.push(context, MaterialPageRoute(builder:(context){
return const AddImages();
}));
setState(() {
imageUrl = imgUrl;
});
}
if(imageUrl?.isNotEmpty == true){
setState(() {
photoUrl = imageUrl?[0];
photoUrl2 = imageUrl?[1];
photoUrl3 = imageUrl?[2];
imageList.add(photoUrl!);
imageList.add(photoUrl2!);
imageList.add(photoUrl3!);
});
}
else{
setState(() {
photoUrl = '';
photoUrl2 = '';
photoUrl3 = '';
});
}
return LoadingOverlay(
isLoading: showSpinner,
opacity: 0.5,
color: Colors.green,
progressIndicator: const CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation(Colors.green),
),
child: Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
title: const Text('Add Restaurant'),
backgroundColor: kBackgroundColor.withOpacity(0.3),
),
body: Padding(
padding: const EdgeInsets.all(40.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
CarouselSlider(
options: CarouselOptions(
enlargeCenterPage: true,
enableInfiniteScroll: false,
autoPlay: false,
),
items:imageList.isEmpty ? defaultImageList.map((e) => ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Stack(
fit: StackFit.expand,
children: [
Image.asset(
e,
width: 1000,
height: 300,
fit: BoxFit.fill,
)
],
),
)).toList()
: imageList.map((e) => ClipRRect(
borderRadius: BorderRadius.circular(8),
child: Stack(
fit: StackFit.expand,
children: [
Image.network(
e,
width: 1000,
height: 300,
fit: BoxFit.fill,
)
],
),
)).toList(),
),
const SizedBox(
height: 20,
),
Column(
children: [
DateButton(
text: 'Upload Images',
onPressed: () {
goToAddImage();
},
),
],
),
Column(
children: [
TimePick(
label: 'Opens at',
labelColor: Colors.green,
time: selectedOpeningTime,
onPressed: (){
openTimePicker(context).then((value) {
setState(() {
selectedOpeningTime = selectedTime!;
});
});
},
),
const SizedBox(
height: 20,
),
TimePick(
label: 'Closes at',
labelColor: Colors.red,
time: selectedClosingTime,
onPressed: (){
openTimePicker(context).then((value) {
setState(() {
selectedClosingTime = selectedTime!;
});
});
}
),
],
),
RoundedButton(
buttonName: 'Add Restaurant',
color: Colors.green,
onPressed: () async{
setState(() {
showSpinner = true;
});
final time = DateTime.now();
await FirebaseFirestore.instance.collection('activities').doc().set({
'Name' : widget.name,
'Location': widget.location,
'Description' : widget.description,
'Price' : widget.price,
'OpeningTime' : selectedOpeningTime,
'ClosingTime' : selectedClosingTime,
'PhotoUrl' : photoUrl,
'PhotoUrl2' : photoUrl2,
'PhotoUrl3' : photoUrl3,
'Type' : selectedType,
'ts' : time,
}).then((value) {
setState(() {
showSpinner = false;
});
showToast(message: 'Restaurant Added',
color: Colors.green
);
Navigator.pushNamedAndRemoveUntil(context, DashboardRestaurant.id, (r) => false);
});
}
),
],
),
),
),
);
}
}
which is supposed to navigate back to this view that brings the error
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:tembea/components/add_button.dart';
import 'package:tembea/components/responsive.dart';
import 'package:tembea/components/show_dialog.dart';
import 'package:tembea/components/show_toast.dart';
import 'package:tembea/screens/admin/admin_screens/view_data.dart';
import 'event_form.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/foundation.dart';
class DashboardEvent extends StatefulWidget {
const DashboardEvent({Key? key}) : super(key: key);
static String id = 'dashboard_event';
#override
_DashboardEventState createState() => _DashboardEventState();
}
class _DashboardEventState extends State<DashboardEvent> {
final Stream<QuerySnapshot> activities = FirebaseFirestore
.instance.collection('activities')
.where('Type', isEqualTo: 'Event')
.orderBy('ts', descending: true)
.snapshots();
#override
Widget build(BuildContext context) {
return Column(
children: [
AddButton(
title: 'Events',
addLabel: 'Add Events',
onPressed: (){
Navigator.pushNamed(context, EventForm.id);
},),
const SizedBox(
height: 16.0,
),
const Divider(
color: Colors.white70,
height:40.0,
),
StreamBuilder<QuerySnapshot>(
stream: activities,
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot){
if(snapshot.connectionState == ConnectionState.waiting){
return const Center(
child: CircularProgressIndicator(
valueColor: AlwaysStoppedAnimation(Colors.green),
),
);
}
final data = snapshot.requireData;
if(data != null){
return ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount: data.size,
itemBuilder: (context, index){
return Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
CircleAvatar(
backgroundColor: Colors.transparent,
radius: 40,
child:Image.network(
data.docs[index]['PhotoUrl'],
),
),
Text( data.docs[index]['Name'], style:const TextStyle(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.bold),
),
if(Responsive.isWeb(context))
const SizedBox(
width: 50,
),
Responsive.isWeb(context) ? ButtonBlue(
addLabel: 'View Event',
color: Colors.green,
onPressed: (){
Navigator.push(context, MaterialPageRoute(builder: (context){
return ViewData(item: data.docs[index],);
}));
},
icon: const Icon(IconData(61161, fontFamily: 'MaterialIcons')),
) : InkWell(
onTap: (){
Navigator.push(context, MaterialPageRoute(builder: (context){
return ViewData(item: data.docs[index],);
}));
},
child: const Icon(IconData(61161, fontFamily: 'MaterialIcons')),
),
Responsive.isWeb(context) ? ButtonBlue(
addLabel: 'Delete Event',
color: Colors.red,
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context){
return ShowDialog(
deleteFunction: () async{
await FirebaseFirestore.instance.runTransaction((Transaction myTransaction) async {
FirebaseStorage.instance.refFromURL(data.docs[index]['PhotoUrl']).delete();
myTransaction.delete(snapshot.data!.docs[index].reference);
}).then((value) => Navigator.pop(context));
},
dialogTitle: "Delete",
dialogContent: "Do you really want to delete ${data.docs[index]['Name']} event?",
);
});
},
icon: const Icon(Icons.delete_outline,),
): InkWell(
onTap: (){
if(defaultTargetPlatform == TargetPlatform.iOS){
showCupertinoDialog(
context: context,
builder: (BuildContext context){
return ShowDialog(
deleteFunction: () async{
await FirebaseFirestore.instance.runTransaction((Transaction myTransaction) async {
FirebaseStorage.instance.refFromURL(data.docs[index]['PhotoUrl']).delete();
myTransaction.delete(snapshot.data!.docs[index].reference);
}).then((value) => Navigator.pop(context));
},
dialogTitle: "Delete",
dialogContent: "Do you really want to delete ${data.docs[index]['Name']} event?",
);
});
}
else if((defaultTargetPlatform == TargetPlatform.android)){
showDialog(
context: context,
builder: (BuildContext context){
return ShowDialog(
deleteFunction: () async{
await FirebaseFirestore.instance.runTransaction((Transaction myTransaction) async {
FirebaseStorage.instance.refFromURL(data.docs[index]['PhotoUrl']).delete();
myTransaction.delete(snapshot.data!.docs[index].reference);
}).then((value) => Navigator.pop(context));
},
dialogTitle: "Delete",
dialogContent: "Do you really want to delete ${data.docs[index]['Name']} event?",
);
});
}
else{
showDialog(
context: context,
builder: (BuildContext context){
return ShowDialog(
deleteFunction: () async{
await FirebaseFirestore.instance.runTransaction((Transaction myTransaction) async {
FirebaseStorage.instance.refFromURL(data.docs[index]['PhotoUrl']).delete();
myTransaction.delete(snapshot.data!.docs[index].reference);
Navigator.pop(context);
showToast(message: 'Deleted Successfully', color: Colors.green);
}).then((value) => Navigator.pop(context));
},
dialogTitle: "Delete",
dialogContent: "Do you really want to delete ${data.docs[index]['Name']} event?",
);
});
}
},
child: const Icon(Icons.delete_outline,),
),
],
),
const Divider(
color: Colors.white70,
height:40.0,
),
],
);
});
}
return const Center(
child: CircularProgressIndicator(),
);
},
)
],
);
}
}
I have been on this the entire day but in vain but in the same product, I have another view details with the same code that works
Please assist me on this
Currently, I have an AlertDialog with an IconButton. The user can click on the IconButton, I have two colors for each click. The problem is that I need to close the AlertDialog and reopen to see the state change of the color icon. I want to change the IconButton color immediately when the user clicks it.
Here is the code:
bool pressphone = false;
//....
new IconButton(
icon: new Icon(Icons.phone),
color: pressphone ? Colors.grey : Colors.green,
onPressed: () => setState(() => pressphone = !pressphone),
),
Use StatefulBuilder to use setState inside Dialog and update Widgets only inside of it.
showDialog(
context: context,
builder: (context) {
String contentText = "Content of Dialog";
return StatefulBuilder(
builder: (context, setState) {
return AlertDialog(
title: Text("Title of Dialog"),
content: Text(contentText),
actions: <Widget>[
TextButton(
onPressed: () => Navigator.pop(context),
child: Text("Cancel"),
),
TextButton(
onPressed: () {
setState(() {
contentText = "Changed Content of Dialog";
});
},
child: Text("Change"),
),
],
);
},
);
},
);
Use a StatefulBuilder in the content section of the AlertDialog. Even the StatefulBuilder docs actually have an example with a dialog.
What it does is provide you with a new context, and setState function to rebuild when needed.
The sample code:
showDialog(
context: context,
builder: (BuildContext context) {
int selectedRadio = 0; // Declare your variable outside the builder
return AlertDialog(
content: StatefulBuilder( // You need this, notice the parameters below:
builder: (BuildContext context, StateSetter setState) {
return Column( // Then, the content of your dialog.
mainAxisSize: MainAxisSize.min,
children: List<Widget>.generate(4, (int index) {
return Radio<int>(
value: index,
groupValue: selectedRadio,
onChanged: (int value) {
// Whenever you need, call setState on your variable
setState(() => selectedRadio = value);
},
);
}),
);
},
),
);
},
);
And as I mentioned, this is what is said on the showDialog docs:
[...] The widget returned by the builder does not share a context with the location
that showDialog is originally called from. Use a StatefulBuilder or a
custom StatefulWidget if the dialog needs to update dynamically.
This is because you need to put your AlertDialog in its own StatefulWidget and move all state manipulation logic on the color there.
Update:
void main() => runApp(MaterialApp(home: Home()));
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton(
child: Text('Open Dialog'),
onPressed: () {
showDialog(
context: context,
builder: (_) {
return MyDialog();
});
},
)));
}
}
class MyDialog extends StatefulWidget {
#override
_MyDialogState createState() => new _MyDialogState();
}
class _MyDialogState extends State<MyDialog> {
Color _c = Colors.redAccent;
#override
Widget build(BuildContext context) {
return AlertDialog(
content: Container(
color: _c,
height: 20.0,
width: 20.0,
),
actions: <Widget>[
FlatButton(
child: Text('Switch'),
onPressed: () => setState(() {
_c == Colors.redAccent
? _c = Colors.blueAccent
: _c = Colors.redAccent;
}))
],
);
}
}
First you need to use StatefulBuilder. Then i am setting _setState variable, which even could be used outside StatefulBuilder, to set new state.
StateSetter _setState;
String _demoText = "test";
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: StatefulBuilder( // You need this, notice the parameters below:
builder: (BuildContext context, StateSetter setState) {
_setState = setState;
return Text(_demoText);
},
),
);
},
);
_setState is used same way as setState method. For example like this:
_setState(() {
_demoText = "new test text";
});
If you're separating your data from the UI via View Models and using the Provider package with ChangeNotifier, you'll need to include your current model like so within the widget calling the dialog:
showDialog(context: context, builder: (dialog) {
return ChangeNotifierProvider.value(
value: context.read<ViewModel>(),
child: CustomStatefulDialogWidget(),
);
},
Note that there may be a cleaner way to do this but this worked for me.
Additional info regarding Provider: https://flutter.dev/docs/development/data-and-backend/state-mgmt/simple
showModalBottomSheet(
context: context,
builder: (context) {
return StatefulBuilder(
builder: (BuildContext context, StateSetter setState /*You can rename this!*/) {
return Container(
height: heightOfModalBottomSheet,
child: RaisedButton(onPressed: () {
setState(() {
heightOfModalBottomSheet += 10;
});
}),
);
});
});
Not sure if this is best practice, but I solved the issue of updating both the dialog state and the content state by wrapping the setState functions, after using the top answer to add state to the dialog:
IconButton(
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) {
return StatefulBuilder(
builder: (context, newSetState) { // Create a "new" state variable
return AlertDialog(
content: DropdownButton(
value: listItem.type,
items: allItems
onChanged: (value) {
newSetState(() {
setState(() {
// HERE SET THE STATE TWICE
// Once with the "new" state, once with the "old"
});
});
})
),
);
}
);
}
),
In fact, you can use StatefullBuilder but the problem is that when you use this widget you cant change the state of the base screen! Prefer to navigate to a new screen in order to use setState.
I was stuck with this issue.You have to Change the name of setState to any Other name and pass this set state to all sub functions.
This will update your Dialog ui on time.
return StatefulBuilder(
builder: (context, setStateSB) {
return AlertDialog(
title: Text("Select Circle To Sync Data!" ,style: TextStyle(color: Colors.white),),
content: Column(
children: [
Text("Select Division!" ,style: TextStyle(color: Colors.white),),
Container(
height: 80,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
InputDecorator(
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0)),
contentPadding: EdgeInsets.all(5),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
isExpanded: true,
value: sync_DivisionName_firstValue,
items: _DivisionName_list.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value,style: TextStyle(color: Colors.black)),
);
}).toList(),
onChanged: (String? newValue) {
setStateSB(() {
sync_DivisionName_firstValue = newValue!;
if(sync_DivisionName_firstValue !="Select Division Name"){
print("sync_DivisionName_firstValue$sync_DivisionName_firstValue");
_getDistrictName(sync_DivisionName_firstValue,setStateSB);
}else{
refreashDivisionName(setStateSB);
}
});
},
)),
),
],
),
),
Text("Select District!" ,style: TextStyle(color: Colors.white),),
Container(
height: 80,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
InputDecorator(
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0)),
contentPadding: EdgeInsets.all(5),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
isExpanded: true,
value: sync_DistrictName_firstValue,
items: _DistrictName_list.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value,style: TextStyle(color: Colors.black),),
);
}).toList(),
onChanged: (String? newValue) {
setStateSB(() {
sync_DistrictName_firstValue = newValue!;
if(sync_DivisionName_firstValue != "Select Division Name" && sync_DistrictName_firstValue != "Select District Name"){
print("sync_DistrictName_firstValue$sync_DistrictName_firstValue");
_getTehsilName(sync_DivisionName_firstValue,sync_DistrictName_firstValue,setStateSB);
}else{
refreashDistrictName(setStateSB);
}
});
},
)),
),
],
),
),
Text("Select Tehsil!" ,style: TextStyle(color: Colors.white),),
Container(
height: 80,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
InputDecorator(
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0)),
contentPadding: EdgeInsets.all(5),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
isExpanded: true,
value: sync_TehsilName_firstValue,
items: _TehsilName_list.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value,style: TextStyle(color: Colors.black),),
);
}).toList(),
onChanged: (String? newValue) {
setStateSB(() {
sync_TehsilName_firstValue = newValue!;
if(sync_DivisionName_firstValue != "Select Division Name" && sync_DistrictName_firstValue != "Select District Name" && sync_TehsilName_firstValue != "Select Tehsil Name"){
print("sync_TehsilName_firstValue$sync_TehsilName_firstValue");
_getRatingAreaName(sync_DivisionName_firstValue,sync_DistrictName_firstValue,sync_TehsilName_firstValue,setStateSB);
}else{
refreashTehsilName(setStateSB);
}
});
},
)),
),
],
),
),
Text("Select Rating Area Name!" ,style: TextStyle(color: Colors.white),),
Container(
height: 80,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
InputDecorator(
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0)),
contentPadding: EdgeInsets.all(5),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
isExpanded: true,
value: sync_RatingAreaName_firstValue,
items: _RatingAreaName_list.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value,style: TextStyle(color: Colors.black),),
);
}).toList(),
onChanged: (String? newValue) {
setStateSB(() {
sync_RatingAreaName_firstValue = newValue!;
if(sync_DivisionName_firstValue != "Select Division Name" && sync_DistrictName_firstValue != "Select District Name" && sync_TehsilName_firstValue != "Select Tehsil Name" && sync_RatingAreaName_firstValue != "Select Rating Area Name"){
print("sync_RatingAreaName_firstValue$sync_RatingAreaName_firstValue");
_getWardCircleName(sync_DivisionName_firstValue,sync_DistrictName_firstValue,sync_TehsilName_firstValue,sync_RatingAreaName_firstValue,setStateSB);
}else{
refreashWardCircleName(setStateSB);
}
});
},
)),
),
],
),
),
Text("Select Ward Circle Name!" ,style: TextStyle(color: Colors.white),),
Container(
height: 80,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
InputDecorator(
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0)),
contentPadding: EdgeInsets.all(5),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
isExpanded: true,
value: sync_circle_name_firstValue,
items: _circle_name_list.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value,style: TextStyle(color: Colors.black),),
);
}).toList(),
onChanged: (String? newValue) {
setStateSB(() {
sync_circle_name_firstValue = newValue!;
print("sync_circle_name_firstValue$sync_circle_name_firstValue");
// if(sync_circle_name_firstValue != "Select Ward Circle Name"){
//
// _getWardCircleName(sync_RatingAreaName_firstValue);
// }else{
//
// }
});
},
)),
),
],
),
),
]),
backgroundColor:Color(0xFFEC9F46),
actions: [
okButton,SyncButton
],
);
},
);
One of the Inner Funciton is like this.
Future<void> refreashDivisionName( StateSetter setInnerState) async {
final List<String> _division_name = await getDivisionNameList();
final List<String> _district_name_list = await getDistrictName(sync_DivisionName_firstValue);
final List<String> _tehsil_name_list = await getTehsilName(sync_DivisionName_firstValue,sync_DistrictName_firstValue);
final List<String> _rating_area_name_list = await getRatingAreaName(sync_DivisionName_firstValue,sync_DistrictName_firstValue,sync_TehsilName_firstValue);
final List<String> _ward_circle_name_list = await getWardCircleName(sync_DivisionName_firstValue,sync_DistrictName_firstValue,sync_TehsilName_firstValue,sync_RatingAreaName_firstValue);
setInnerState(() {
_division_name.insert(0, "Select Division Name");
_DivisionName_list = _division_name;
sync_DivisionName_firstValue = _DivisionName_list[0];
_district_name_list.insert(0, "Select District Name");
_DistrictName_list = _district_name_list;
sync_DistrictName_firstValue = _DistrictName_list[0];
_tehsil_name_list.insert(0, "Select Tehsil Name");
_TehsilName_list = _tehsil_name_list;
sync_TehsilName_firstValue = _TehsilName_list[0];
_rating_area_name_list.insert(0, "Select Rating Area Name");
_RatingAreaName_list = _rating_area_name_list;
sync_RatingAreaName_firstValue = _RatingAreaName_list[0];
_ward_circle_name_list.insert(0, "Select Ward Circle Name");
_circle_name_list = _ward_circle_name_list;
sync_circle_name_firstValue = _circle_name_list[0];
});
}
I hope you under Stand.
base on Andris's answer.
when dialog share the same state with parent widget, you can override parent widget's method setState to invoke StatefulBuilder's setState, so you don't need to call setState twice.
StateSetter? _setState;
Dialog dialog = showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: StatefulBuilder( // You need this, notice the parameters below:
builder: (BuildContext context, StateSetter setState) {
_setState = setState;
return Text(_demoText);
},
),
);
},
);
// set the function to null when dialo is dismiss.
dialogFuture.whenComplete(() => {_stateSetter = null});
#override
void setState(VoidCallback fn) {
// invoke dialog setState to refresh dialog content when need
_stateSetter?.call(fn);
super.setState(fn);
}
Currently to retrieve the value of Dialog I use
showDialog().then((val){
setState (() {});
print (val);
});
Example
1st screen
onPressed: () {
showDialog(
context: context,
barrierDismissible: false,
builder: (context) {
return AddDespesa();
}).then((val) {
setState(() {});
print(val);
}
);
}
2nd screen
AlertDialog(
title: Text("Sucesso!"),
content: Text("Gasto resgristrado com sucesso"),
actions: <Widget>[
FlatButton(
child: Text("OK"),
onPressed: () {
Navigator.pop(context, true);
},
),
],
);
Will be printed true,
Currently, I have an AlertDialog with an IconButton. The user can click on the IconButton, I have two colors for each click. The problem is that I need to close the AlertDialog and reopen to see the state change of the color icon. I want to change the IconButton color immediately when the user clicks it.
Here is the code:
bool pressphone = false;
//....
new IconButton(
icon: new Icon(Icons.phone),
color: pressphone ? Colors.grey : Colors.green,
onPressed: () => setState(() => pressphone = !pressphone),
),
Use StatefulBuilder to use setState inside Dialog and update Widgets only inside of it.
showDialog(
context: context,
builder: (context) {
String contentText = "Content of Dialog";
return StatefulBuilder(
builder: (context, setState) {
return AlertDialog(
title: Text("Title of Dialog"),
content: Text(contentText),
actions: <Widget>[
TextButton(
onPressed: () => Navigator.pop(context),
child: Text("Cancel"),
),
TextButton(
onPressed: () {
setState(() {
contentText = "Changed Content of Dialog";
});
},
child: Text("Change"),
),
],
);
},
);
},
);
Use a StatefulBuilder in the content section of the AlertDialog. Even the StatefulBuilder docs actually have an example with a dialog.
What it does is provide you with a new context, and setState function to rebuild when needed.
The sample code:
showDialog(
context: context,
builder: (BuildContext context) {
int selectedRadio = 0; // Declare your variable outside the builder
return AlertDialog(
content: StatefulBuilder( // You need this, notice the parameters below:
builder: (BuildContext context, StateSetter setState) {
return Column( // Then, the content of your dialog.
mainAxisSize: MainAxisSize.min,
children: List<Widget>.generate(4, (int index) {
return Radio<int>(
value: index,
groupValue: selectedRadio,
onChanged: (int value) {
// Whenever you need, call setState on your variable
setState(() => selectedRadio = value);
},
);
}),
);
},
),
);
},
);
And as I mentioned, this is what is said on the showDialog docs:
[...] The widget returned by the builder does not share a context with the location
that showDialog is originally called from. Use a StatefulBuilder or a
custom StatefulWidget if the dialog needs to update dynamically.
This is because you need to put your AlertDialog in its own StatefulWidget and move all state manipulation logic on the color there.
Update:
void main() => runApp(MaterialApp(home: Home()));
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton(
child: Text('Open Dialog'),
onPressed: () {
showDialog(
context: context,
builder: (_) {
return MyDialog();
});
},
)));
}
}
class MyDialog extends StatefulWidget {
#override
_MyDialogState createState() => new _MyDialogState();
}
class _MyDialogState extends State<MyDialog> {
Color _c = Colors.redAccent;
#override
Widget build(BuildContext context) {
return AlertDialog(
content: Container(
color: _c,
height: 20.0,
width: 20.0,
),
actions: <Widget>[
FlatButton(
child: Text('Switch'),
onPressed: () => setState(() {
_c == Colors.redAccent
? _c = Colors.blueAccent
: _c = Colors.redAccent;
}))
],
);
}
}
First you need to use StatefulBuilder. Then i am setting _setState variable, which even could be used outside StatefulBuilder, to set new state.
StateSetter _setState;
String _demoText = "test";
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: StatefulBuilder( // You need this, notice the parameters below:
builder: (BuildContext context, StateSetter setState) {
_setState = setState;
return Text(_demoText);
},
),
);
},
);
_setState is used same way as setState method. For example like this:
_setState(() {
_demoText = "new test text";
});
If you're separating your data from the UI via View Models and using the Provider package with ChangeNotifier, you'll need to include your current model like so within the widget calling the dialog:
showDialog(context: context, builder: (dialog) {
return ChangeNotifierProvider.value(
value: context.read<ViewModel>(),
child: CustomStatefulDialogWidget(),
);
},
Note that there may be a cleaner way to do this but this worked for me.
Additional info regarding Provider: https://flutter.dev/docs/development/data-and-backend/state-mgmt/simple
showModalBottomSheet(
context: context,
builder: (context) {
return StatefulBuilder(
builder: (BuildContext context, StateSetter setState /*You can rename this!*/) {
return Container(
height: heightOfModalBottomSheet,
child: RaisedButton(onPressed: () {
setState(() {
heightOfModalBottomSheet += 10;
});
}),
);
});
});
Not sure if this is best practice, but I solved the issue of updating both the dialog state and the content state by wrapping the setState functions, after using the top answer to add state to the dialog:
IconButton(
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) {
return StatefulBuilder(
builder: (context, newSetState) { // Create a "new" state variable
return AlertDialog(
content: DropdownButton(
value: listItem.type,
items: allItems
onChanged: (value) {
newSetState(() {
setState(() {
// HERE SET THE STATE TWICE
// Once with the "new" state, once with the "old"
});
});
})
),
);
}
);
}
),
In fact, you can use StatefullBuilder but the problem is that when you use this widget you cant change the state of the base screen! Prefer to navigate to a new screen in order to use setState.
I was stuck with this issue.You have to Change the name of setState to any Other name and pass this set state to all sub functions.
This will update your Dialog ui on time.
return StatefulBuilder(
builder: (context, setStateSB) {
return AlertDialog(
title: Text("Select Circle To Sync Data!" ,style: TextStyle(color: Colors.white),),
content: Column(
children: [
Text("Select Division!" ,style: TextStyle(color: Colors.white),),
Container(
height: 80,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
InputDecorator(
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0)),
contentPadding: EdgeInsets.all(5),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
isExpanded: true,
value: sync_DivisionName_firstValue,
items: _DivisionName_list.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value,style: TextStyle(color: Colors.black)),
);
}).toList(),
onChanged: (String? newValue) {
setStateSB(() {
sync_DivisionName_firstValue = newValue!;
if(sync_DivisionName_firstValue !="Select Division Name"){
print("sync_DivisionName_firstValue$sync_DivisionName_firstValue");
_getDistrictName(sync_DivisionName_firstValue,setStateSB);
}else{
refreashDivisionName(setStateSB);
}
});
},
)),
),
],
),
),
Text("Select District!" ,style: TextStyle(color: Colors.white),),
Container(
height: 80,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
InputDecorator(
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0)),
contentPadding: EdgeInsets.all(5),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
isExpanded: true,
value: sync_DistrictName_firstValue,
items: _DistrictName_list.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value,style: TextStyle(color: Colors.black),),
);
}).toList(),
onChanged: (String? newValue) {
setStateSB(() {
sync_DistrictName_firstValue = newValue!;
if(sync_DivisionName_firstValue != "Select Division Name" && sync_DistrictName_firstValue != "Select District Name"){
print("sync_DistrictName_firstValue$sync_DistrictName_firstValue");
_getTehsilName(sync_DivisionName_firstValue,sync_DistrictName_firstValue,setStateSB);
}else{
refreashDistrictName(setStateSB);
}
});
},
)),
),
],
),
),
Text("Select Tehsil!" ,style: TextStyle(color: Colors.white),),
Container(
height: 80,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
InputDecorator(
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0)),
contentPadding: EdgeInsets.all(5),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
isExpanded: true,
value: sync_TehsilName_firstValue,
items: _TehsilName_list.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value,style: TextStyle(color: Colors.black),),
);
}).toList(),
onChanged: (String? newValue) {
setStateSB(() {
sync_TehsilName_firstValue = newValue!;
if(sync_DivisionName_firstValue != "Select Division Name" && sync_DistrictName_firstValue != "Select District Name" && sync_TehsilName_firstValue != "Select Tehsil Name"){
print("sync_TehsilName_firstValue$sync_TehsilName_firstValue");
_getRatingAreaName(sync_DivisionName_firstValue,sync_DistrictName_firstValue,sync_TehsilName_firstValue,setStateSB);
}else{
refreashTehsilName(setStateSB);
}
});
},
)),
),
],
),
),
Text("Select Rating Area Name!" ,style: TextStyle(color: Colors.white),),
Container(
height: 80,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
InputDecorator(
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0)),
contentPadding: EdgeInsets.all(5),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
isExpanded: true,
value: sync_RatingAreaName_firstValue,
items: _RatingAreaName_list.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value,style: TextStyle(color: Colors.black),),
);
}).toList(),
onChanged: (String? newValue) {
setStateSB(() {
sync_RatingAreaName_firstValue = newValue!;
if(sync_DivisionName_firstValue != "Select Division Name" && sync_DistrictName_firstValue != "Select District Name" && sync_TehsilName_firstValue != "Select Tehsil Name" && sync_RatingAreaName_firstValue != "Select Rating Area Name"){
print("sync_RatingAreaName_firstValue$sync_RatingAreaName_firstValue");
_getWardCircleName(sync_DivisionName_firstValue,sync_DistrictName_firstValue,sync_TehsilName_firstValue,sync_RatingAreaName_firstValue,setStateSB);
}else{
refreashWardCircleName(setStateSB);
}
});
},
)),
),
],
),
),
Text("Select Ward Circle Name!" ,style: TextStyle(color: Colors.white),),
Container(
height: 80,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
InputDecorator(
decoration: InputDecoration(
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10.0)),
contentPadding: EdgeInsets.all(5),
),
child: DropdownButtonHideUnderline(
child: DropdownButton<String>(
isExpanded: true,
value: sync_circle_name_firstValue,
items: _circle_name_list.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value,style: TextStyle(color: Colors.black),),
);
}).toList(),
onChanged: (String? newValue) {
setStateSB(() {
sync_circle_name_firstValue = newValue!;
print("sync_circle_name_firstValue$sync_circle_name_firstValue");
// if(sync_circle_name_firstValue != "Select Ward Circle Name"){
//
// _getWardCircleName(sync_RatingAreaName_firstValue);
// }else{
//
// }
});
},
)),
),
],
),
),
]),
backgroundColor:Color(0xFFEC9F46),
actions: [
okButton,SyncButton
],
);
},
);
One of the Inner Funciton is like this.
Future<void> refreashDivisionName( StateSetter setInnerState) async {
final List<String> _division_name = await getDivisionNameList();
final List<String> _district_name_list = await getDistrictName(sync_DivisionName_firstValue);
final List<String> _tehsil_name_list = await getTehsilName(sync_DivisionName_firstValue,sync_DistrictName_firstValue);
final List<String> _rating_area_name_list = await getRatingAreaName(sync_DivisionName_firstValue,sync_DistrictName_firstValue,sync_TehsilName_firstValue);
final List<String> _ward_circle_name_list = await getWardCircleName(sync_DivisionName_firstValue,sync_DistrictName_firstValue,sync_TehsilName_firstValue,sync_RatingAreaName_firstValue);
setInnerState(() {
_division_name.insert(0, "Select Division Name");
_DivisionName_list = _division_name;
sync_DivisionName_firstValue = _DivisionName_list[0];
_district_name_list.insert(0, "Select District Name");
_DistrictName_list = _district_name_list;
sync_DistrictName_firstValue = _DistrictName_list[0];
_tehsil_name_list.insert(0, "Select Tehsil Name");
_TehsilName_list = _tehsil_name_list;
sync_TehsilName_firstValue = _TehsilName_list[0];
_rating_area_name_list.insert(0, "Select Rating Area Name");
_RatingAreaName_list = _rating_area_name_list;
sync_RatingAreaName_firstValue = _RatingAreaName_list[0];
_ward_circle_name_list.insert(0, "Select Ward Circle Name");
_circle_name_list = _ward_circle_name_list;
sync_circle_name_firstValue = _circle_name_list[0];
});
}
I hope you under Stand.
base on Andris's answer.
when dialog share the same state with parent widget, you can override parent widget's method setState to invoke StatefulBuilder's setState, so you don't need to call setState twice.
StateSetter? _setState;
Dialog dialog = showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: StatefulBuilder( // You need this, notice the parameters below:
builder: (BuildContext context, StateSetter setState) {
_setState = setState;
return Text(_demoText);
},
),
);
},
);
// set the function to null when dialo is dismiss.
dialogFuture.whenComplete(() => {_stateSetter = null});
#override
void setState(VoidCallback fn) {
// invoke dialog setState to refresh dialog content when need
_stateSetter?.call(fn);
super.setState(fn);
}
Currently to retrieve the value of Dialog I use
showDialog().then((val){
setState (() {});
print (val);
});
Example
1st screen
onPressed: () {
showDialog(
context: context,
barrierDismissible: false,
builder: (context) {
return AddDespesa();
}).then((val) {
setState(() {});
print(val);
}
);
}
2nd screen
AlertDialog(
title: Text("Sucesso!"),
content: Text("Gasto resgristrado com sucesso"),
actions: <Widget>[
FlatButton(
child: Text("OK"),
onPressed: () {
Navigator.pop(context, true);
},
),
],
);
Will be printed true,