Nitneuq
Nitneuq

Reputation: 5042

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),
),

Upvotes: 202

Views: 153943

Answers (13)

Jostin Akkara
Jostin Akkara

Reputation: 106

You can actually extract the widget defined inside the builder function into a stateful widget and then use setState() inside the extracted widget as usual. This will also work.

Upvotes: 0

Covenant T. Junior
Covenant T. Junior

Reputation: 66

What I did was to auto-rebuild the dialog. It seems to work and no glitch.

bool verifying = false;

Above is the value that needs to be watched to update the progress bar and the text.

void verify() async {
    String email = _verifyEmailController.text;
    if (email.isEmpty) {
      ScaffoldMessenger.of(context).removeCurrentMaterialBanner();
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(
          content: StyledTextSnackBar(text: "Please input an email"),
          backgroundColor: Colors.white,
          duration: Duration(seconds: 3),
        )
      );
      return;
    }
    setState(() {
      verifying = true;
    });
    try {
      var response = await http.post(
        Uri.parse('https://url/verify/$email/$appID')
      );
      if (response.statusCode >= 200) {
        setState(() {
          verifying = false;
        });
      }
      var data = jsonDecode(response.body);
      print(data);
      if (data['flag'] == 1) {
        Navigator.pop(context);
        ScaffoldMessenger.of(context).removeCurrentMaterialBanner();
        ScaffoldMessenger.of(context).showSnackBar(
          const SnackBar(
            content: StyledTextSnackBar(text: "Verified successfully"),
            backgroundColor: Colors.white,
            duration: Duration(seconds: 3),
          )
        );
        MaterialPageRoute(builder: (context) => const PaymentSuccess());
      } else if(data['flag'] == 0) {
        setState(() {
          verifying = false;
        });
        Navigator.pop(context);
        ScaffoldMessenger.of(context).removeCurrentMaterialBanner();
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: StyledTextSnackBar(text: data['message']),
            backgroundColor: Colors.white,
            duration: const Duration(seconds: 3),
          )
        );
      }
    } catch (e) {
      setState(() {
        verifying = false;
      });
      if (context.read<SettingDatabase>().settings.first.enableVibrations) Vibration.vibrate(duration: 50);
      ScaffoldMessenger.of(context).removeCurrentMaterialBanner();
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(
          content: StyledTextSnackBar(text: "Something went wrong. Please try again"),
          backgroundColor: Colors.white,
          duration: Duration(seconds: 3),
        )
      );
    }
  }

The dialog function:

void verifyPurchase(appID) {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        elevation: 50,
        backgroundColor: Colors.black,
        shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
        title: const Center(child: StyledTitle(text: 'Verify Purchase')),
        content: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            const StyledTextLightCenter(
              text: 'Input your email here',
            ),
            const SizedBox(height: 10),
            _buildTextField(
              controller: _verifyEmailController,
              label: const StyledTextLabel(text: 'Email'),
              icon: Icons.email,
              onChanged: (value) {
              },
            ),
            const SizedBox(height: 20),
            const Icon(
              Icons.workspace_premium_rounded,
              size: 100,
              color: Colors.white,
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () async {
                if(!verifying) verify();
                
                // THE MAIN TRICK
                Navigator.pop(context);
                verifyPurchase(appID);
              },
              style: ElevatedButton.styleFrom(
                fixedSize: const Size(100, 50),
                backgroundColor: Colors.white,
                shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(10)),
              ),
              child: verifying ? const SizedBox(
                height: 15,
                width: 15,
                child: CircularProgressIndicator(
                  color: Colors.black,
                  strokeWidth: 2,
                ),
              ) : const StyledTextDark(text: 'Verify'),
            ),
          ],
        ),
      ),
    );
  }

The popping and rebuild works within split microseconds which was unnoticeable with the button elements updating seamlessly.

// THE MAIN TRICK
Navigator.pop(context);
verifyPurchase(appID);

Upvotes: 0

Fayyaz kharl
Fayyaz kharl

Reputation: 49

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.

Upvotes: 0

Ricky Kresslein
Ricky Kresslein

Reputation: 419

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"
                  });
                });
              })
            ),
          );
        }
      );
    }
  ),

Upvotes: 1

poya sanjari
poya sanjari

Reputation: 1

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.

Upvotes: 0

Ndiaga GUEYE
Ndiaga GUEYE

Reputation: 391

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;
            });
          }),
        );
      });
});

Upvotes: 1

wjploop
wjploop

Reputation: 259

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);
}

Upvotes: -1

Adam Bridges
Adam Bridges

Reputation: 518

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

Upvotes: 3

George Rappel
George Rappel

Reputation: 7206

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.

Upvotes: 98

Ali Bayram
Ali Bayram

Reputation: 7941

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"),
            ),
          ],
        );
      },
    );
  },
);

Upvotes: 486

Andris
Andris

Reputation: 4203

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";
});

Upvotes: 44

Developer CF
Developer CF

Reputation: 21

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,

Upvotes: -2

Shady Aziza
Shady Aziza

Reputation: 53347

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;
                }))
      ],
    );
  }
}

Upvotes: 90

Related Questions