0

我有一个带有 StatelessWidget 子级的 StatefulWidget 父级,它返回一个 AlertDialog 框。当按下“绿色下载”按钮时,StatelessWidget 是从 StatefulWidget 中的构建器构建的。(在 AlertDialog 中确认后,完整的代码将获取并存储数据)。

在 AlertDialog 框中是一个 DropdownButtonFormField。我已经内置了自己的验证和错误消息,以确保关联的值不为空。(我无法获得 DropdownButtonFormField 的内置验证来显示整个错误消息而不会被切断)。

我不明白为什么我的 AlertDialog 没有更新以显示回调的 SetState 之后的错误消息,即使使用 StatefulBuilder(我可能没有正确使用)。我试过使用 StatefulWidget

当前输出: 当您在 AlertDialog 中按下 yes 按钮,但下拉列表值为 null 或为空时,AlertDialog 不会更新以在显示错误消息的 AlertDialog 中显示 Center 小部件。如果您弹出 AlertDialog 并重新打开它,它会显示错误消息。

所需的输出 当您按下 AlertDialog 中的 yes 按钮,但下拉列表值为 null 或为空时,AlertDialog 会更新以在显示错误消息的 AlertDialog 中显示 Center 小部件。

请问你能帮忙吗?

在下面重新创建的可用代码:

import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'dart:io';

void main() {
  runApp(MaterialApp(home: MyApp()));
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  bool _isLoading = false;
  bool _downloaded = false;

  File cardImage;

  String _languageDropdownValue;

  bool isError = false;

  List<Map<String, String>> _languages = [
    {'code': 'en', 'value': 'English'},
    {'code': 'fr', 'value': 'French'},
  ];

  @override
  Widget build(BuildContext context) {
    return Material(
      child: Center(
        child: _downloaded
            ? IconButton(
                alignment: Alignment.center,
                padding: EdgeInsets.symmetric(horizontal: 0),
                icon: Icon(
                  Icons.open_in_new,
                  size: 45.0,
                  color: Colors.green,
                ),
                onPressed: () {
                  print('Open button pressed');
                })
            : _isLoading
                ? CircularProgressIndicator(
                    valueColor: AlwaysStoppedAnimation<Color>(Colors.green),
                  )
                : IconButton(
                    alignment: Alignment.center,
                    padding: EdgeInsets.symmetric(horizontal: 0),
                    icon: Icon(
                      Icons.download_rounded,
                      size: 45.0,
                      color: Colors.green,
                    ),
                    onPressed: () {
                      print('Download button pressed');
                      showDialog(
                        context: context,
                        builder: (context) {
                          return StatefulBuilder(
                              builder: (context, StateSetter setState) {
                            return DownloadScreen(
                              callbackFunction: alertDialogCallback,
                              dropDownFunction: alertDialogDropdown,
                              isError: isError,
                              languages: _languages,
                              languageDropdownValue: _languageDropdownValue,
                            );
                          });
                        },
                      );
                    }),
      ),
    );
  }

  String alertDialogDropdown(String newValue) {
    setState(() {
      _languageDropdownValue = newValue;
    });
    return newValue;
  }

  alertDialogCallback() {
    if (_languageDropdownValue == null || _languageDropdownValue.isEmpty) {
      setState(() {
        isError = true;
      });
    } else {
      setState(() {
        isError = false;
        startDownload();
      });
    }
  }

  void startDownload() async {
    print('selected language is: $_languageDropdownValue');
    Navigator.pop(context);
    print('start download');
    setState(() => _downloaded = true);
  }
}

class DownloadScreen extends StatelessWidget {
  DownloadScreen(
      {@required this.callbackFunction,
      @required this.dropDownFunction,
      @required this.isError,
      @required this.languages,
      @required this.languageDropdownValue});

  final Function callbackFunction;
  final Function dropDownFunction;
  final String languageDropdownValue;
  final bool isError;
  final List<Map<String, String>> languages;

