父级中的 SetState 未更新 AlertDialog 的 Flutter 构建器

问题描述

我有一个带有 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,icon: Icon(
                      Icons.download_rounded,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,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,StateSetter setInnerState) {
                            return DownloadScreen(
                              callbackFunction: () =>
                                  alertDialogCallback(setInnerState),dropDownFunction: (value) =>
                                  alertDialogDropdown(value,setInnerState),).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,);
  }
}

解决方法

根据我的理解,主要问题是您正在调用 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();
      });
    }
}
,

已解决您的问题:

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,icon: Icon(
              Icons.download_rounded,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,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,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,在此过程中创建其对应的 _DownloadScreen 类,其中 extends State<DownloadScreen>
  • 您在 alertDialogCallback() 函数中使用的 setState 只会刷新 _MyAppState 类中的小部件,而不是 _DownloadScreen 中的小部件。为了实现这一点,在 DownloadScreen 中创建了 _MyAppState 的私有实例。因此,当您输入 alertDialogCallback() 并且 isError 设置为 true 时,您调用 DownloadScreen,后者将依次调用 _DownloadScreen,后者将调用 { {1}} 刷新 setState 而不是 _DownloadScreen 的状态。

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

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...