SetState() does not change my state in a PopupMenuButton - flutter

My screen bears a popup menu where the user can pass in expert mode by taping on a button (see the screen shot below).
But, though the setState() is being executed, the redraw always resets ìsExpertMode' to its default value (true).
class _SmartboxDashboard extends State<SmartboxDashboard>
with
Module<SmartboxDashboard>,
SingleTickerProviderStateMixin,
AutomaticKeepAliveClientMixin<SmartboxDashboard> {
/// Is expert mode?
bool isExpertMode = true;
return DefaultTabController(
length: _tabSectionBodies.length,
child: Scaffold(
resizeToAvoidBottomInset: true,
appBar: AppBar(
bottom: TabBar(isScrollable: true, tabs: tabSectionTabs),
title: Text(deviceDisplayName(widget.device.name)),
actions: [
PopupMenuButton(
icon: Icon(Icons.menu),
position: PopupMenuPosition.under,
itemBuilder: (context) {
return [
PopupMenuItem<int>(
value: 2,
child: ListTile(
leading: ElevatedButton(
child: Text(isExpertMode
? "Normal mode"
: "Expert mode"),
//value: isExpertMode,
onPressed: () {
setState(() {
isExpertMode =
appState.isExpertMode = !isExpertMode;
});
}),
title: Text(
isExpertMode ? "Expert mode" : "Normal mode"),
subtitle: Text(isExpertMode
? "Turning to \"normal mode\" will streamline the information and you only see essentials informationabout your battery"
: "Turning to \"expert mode\" will show more screens and deeper information about your batteries"),
)
),
];
},
onSelected: (value) async {
switch (value) {
case 2:
//setState(() {
// isExpertMode = appState.isExpertMode = !isExpertMode;
// print("Expert mode turned to $isExpertMode");
});
break;
}
setState(() {
sharing = false;
});
}),
]),
body: TabBarView(children: _tabSectionBodies),

you can use StatefulBuilder
bool isSwitched = true;
PopupMenuItem(child: StatefulBuilder(
builder: (BuildContext context,
void Function(void Function()) setState) {
return Switch(
value: isSwitched,
onChanged: (value) {
setState(() {
isSwitched = value;
print(isSwitched);
});
},
activeTrackColor: Colors.lightGreenAccent,
activeColor: Colors.green,
);
},
))

I think it should be
isExpertMode = !isExpertMode;.

You need to use StatefulBuilder here, because the context and setState scope has been changed. Whenever new StatefulBuilder Widget is used you will get a new setState local function to update the state.

Related

How to show active mark on RadioListTile?

How to show active mark on RadioListTile? I used selected and activeColor but it does not work.
RadioListTile(
selected: true,
activeColor: Theme.of(context).primaryColor, //
title: Text(AppLocalizations.key(context, 'setDark')),
value: ThemeMode.dark,
groupValue: _themeMode,
onChanged: (value) {},
),
Documentation
"groupValue → T?
The currently selected value for this group of radio buttons.
This radio button is considered selected if its value matches the groupValue.
"
The one selected is the one whose group value matches the value.
Therefore your radio buttons should have as groupValue Theme.Dark or Theme.Light and update it on onChanged.
You need to create two RadioListTile for this and will contain the same groupValue. Based on groupValue it will decide active RadioListTile.
When value match with groupValue it will show active mark status.
class _HomesState extends State<Homes> {
ThemeMode _themeMode = ThemeMode.dark;
#override
Widget build(BuildContext context) {
return Scaffold(body: LayoutBuilder(
builder: (context, constraints) {
return Center(
child: Column(
children: [
RadioListTile<ThemeMode>(
selected: true,
activeColor: Theme.of(context).primaryColor,
title: Text(
'setDark',
),
value: ThemeMode.dark,
groupValue: _themeMode,
onChanged: (value) {
setState(() {
_themeMode = value!;
});
},
),
RadioListTile<ThemeMode>(
selected: true,
activeColor: Theme.of(context).primaryColor,
title: Text(
'setLight',
),
value: ThemeMode.light,
groupValue: _themeMode,
onChanged: (value) {
setState(() {
_themeMode = value!;
});
},
),
],
),
);
},
));
}
}

Flutter/Dart - How can I pass radio button value to a different class in another file?

I need to pass the radio button value stored in variable radioValue from homepage.dart to class DialogFactory in dialogfactory.dart, where I will use it. The current radioValue value should be passed when I press a button which calls function _openDialog(), which is meant to open the alert dialog with the selected style.
======================================
homepage.dart
[...]
class _MyHomePageState extends State<MyHomePage> {
int radioValue = 1;
void _handleRadioValueChange(int value) {
setState(() {
radioValue = value;
});
}
void _openDialog() {
DialogFactory.showAlertDialog(
context,
title: Text('Alert Dialog!!!'),
content: Text(
'THIS IS AN ALERT DIALOG! IT MEANS YOU SHOULD BE IN ALERT STATE, RIGHT?'),
actions: [
DialogAction(
child: Text('YES'),
onPressed: () => print('YES'),
),
DialogAction(
child: Text('NO'),
onPressed: () => print('NO'),
)
],
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Radio(
value: 0,
groupValue: radioValue,
onChanged: _handleRadioValueChange,
),
new Text('IOS'),
Radio(
value: 1,
groupValue: radioValue,
onChanged: _handleRadioValueChange,
),
new Text('Android'),
RaisedButton(
child: Text('Show Alert'),
color: Colors.purple,
textColor: Colors.white,
onPressed: _openDialog,
),
],
),
),
);
}
}
dialogfactory.dart:
class DialogFactory {
static Future<T> showAlertDialog<T>(BuildContext context,
{Widget title, Widget content, List<DialogAction> actions}) {
IDialog dialogData;
int radioValue = -1;
// HOW TO GET RADIO VALUE?
if (radioValue == 0) {
// ios
dialogData = IosDialog();
} else if (radioValue == 1) {
//android
dialogData = AndroidDialog();
} else {
dialogData = AndroidDialog();
}
return showDialog(
context: context,
builder: (context) => dialogData.create(
context,
title ?? Text('Não informado'),
content ?? Text('Não informado'),
actions ?? [],
),
);
}
}
link to the repository with the code: https://github.com/gicraveiro/FactoryMethod
I passed the radioValue from the homepage.dart into the showAlertDialog.

