I have a parent StatefulWidget with a StatelessWidget child, which returns an AlertDialog box. The StatelessWidget is built from a builder in the StatefulWidget when the "green download" button is pressed. (Upon confirmation in the AlertDialog the full code would then get and store the data).
Within the AlertDialog box is a DropdownButtonFormField. I've built in my own validation and error message to ensure the associated value is not null. (I couldn't get the built-in validation of the DropdownButtonFormField to show the whole error message without it being cut-off).
I can't understand why my AlertDialog isn't being updated to show the error message following the callback's SetState, even with a StatefulBuilder (which I might not be using correctly). I have tried using a StatefulWidget
Current Output: When you press the yes button in the AlertDialog, but the dropdown value is null or empty, the AlertDialog does not update to show the Centre widget in the AlertDialog that displays the error message. If you pop the AlertDialog and reopen it, it displays the error message.
Desired Output When you press the the yes button in the AlertDialog, but the dropdown value is null or empty, the AlertDialog updates to show the Centre widget in the AlertDialog that displays the error message.
Please can you help?
Useable code to recreate below:
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),
),
),
],
)
],
),
);
}
}
Solution (thanks to CbL) with a bit more functionality
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),
),
),
],
)
],
),
);
}
}