  @override
  Widget build(BuildContext context) {
    return AlertDialog(
      contentPadding: EdgeInsets.fromLTRB(24, 24, 24, 14),
      title: Text('Confirm purchase'),
      content: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        mainAxisAlignment: MainAxisAlignment.start,
        mainAxisSize: MainAxisSize.min,
        children: [
          Text('Please select the guide language:'),
          Flexible(
            child: DropdownButtonFormField(
              isExpanded: false,
              isDense: true,
              dropdownColor: Colors.white,
              value: languageDropdownValue,
              hint: Text(
                'Preferred Language',
                style: TextStyle(color: Colors.grey),
              ),
              items: languages.map((map) {
                return DropdownMenuItem(
                  value: map['code'],
                  child: Text(
                    map['value'],
                    overflow: TextOverflow.ellipsis,
                  ),
                );
              }).toList(),
              onChanged: (String newValue) => dropDownFunction(newValue),
              decoration: InputDecoration(
                filled: true,
                fillColor: Colors.white,
                labelStyle: TextStyle(color: Colors.grey),
                hintStyle: TextStyle(color: Colors.grey),
                errorStyle: TextStyle(fontSize: 17.0),
                border: OutlineInputBorder(
                  borderRadius: BorderRadius.all(
                    Radius.circular(10),
                  ),
                  borderSide: BorderSide.none,
                ),
                focusedBorder: OutlineInputBorder(
                  borderSide: BorderSide(color: Colors.blue, width: 2),
                  borderRadius: BorderRadius.all(
                    Radius.circular(10),
                  ),
                ),
              ),
            ),
          ),
          isError
              ? Center(
                  child: Padding(
                    padding: const EdgeInsets.only(bottom: 8.0),
                    child: Text(
                      'Please select a language',
                      style: TextStyle(
                        color: Colors.red,
                      ),
                    ),
                  ),
                )
              : Container(),
          Padding(
            padding: const EdgeInsets.symmetric(vertical: 20.0),
            child: Text('Are you sure you want to purchase this audio guide?'),
          ),
          Row(
            crossAxisAlignment: CrossAxisAlignment.start,
            mainAxisAlignment: MainAxisAlignment.center,
            mainAxisSize: MainAxisSize.max,
            children: [
              ElevatedButton(
                onPressed: callbackFunction,
                child: Text('Yes'),
              ),
              SizedBox(
                width: 40,
              ),
              ElevatedButton(
                onPressed: () {
                  Navigator.of(context).pop(false);
                },
                child: Text('No'),
                style: ButtonStyle(
                  backgroundColor: MaterialStateProperty.all(Colors.blue),
                ),
              ),
            ],
          )
        ],
      ),
    );
  }
}

具有更多功能的解决方案(感谢 CbL)

import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'dart:io';

void main() {
  runApp(MaterialApp(home: MyApp()));
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  bool _isLoading = false;
  bool _downloaded = false;

  File cardImage;

  String _languageDropdownValue;

  bool isError = false;

  List<Map<String, String>> _languages = [
    {'code': 'en', 'value': 'English'},
    {'code': 'fr', 'value': 'French'},
  ];

  @override
  Widget build(BuildContext context) {
    return Material(
      child: Center(
        child: _downloaded
            ? IconButton(
                alignment: Alignment.center,
                padding: EdgeInsets.symmetric(horizontal: 0),
                icon: Icon(
                  Icons.open_in_new,
                  size: 45.0,
                  color: Colors.green,
                ),
                onPressed: () {
                  print('Open button pressed');
                })
            : _isLoading
                ? CircularProgressIndicator(
                    valueColor: AlwaysStoppedAnimation<Color>(Colors.green),
                  )
                : IconButton(
                    alignment: Alignment.center,
                    padding: EdgeInsets.symmetric(horizontal: 0),
                    icon: Icon(
                      Icons.download_rounded,
                      size: 45.0,
                      color: Colors.green,
                    ),
                    onPressed: () {
                      print('Download button pressed');
                      showDialog(
                        context: context,
                        builder: (context) {
                          return StatefulBuilder(
                              builder: (context, StateSetter setInnerState) {
                            return DownloadScreen(
                              callbackFunction: () =>
                                  alertDialogCallback(setInnerState),
                              dropDownFunction: (value) =>
                                  alertDialogDropdown(value, setInnerState),
                              isError: isError,
                              languages: _languages,
                              languageDropdownValue: _languageDropdownValue,
                            );
                          });
                        },
                      ).then((value) => _languageDropdownValue = null);
                    }),
      ),
    );
  }

  String alertDialogDropdown(String newValue, StateSetter setInnerState) {
    setInnerState(() {
      _languageDropdownValue = newValue;
      isError = false;
    });
    return newValue;
  }

  alertDialogCallback(StateSetter setInnerState) {
    if (_languageDropdownValue == null || _languageDropdownValue.isEmpty) {
      setInnerState(() {
        isError = true;
      });
    } else {
      setInnerState(() {
        isError = false;
        startDownload();
      });
    }
  }

  void startDownload() async {
    print('selected language is: $_languageDropdownValue');
    Navigator.pop(context);
    print('start download');
    setState(() => _downloaded = true);
  }
}

