173

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),
),
George
  • 6,886
  • 3
  • 44
  • 56
Nitneuq
  • 3,866
  • 13
  • 41
  • 64

11 Answers11

431

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"),
            ),
          ],
        );
      },
    );
  },
);
Michal Šrůtek
  • 1,647
  • 16
  • 17
Mehmet Ali Bayram
  • 7,222
  • 2
  • 22
  • 27
  • 2
    this is the right answer, I never thought you could 'subClass\subOverload' [setState] live saviour – Kohls Dec 12 '19 at 18:35
  • 2
    Not working for me either. Is there anyone who can explain why it is not working for some people? – Mutlu Simsek Apr 25 '20 at 09:50
  • 2
    I'm on Dart 2.8.1 and Flutter 1.19.0-0.0.pre, It's workin g as expecte in WEB, Thanks :) – Jefriiyer S May 10 '20 at 13:28
  • 5
    @MutluSimsek It doesnt work when you trying to setState a variable that is declared outside of StatefulBuilder/StatefulWidget – kashlo May 14 '20 at 20:58
  • 2
    Thanks. It's working for me. But if I add any child in a stateful builder from outside separate method it's not working. Then if I add the child directly inside the stateful builder it works. Changing the variable inside the stateful builder works. If it's outside the stateful builder then its not working. – Vinoth Vino Jun 12 '20 at 07:34
  • This solution does not work in "content" attribute, only works in actions. @malibayram91 malibayram91 do you have any suggestion? Please share. Thanks. – Kamlesh May 22 '21 at 13:05
  • I had to add `StatefulBuilder` between `showDialog` and `AlertDialog` – Kasun Oct 30 '21 at 19:39
94

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.

George
  • 6,886
  • 3
  • 44
  • 56
  • 1
    Perfect! Was stuck on this for a long while! – Monis Apr 01 '20 at 17:48
  • 1
    Thank you for your code, and a reminder as in your code, variable must bu given outside from builder methot or you can spend your ten minutes why is the heck is that not working :D and answer is, if your StatefulBuilder widget, builder method recall, you are simply creating same state. Example (context, setState){bool isLoading=true;} => for this code, isloading always become true :D Again thank u, again. – leylekseven May 14 '22 at 12:22
86

This is because you need to put your AlertDialog in its own StatefulWidget and move all state manipulation logic on the color there.

Update:

enter image description here

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;
                }))
      ],
    );
  }
}
Shady Aziza
  • 50,824
  • 20
  • 115
  • 113
  • thank you , I tried to create an other stful widget class but when I press the button to call my widet, I have nothing. Normaly when I call my alertdialog stfull widget It return this @override Widget build(BuildContext context) { return new AlertDialog( content: new Container .... – Nitneuq Aug 23 '18 at 11:23
  • I'm trying to extend this to change the enabled/disabled state of the "ok" action button for a dialog. But I'm not having any success yet. Any suggestions? – Eradicatore Aug 18 '19 at 20:10
  • This is the best way to do! – Ballsigno Jun 13 '20 at 14:05
  • Perfect solution, for creating a stateful AlertDialog. Should have been the accepted answer, as this provides more options for code refactoring instead of nested blocks of code. Thank you for the clean answer. 50+ – vijay Dec 10 '20 at 11:34
  • very useful, thnak you – Yılmaz edis Jan 16 '22 at 14:11
  • What if in Home there is a list and you have to refresh it on Dialog close ? – cwhisperer May 03 '23 at 09:21
  • this answer is useful, thanks. – mNouh May 07 '23 at 13:08
42

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";
});
Andris
  • 3,895
  • 2
  • 24
  • 27
3

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

Adam Bridges
  • 306
  • 3
  • 7
1
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;
            });
          }),
        );
      });
});
Regular Jo
  • 5,190
  • 3
  • 25
  • 47
Ndiaga GUEYE
  • 191
  • 3
  • 7
1

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"
                  });
                });
              })
            ),
          );
        }
      );
    }
  ),
Ricky Kresslein
  • 372
  • 1
  • 13
0

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.

lepsch
  • 8,927
  • 5
  • 24
  • 44
0

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.

-1

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);
}
wjploop
  • 209
  • 2
  • 8
-2

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,

Catarina Ferreira
  • 1,824
  • 5
  • 17
  • 26
  • This does not really change the state of the dialog, because the dialog has its own state and using the conventional setState is really not going to solve anything. – Denzel Aug 23 '21 at 20:59