Flutter setState not update Widget [duplicate]

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,

Flutter showDialog with loading spinner and confirmation

In flutter, I have a showDialog() with cancel and confirm button. The confirm button will trigger a call to an API. What I need is to show a "loading..." in the showDialog window after the click and once the API call is finished to show a success or failure. How can I manage this? Or should I close the window, waiting for the reply and popup a new dialog window with success or false? Thx for any help or better suggestion. Here what I have so far:
void _showAlert(String textLabel, String action, String linkId) {
showDialog(
context: context,
//barrierDismissible: false, // on external click
builder: (_) => new AlertDialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10.0)),
title: new Text(
'Please confirm:',
style: TextStyle(color: Colors.deepOrange, fontWeight: FontWeight.bold),
textAlign: TextAlign.center,
),
content: new Text(
textLabel,
style: new TextStyle(fontSize: 20.0),
),
actions: <Widget>[
new FlatButton(
onPressed: () {
Navigator.pop(context);
},
child: new Text('CANCEL')),
new FlatButton(
onPressed: () {
_postAction(action, linkId).then((_) => setState(() {}));
Navigator.pop(context);
},
child: new Text('I CONFIRM')),
],
));
}
You can try this,this is just the idea..
class _SampleState extends State<Sample> {
bool isSuccessFromApi = false;
bool isLoading = false;
Widget popup() {
showDialog(context: context, builder: (builder) {
return AlertDialog(
content: !isSuccessFromApi ? Container(
child: Text('Are you Sure???'),
) : Container( child: isLoading ? CircularProgressIndicator() : Text('Success'),),
actions: <Widget>[
Text('Cancel'),
InkWell(child: Text('OK'), onTap: apiCall,)
],
);
});
}
void apiCall(){
setState(() {
isLoading = true;
});
//call the api
//after success or failure
setState(() {
isLoading = false;
isSuccessFromApi = true;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: popup(),
);
}
}

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,