如何在Flutter中刷新AlertDialog?

185

目前,我有一个带有IconButtonAlertDialog。用户可以单击IconButton,每次点击都有两种颜色。问题是,我需要关闭AlertDialog并重新打开才能看到颜色图标的状态更改。我希望在用户单击它时立即更改IconButton的颜色。

以下是代码:

bool pressphone = false;
//....
new IconButton(
   icon: new Icon(Icons.phone),
   color: pressphone ? Colors.grey : Colors.green,
   onPressed: () => setState(() => pressphone = !pressphone),
),
11个回答

450

使用StatefulBuilder在Dialog中使用setState并仅在其中更新小部件。

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

2
这是正确的答案,我从未想过你能够 “subClass\subOverload” [setState],真是救命稻草。 - Kohls
3
我也无法使用。有没有人能解释一下为什么有些人不能使用? - Mutlu Simsek
2
我正在使用Dart 2.8.1和Flutter 1.19.0-0.0.pre,在WEB中正常工作,谢谢 :) - Jefriiyer S
5
当你尝试在StatefulBuilder/StatefulWidget之外声明一个变量并进行setState时,它是无效的。 - kashlo
2
谢谢。这对我很有效。但是如果我从外部单独的方法中添加任何子级到有状态构建器中,它就不起作用。然后,如果我直接在有状态构建器内添加子级,则可以正常工作。在有状态构建器内更改变量有效,但如果在有状态构建器之外则无效。 - Vinoth Vino
显示剩余2条评论

95
在AlertDialog的content部分使用StatefulBuilder。即使StatefulBuilder文档实际上也有一个带有对话框的示例。
它提供了一个新的contextsetState函数,以在需要时重新构建。
示例代码:
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);
                },
              );
            }),
          );
        },
      ),
    );
  },
);

正如我之前所提到的,showDialog docs中说:

[...] 由builder返回的widget与最初调用showDialog的位置不共享上下文。如果对话框需要动态更新,请使用StatefulBuilder或自定义StatefulWidget。


1
太好了!我卡在这里很久了! - Monis
1
感谢您的代码,并提醒您在代码中,变量必须在构建器方法之外给出,否则您可能会花费十分钟来弄清楚为什么不起作用:D 答案是,如果您的StatefulBuilder小部件,构建器方法被调用,您只是创建了相同的状态。例如(context,setState){bool isLoading=true;} => 对于此代码,isloading始终为true:D 再次感谢您。 - leylekseven

87

这是因为您需要将AlertDialog放在自己的StatefulWidget中,并将所有状态操作逻辑移动到那里。

更新:

输入图像描述

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

谢谢,我尝试创建另一个有状态的小部件类,但当我按按钮调用我的小部件时,什么都没有。通常当我调用我的AlertDialog stfull小部件时,它会返回以下内容:@override Widget build(BuildContext context) { return new AlertDialog( content: new Container .... - Nitneuq
我正在尝试扩展此功能,以更改对话框中“确定”操作按钮的启用/禁用状态。但是我还没有取得任何成功。有什么建议吗? - Eradicatore
完美的解决方案,用于创建有状态的AlertDialog。这应该是被接受的答案,因为它提供了更多的代码重构选项,而不是嵌套的代码块。 感谢您提供简洁明了的答案。50+ - vijay
非常有用,谢谢。 - Yılmaz edis
这个答案很有用,谢谢。 - mNouh
显示剩余2条评论

42

首先您需要使用StatefulBuilder。然后我设置了_setState变量,即使在StatefulBuilder之外也可以使用它来设置新的状态。

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的使用方式与setState方法相同。例如像这样:

_setState(() {
    _demoText = "new test text";
});

这对我来说非常完美,这是我找到的唯一能够从对话框代码外部更新对话框内容的方法。 - Hasan Mhd Amin
这也是唯一一个对我有效的。 - Michael Ashenafi
1
如何初始化_setState变量 - minato
太好了..这是我找到的唯一一种在对话框外更新变量的方法。谢谢!! - fazilSizzlers

3
如果您使用视图模型将数据与UI分离,并且使用 Provider 包和 ChangeNotifier,那么您需要在调用对话框的小部件中这样包含当前模型:
showDialog(context: context, builder: (dialog) {
              return ChangeNotifierProvider.value(
                  value: context.read<ViewModel>(),
                child: CustomStatefulDialogWidget(),
              );
            },

请注意,可能有更简洁的方法来完成此操作,但这对我有效。
有关Provider的其他信息: https://flutter.dev/docs/development/data-and-backend/state-mgmt/simple

非常感谢,这正是我想要的。 - Varad Gauthankar
谢谢您的建议。在我的情况下,我使用了 Consumer,它很有效! - John Siu

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

1

我不确定这是否是最佳实践,但我通过包装setState函数解决了更新对话状态和内容状态的问题,在使用顶部答案将状态添加到对话框后:

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

0

我被这个问题困扰了。你必须将setState的名称更改为其他名称,并将此setState传递给所有子函数。这将及时更新您的对话框UI。

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

其中一个内部函数是这样的。

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

我希望你能理解。


0
实际上,你可以使用 StatefulBuilder 但问题是当你使用这个小部件的时候,你无法改变基础屏幕的状态!建议导航到新的屏幕以便使用 setState

-1

基于 Andris 的 答案

当对话框与父部件共享状态时,您可以重写父部件的方法 setState 以调用 StatefulBuilder 的 setState,这样就不需要调用两次 setState。

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接