Unable to update switch tile within modal bottom sheet - flutter

We are trying to update a switch tile within a modal bottom and I suspect this is effecting the state somehow but I am not sure how to resolve this issue.
ListTile(
title: const Text('Transactional'),
subtitle: const Text('Email, Push, SMS'),
trailing: Icon(Icons.adaptive.arrow_forward),
onTap: () {
HapticFeedback.lightImpact();
showModalBottomSheet(
barrierColor: Colors.black.withOpacity(0.5),
enableDrag: true,
shape: const RoundedRectangleBorder(
borderRadius:
BorderRadius.vertical(top: Radius.circular(20))),
context: context,
builder: (context) {
// Using Wrap makes the bottom sheet height the height of the content.
// Otherwise, the height will be half the height of the screen.
return Wrap(alignment: WrapAlignment.center, children: [
bottomSheetBar(),
ListTile(
title: Text('Transactional',
style: Theme.of(context).textTheme.titleLarge),
),
ListTile(
title: Text(
'Recieve important notifications about any payments, cancellations and about your acccount.',
style: Theme.of(context).textTheme.bodyMedium),
),
SwitchListTile(
title: const Text('Push'),
value: _push,
onChanged: (bool value) {
setState(() {
_push = value;
});
},
// secondary: const Icon(Icons.lightbulb_outline),
),
SwitchListTile(
title: const Text('Email'),
value: _email,
onChanged: (bool value) {
setState(() {
_email = value;
});
},
// secondary: const Icon(Icons.lightbulb_outline),
),
SwitchListTile(
title: const Text('SMS'),
value: _sms,
onChanged: (bool value) {
setState(() {
_sms = value;
});
},
// secondary: const Icon(Icons.lightbulb_outline),
),
ElevatedButton(
onPressed: () {}, child: const Text('Done')),
const SizedBox(height: 20),
]);
},
);
},
),

you could use the StatefulBuilder widget above the wrap and thus only update the state within it and not the entire modal

Related

how to have two radio buttons in one row in each listtile item created by the user? in flutter

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.

how to update the value of a text in a card - Flutter/dart

I'm trying to change the value of the text which is the 'placeholder' in this method. But for some reason it's not updating to the new value that i wrote & retrieved from the dialog. I even tested to see what's the value of 'placeholder' and it returned the correct value/text that i wrote in the dialog but still it wasn't updated in the card.
Widget buildTextField(
String subtitle, String placeholder,icon) {
// ignore: dead_code
return Padding(
padding: const EdgeInsets.only(bottom: 15.0),
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0)),
color: Colors.white,
margin: EdgeInsets.fromLTRB(100, 10, 100, 10),
child: ListTile(
title: Text(placeholder),
subtitle: Text(subtitle),
leading: Icon(
icon,
color: Color.fromARGB(255, 44, 148, 233),
),
trailing: IconButton(
onPressed: () async {
final name = await openDialog(subtitle,placeholder);
if(name == null || name.isEmpty) return;
print(name);
setState(() => placeholder = name);
print(placeholder);
//this.title: Text(placeholder);
// setState(() {
// placeholder = this.name;
// });
},
icon: Icon(Icons.edit_outlined),
), ),
),
);
}
Future <String?>openDialog(String subtitle, String placeholder) => showDialog<String>
(
context: context, builder: (context)=> AlertDialog(
title: Text(subtitle),
content: TextField(autofocus: true,
decoration: InputDecoration(hintText: 'Enter your name'),
controller: controller ..text = placeholder,
// controller: controller,
),
actions: [
TextButton(onPressed: submit,
child: Text('Save'))
],
),
);
void submit(){
// Navigator.of(context).pop(controller.text);
Navigator.of(context, rootNavigator: true).pop(controller.text);
}
The text don't update in your card because when your setState rebuild your widget tree, the call to the function buildTextField reset it to the value passed in parameter. You can try to use a variable defined in your class instead of using an Hard coded string in your function parameter.
Here an exemple:
class _MyHomePageState extends State<MyHomePage> {
TextEditingController controller = TextEditingController();
String CardText = "Name";
String CardSubtitle = 'Card Subtitle';
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
backgroundColor: Color.fromRGBO(37, 57, 92, 1.0),
),
body: SafeArea(
child: Center(
child: Column(
children: [
buildTextField(
CardSubtitle, CardText, Icons.person
),
],
),
),
),
);
}
Widget buildTextField(
String subtitle, String placeholder,icon) {
// ignore: dead_code
return Padding(
padding: const EdgeInsets.only(bottom: 15.0),
child: Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0)),
color: Colors.white,
margin: EdgeInsets.fromLTRB(100, 10, 100, 10),
child: ListTile(
title: Text(placeholder),
subtitle: Text(subtitle),
leading: Icon(
icon,
color: Color.fromARGB(255, 44, 148, 233),
),
trailing: IconButton(
onPressed: () async {
final name = await openDialog(subtitle,placeholder);
if(name == null || name.isEmpty) return;
print(name);
setState(() => CardText = name);
print(placeholder);
//this.title: Text(placeholder);
// setState(() {
// placeholder = this.name;
// });
},
icon: Icon(Icons.edit_outlined),
), ),
),
);
}
Future <String?>openDialog(String subtitle, String placeholder) => showDialog<String>
(
context: context, builder: (context)=> AlertDialog(
title: Text(subtitle),
content: TextField(autofocus: true,
decoration: InputDecoration(hintText: 'Enter your name'),
controller: controller ..text = placeholder,
// controller: controller,
),
actions: [
TextButton(onPressed: submit,
child: Text('Save'))
],
),
);
void submit(){
// Navigator.of(context).pop(controller.text);
Navigator.of(context, rootNavigator: true).pop(controller.text);
}
}
Try to wrap your Card widget with the Builder Widget that can rebuild and update your widget
Builder(
builder: (context) {
return Card(...);
},
),