class DownloadScreen extends StatelessWidget {
  DownloadScreen(
      {@required this.callbackFunction,
      @required this.dropDownFunction,
      @required this.isError,
      @required this.languages,
      @required this.languageDropdownValue});

  final Function callbackFunction;
  final Function dropDownFunction;
  final String languageDropdownValue;
  final bool isError;
  final List<Map<String, String>> languages;

  @override
  Widget build(BuildContext context) {
    return AlertDialog(
      contentPadding: EdgeInsets.fromLTRB(24, 24, 24, 14),
      title: Text('Confirm purchase'),
      content: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        mainAxisAlignment: MainAxisAlignment.start,
        mainAxisSize: MainAxisSize.min,
        children: [
          Text('Please select the guide language:'),
          Flexible(
            child: DropdownButtonFormField(
              isExpanded: false,
              isDense: true,
              dropdownColor: Colors.white,
              value: languageDropdownValue,
              hint: Text(
                'Preferred Language',
                style: TextStyle(color: Colors.grey),
              ),
              items: languages.map((map) {
                return DropdownMenuItem(
                  value: map['code'],
                  child: Text(
                    map['value'],
                    overflow: TextOverflow.ellipsis,
                  ),
                );
              }).toList(),
              onChanged: (String newValue) => dropDownFunction(newValue),
              decoration: InputDecoration(
                filled: true,
                fillColor: Colors.white,
                labelStyle: TextStyle(color: Colors.grey),
                hintStyle: TextStyle(color: Colors.grey),
                errorStyle: TextStyle(fontSize: 17.0),
                border: OutlineInputBorder(
                  borderRadius: BorderRadius.all(
                    Radius.circular(10),
                  ),
                  borderSide: BorderSide.none,
                ),
                focusedBorder: OutlineInputBorder(
                  borderSide: BorderSide(color: Colors.blue, width: 2),
                  borderRadius: BorderRadius.all(
                    Radius.circular(10),
                  ),
                ),
              ),
            ),
          ),
          isError
              ? Center(
                  child: Padding(
                    padding: const EdgeInsets.only(bottom: 8.0),
                    child: Text(
                      'Please select a language',
                      style: TextStyle(
                        color: Colors.red,
                      ),
                    ),
                  ),
                )
              : Container(),
          Padding(
            padding: const EdgeInsets.symmetric(vertical: 20.0),
            child: Text('Are you sure you want to purchase this audio guide?'),
          ),
          Row(
            crossAxisAlignment: CrossAxisAlignment.start,
            mainAxisAlignment: MainAxisAlignment.center,
            mainAxisSize: MainAxisSize.max,
            children: [
              ElevatedButton(
                onPressed: callbackFunction,
                child: Text('Yes'),
              ),
              SizedBox(
                width: 40,
              ),
              ElevatedButton(
                onPressed: () {
                  Navigator.of(context).pop(false);
                },
                child: Text('No'),
                style: ButtonStyle(
                  backgroundColor: MaterialStateProperty.all(Colors.blue),
                ),
              ),
            ],
          )
        ],
      ),
    );
  }
}
4

2 回答 2

1

据我了解,主要问题是您正在调用 setState,设置 _MyAppState 的状态而不更新对话框的内部状态。

