父级中的 SetState 未更新 AlertDialog 的 Flutter 构建器
Flutter builder for AlertDialog not being updated by SetState in parent
我有一个父级 StatefulWidget 和一个 StatelessWidget 子级,returns 一个 AlertDialog 框。当按下“绿色下载”按钮时,StatelessWidget 是从 StatefulWidget 中的构建器构建的。 (在 AlertDialog 中确认后,完整代码将获取并存储数据)。
AlertDialog 框内有一个 DropdownButtonFormField。我内置了自己的验证和错误消息,以确保关联值不为空。 (我无法获得 DropdownButtonFormField 的内置验证来显示整个错误消息而不被截断)。
我不明白为什么我的 AlertDialog 没有更新以显示回调的 SetState 之后的错误消息,即使使用了 StatefulBuilder(我可能没有正确使用它)。我试过使用 StatefulWidget
当前输出:
当您在 AlertDialog 中按下是按钮,但下拉列表值为 null 或空时,AlertDialog 不会更新以在显示错误消息的 AlertDialog 中显示中心小部件。如果弹出 AlertDialog 并重新打开它,它会显示错误消息。
期望输出
当您在 AlertDialog 中按下“是”按钮,但下拉列表值为 null 或空时,AlertDialog 会更新以在显示错误消息的 AlertDialog 中显示中心小部件。
你能帮忙吗?
下面重新创建的可用代码:
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),
),
),
],
)
],
),
);
}
}
根据我的理解,主要问题是您正在调用 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,
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,在进程中创建其对应的 _DownloadScreen
class 其中 extends State<DownloadScreen>
- 您在
alertDialogCallback()
函数中使用的 setState 仅刷新 _MyAppState
class 中的小部件,而不刷新 _DownloadScreen
中的小部件。为了实现这一点,在 _MyAppState
中创建了 DownloadScreen
的私有实例。因此,当您输入 alertDialogCallback()
并且 isError
设置为 true
时,您调用 DownloadScreen
,后者将依次调用 _DownloadScreen
,后者将调用 setState
刷新 _DownloadScreen
的状态而不是 _MyAppState
.
我不喜欢,但很管用。如果有更多 flutter 经验的人对此有更好的工作流程,请随时发表评论或编辑。
我有一个父级 StatefulWidget 和一个 StatelessWidget 子级,returns 一个 AlertDialog 框。当按下“绿色下载”按钮时,StatelessWidget 是从 StatefulWidget 中的构建器构建的。 (在 AlertDialog 中确认后,完整代码将获取并存储数据)。
AlertDialog 框内有一个 DropdownButtonFormField。我内置了自己的验证和错误消息,以确保关联值不为空。 (我无法获得 DropdownButtonFormField 的内置验证来显示整个错误消息而不被截断)。
我不明白为什么我的 AlertDialog 没有更新以显示回调的 SetState 之后的错误消息,即使使用了 StatefulBuilder(我可能没有正确使用它)。我试过使用 StatefulWidget
当前输出: 当您在 AlertDialog 中按下是按钮,但下拉列表值为 null 或空时,AlertDialog 不会更新以在显示错误消息的 AlertDialog 中显示中心小部件。如果弹出 AlertDialog 并重新打开它,它会显示错误消息。
期望输出 当您在 AlertDialog 中按下“是”按钮,但下拉列表值为 null 或空时,AlertDialog 会更新以在显示错误消息的 AlertDialog 中显示中心小部件。
你能帮忙吗?
下面重新创建的可用代码:
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),
),
),
],
)
],
),
);
}
}
根据我的理解,主要问题是您正在调用 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,
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,在进程中创建其对应的_DownloadScreen
class 其中extends State<DownloadScreen>
- 您在
alertDialogCallback()
函数中使用的 setState 仅刷新_MyAppState
class 中的小部件,而不刷新_DownloadScreen
中的小部件。为了实现这一点,在_MyAppState
中创建了DownloadScreen
的私有实例。因此,当您输入alertDialogCallback()
并且isError
设置为true
时,您调用DownloadScreen
,后者将依次调用_DownloadScreen
,后者将调用setState
刷新_DownloadScreen
的状态而不是_MyAppState
.
我不喜欢,但很管用。如果有更多 flutter 经验的人对此有更好的工作流程,请随时发表评论或编辑。