How to remove popup when I move the cursor away from the button in Flutter?

I build this code below that makes possible to show my Popupmenubuttons Items when I put the cursor over the button without clicking the button. But there is a problem. I can show my items with this method but I can not make possible to close my items in the moment I move my cursor from the button. Please look carefully my code and tell me if you have any idea how to solve this.
openPopUpItem2() {
GestureDetector? detector;
searchForGestureDetector(BuildContext element) {
element.visitChildElements((element) {
if (element.widget != null && element.widget is GestureDetector) {
detector = element.widget as GestureDetector;
} else {
searchForGestureDetector(element);
}
});
}
searchForGestureDetector(keyList[2].currentContext!);
detector!.onTap!();
}
The code below shows how I have used the function to show my popupItems.
InkWell(
onHover: (value) {
if (value) openPopUpItem2();
},
onTap: () {},
child: PopupMenuButton(
key: keyList[2],
tooltip: '',
color: Color(0xFF262533),
position: PopupMenuPosition.under,
child: MouseRegion(
onEnter: (details) =>
setState(() => ishovering2 = true),
onExit: (details) => setState(() {
ishovering2 = false;
}),
child: Text(
'Blog',
style: TextStyle(
color: ishovering2 ? Colors.pink : Colors.white,
fontSize: 24,
fontFamily: 'Poppins',
),
),
),
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
PopupMenuItem(
child: OnHover(
builder: (isHovered) {
final color = isHovered
? Colors.pink
: const Color(0xFF262533);
return ListTile(
title: const Text(
'Archiv',
style: TextStyle(color: Colors.white),
),
enabled: true,
hoverColor: color,
onTap: () {},
);
},
),
),
]),
),
This is tricky indeed. What comes to my mind is if you would wrap your button on another InkWell and add some padding to it. And on onHover method you would dismiss your items.
Here is how I think it should look like:
InkWell(
onHiver: (){
dismissItems();
},
child:
Padding(
padding: //some padding
child: // your button
)
)

Clear controller textfield flutter

I'm facing something strange. I have textfield in my app that can be cleared without problem, and once cleared, the delete icon disappears.
But, when i want to clear a textfield that is in a AlertDialog, the text is cleared, but the icon to delete stays on.
final TextEditingController _namespaceController = TextEditingController();
void clearNamespaceController() {
_namespaceController.clear();
setState(() {});
}
Widget _displayDialogForEdition(result, index, context) {
return IconButton(
icon: Icon(Icons.edit),
onPressed: () {
setState(() {
_namespaceController.text = result[index].name;
});
showDialog<String>(
context: context,
builder: (BuildContext context) => AlertDialog(
title: const Text("Modification d'une configuration"),
content: Container(
// padding: EdgeInsets.all(16),
child: TextField(
controller: _namespaceController,
decoration: InputDecoration(
prefixIcon: Icon(Icons.search, color: Theme.of(context).primaryColor),
border: OutlineInputBorder(),
labelText: 'Exclure le Namespace',
suffixIcon: _namespaceController.text.length == 0
? null
: IconButton(
icon: Icon(Icons.clear),
onPressed: clearNamespaceController,
),
labelStyle: Theme.of(context).textTheme.headline6),
),
),
actions: <Widget>[
TextButton(
onPressed: () => Navigator.pop(context, 'Cancel'),
child: const Text('Cancel'),
),
TextButton(
onPressed: () => Navigator.pop(context, 'OK'),
child: const Text('OK'),
),
],
),
);
});
}
You need to use StatefulBuilder() to use setState() inside AlertBox(). Using StatefulBuilder() it build the UI of only your AlertBox(). If you don't use StatefulBuilder() the UI of your AlertBox() wont update if you check your _controller.text==0 and change your Icon.
You can use like this.
showDialog(
context: context,
builder: (ctx) {
bool isTextClear = true;
return StatefulBuilder(builder: (ctx, setState) {
return AlertDialog(
title: Text("Add name"),
content: Container(
child: TextField(
controller: _controller,
onChanged: (val) {
setState(
() {
if (_controller.text.isEmpty) {
isTextClear = true;
} else {
isTextClear = false;
}
},
);
},
decoration: InputDecoration(
labelText: "Name",
prefixIcon: Icon(Icons.search),
suffixIcon: isTextClear
? null
: IconButton(
onPressed: () {
setState(() {
isTextClear = true;
_controller.clear();
});
},
icon: Icon(
Icons.clear,
),
),
),
),
),
);
});
},
);
You can try here
That's because your TextField holds the Focus you need to unfocus that in order to get what you want.

How to refresh an AlertDialog in Flutter?

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,