由于您使用的是 StatefulBuilder,因此您需要将 StateSetter 传递给值回调函数。

          showDialog(
                    context: context,
                    builder: (context) {
                      return StatefulBuilder(
                          builder: (context, StateSetter setInnerState) {
                        return DownloadScreen(
                          callbackFunction: () => alertDialogCallback(setInnerState),
                          dropDownFunction: (value) => alertDialogDropdown(value, setInnerState),
                          isError: isError,
                          languages: _languages,
                          languageDropdownValue: _languageDropdownValue,
                        );
                      });
                    },
                  );

然后使用 setInnerState 设置对话框的状态,当下拉选择更改时,下拉列表将更新。我还更新了 alertDialogCallback。同样的道理,如果你想更新对话框的状态,你必须调用 setInnerState 而不是 setState

String alertDialogDropdown(String newValue, StateSetter setInnerState) {
    setInnerState(() { //use this because calling setState here is calling _MyAppState's state
      _languageDropdownValue = newValue;
    });
    return newValue;
}


alertDialogCallback(StateSetter setInnerState) {
    if (_languageDropdownValue == null || _languageDropdownValue.isEmpty) {
      setInnerState(() {
        isError = true;
      });
    } else {
      setInnerState(() {
        isError = false;
        startDownload();
      });
    }
}
于 2021-04-30T08:19:28.173 回答
0

修复了您的问题:

import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'dart:io';

void main() {
  runApp(MaterialApp(home: MyApp()));
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  bool _isLoading = false;
  bool _downloaded = false;
  DownloadScreen downloadScreen;

  File cardImage;

  String _languageDropdownValue;

  bool isError = false;

  List<Map<String, String>> _languages = [
    {'code': 'en', 'value': 'English'},
    {'code': 'fr', 'value': 'French'},
  ];


  @override
  Widget build(BuildContext context) {
    return Material(
      child: Center(
        child: _downloaded
            ? IconButton(
            alignment: Alignment.center,
            padding: EdgeInsets.symmetric(horizontal: 0),
            icon: Icon(
              Icons.open_in_new,
              size: 45.0,
              color: Colors.green,
            ),
            onPressed: () {
              print('Open button pressed');
            })
            : _isLoading
            ? CircularProgressIndicator(
          valueColor: AlwaysStoppedAnimation<Color>(Colors.green),
        )
            : IconButton(
            alignment: Alignment.center,
            padding: EdgeInsets.symmetric(horizontal: 0),
            icon: Icon(
              Icons.download_rounded,
              size: 45.0,
              color: Colors.green,
            ),
            onPressed: () {
              print('Download button pressed');
              showDialog(
                context: context,
                builder: (context) {
                  return StatefulBuilder(
                      builder: (context, StateSetter setState) {
                        return downloadScreen = DownloadScreen(
                          alertDialogCallback,
                          alertDialogDropdown,
                          isError,
                          _languages,
                          _languageDropdownValue,
                        );
                      });
                },
              );
            }),
      ),
    );
  }

  String alertDialogDropdown(String newValue) {
    setState(() {
      _languageDropdownValue = newValue;
    });
    return newValue;
  }

  alertDialogCallback() {
    if (_languageDropdownValue == null || _languageDropdownValue.isEmpty) {
        isError = true;
        reloadDownloadScreen(true);
    } else {
      setState(() {
        isError = false;
        startDownload();
      });
    }
  }

  void startDownload() async {
    print('selected language is: $_languageDropdownValue');
    Navigator.pop(context);
    print('start download');
    setState(() => _downloaded = true);
  }

  void reloadDownloadScreen(bool isError) {
    downloadScreen.refresh(isError);
  }
}

class DownloadScreen extends StatefulWidget {
  final Function alertDialogCallback;
  final Function alertDialogDropdown;
  final bool isError;
  final List<Map<String, String>> languages;
  _DownloadScreen _downloadScreen;

  final String languageDropdownValue;
  void refresh(bool isError){
    _downloadScreen.refresh(isError);
  }

