问题描述
我有一个带有 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),],)
],);
}
}
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 经验的人对此有更好的工作流程,请随时发表评论或编辑。