  DownloadScreen(this.alertDialogCallback, this.alertDialogDropdown, this.isError, this.languages, this.languageDropdownValue);
  @override
  _DownloadScreen createState(){
    _downloadScreen = _DownloadScreen(
        callbackFunction: alertDialogCallback,
        dropDownFunction: alertDialogDropdown,
        isError: isError,
        languages: languages,
        languageDropdownValue: languageDropdownValue
    );
    return _downloadScreen;
  }
}

class _DownloadScreen extends State<DownloadScreen> {
  _DownloadScreen(
      {@required this.callbackFunction,
        @required this.dropDownFunction,
        @required this.isError,
        @required this.languages,
        @required this.languageDropdownValue
      });

  final Function callbackFunction;
  final Function dropDownFunction;
  final String languageDropdownValue;
  bool isError;
  final List<Map<String, String>> languages;

  @override
  Widget build(BuildContext context) {
    return AlertDialog(
      contentPadding: EdgeInsets.fromLTRB(24, 24, 24, 14),
      title: Text('Confirm purchase'),
      content: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        mainAxisAlignment: MainAxisAlignment.start,
        mainAxisSize: MainAxisSize.min,
        children: [
          Text('Please select the guide language:'),
          Flexible(
            child: DropdownButtonFormField(
              isExpanded: false,
              isDense: true,
              dropdownColor: Colors.white,
              value: languageDropdownValue,
              hint: Text(
                'Preferred Language',
                style: TextStyle(color: Colors.grey),
              ),
              items: languages.map((map) {
                return DropdownMenuItem(
                  value: map['code'],
                  child: Text(
                    map['value'],
                    overflow: TextOverflow.ellipsis,
                  ),
                );
              }).toList(),
              onChanged: (String newValue) => dropDownFunction(newValue),
              decoration: InputDecoration(
                filled: true,
                fillColor: Colors.white,
                labelStyle: TextStyle(color: Colors.grey),
                hintStyle: TextStyle(color: Colors.grey),
                errorStyle: TextStyle(fontSize: 17.0),
                border: OutlineInputBorder(
                  borderRadius: BorderRadius.all(
                    Radius.circular(10),
                  ),
                  borderSide: BorderSide.none,
                ),
                focusedBorder: OutlineInputBorder(
                  borderSide: BorderSide(color: Colors.blue, width: 2),
                  borderRadius: BorderRadius.all(
                    Radius.circular(10),
                  ),
                ),
              ),
            ),
          ),
          isError
              ? Center(
            child: Padding(
              padding: const EdgeInsets.only(bottom: 8.0),
              child: Text(
                'Please select a language',
                style: TextStyle(
                  color: Colors.red,
                ),
              ),
            ),
          )
              : Container(),
          Padding(
            padding: const EdgeInsets.symmetric(vertical: 20.0),
            child: Text('Are you sure you want to purchase this audio guide?'),
          ),
          Row(
            crossAxisAlignment: CrossAxisAlignment.start,
            mainAxisAlignment: MainAxisAlignment.center,
            mainAxisSize: MainAxisSize.max,
            children: [
              ElevatedButton(
                onPressed: callbackFunction,
                child: Text('Yes'),
              ),
              SizedBox(
                width: 40,
              ),
              ElevatedButton(
                onPressed: () {
                  Navigator.of(context).pop(false);
                },
                child: Text('No'),
                style: ButtonStyle(
                  backgroundColor: MaterialStateProperty.all(Colors.blue),
                ),
              ),
            ],
          )
        ],
      ),
    );
  }

  void refresh(bool isError) {setState(() {
    this.isError = isError;
  });}
}

主要变化如下:

  • 改为DownloadScreen扩展StatefulWidget,在进程中创建其对应的_DownloadScreenextends State<DownloadScreen>
  • 您在alertDialogCallback()函数中使用的 setState 仅刷新_MyAppState类中的小部件,而不是来自_DownloadScreen. 为了实现这一点,创建了DownloadScreenin的私有实例_MyAppState。所以当你进入alertDialogCallback()isError设置为时true,你调用DownloadScreen,它会反过来调用_DownloadScreenwhich,它会调用setState刷新状态_DownloadScreen而不是_MyAppState

我不喜欢它,但有效。如果有更多 Flutter 经验的人对此有更好的工作流程,请随时评论或编辑。

于 2021-04-30T10:53:04.923 回答