I am building a form using the "Flutter Form Builder" package 4.0.2.
Most form questions are on a screen, but the last two are on successive* popups created with the RFlutter Alert package 1.1.0
* By "successive" I mean there is a "Post" button on the bottom of the main form screen (see first image immediately below). When pressed, an RFlutter alert popup appears (see 2nd screenshot image below), with the second-to-last FormBuilderRadioGroup question ("q11"). When the "Next" button is pressed, yet another RFlutter alert popup appears, on which the last FormBuilderRadioGroup question appears ("q12"). The entire Flutter Form Builder form submits when the "Finish Posting" button here is pressed.
The user interface works well, but the problem is nothing from the questions in the alert popups (Q11 and Q12) gets submitted with the form (see console snapshot below - q1, q2 and q3 also have problems, but that's not what I'm asking about here).
Here is the code (skipped irrelevant parts, for length):
// This is the stateful widget that the main application instantiates, per https://api.flutter.dev/flutter/widgets/Form-class.html
class SandboxWriteReviewScreen extends StatefulWidget {
// BEGIN code from material_tag_editor
final String title = 'Material Tag Editor Demo';
// END code from material_tag_editor
@override
_SandboxWriteReviewScreenState createState() =>
_SandboxWriteReviewScreenState();
}
// This is the private State class that goes with WriteReviewScreen
class _SandboxWriteReviewScreenState extends State<SandboxWriteReviewScreen> {
var data;
AutovalidateMode autovalidateMode = AutovalidateMode.always;
bool readOnly = false;
bool showSegmentedControl = true;
//final _newFormbuilderKey = GlobalKey<FormState>();
final _newnewFormbuilderKey = GlobalKey<FormBuilderState>();
// above "GlobalKey" lets us generate a unique, app-wide ID that we can associate with our form, per https://fluttercrashcourse.com/blog/realistic-forms-part1
final ValueChanged _onChanged = (val) => print(val);
// BEGIN related to FormBuilderTextField in form below
final _ageController = TextEditingController(text: '45');
bool _ageHasError = false;
// END related to FormBuilderTextField in form below
String qEleven;
String qTwelve;
// BEGIN code from material_tag_editor
List<String> qOne = [];
final FocusNode _focusNode = FocusNode();
onDelete(index) {
setState(() {
qOne.removeAt(index);
});
}
// below = reiteration for cons
List<String> qThree = [];
//final FocusNode _focusNode = FocusNode();
uponDelete(index) {
// NOTE: "uponDelete" for cons vs. "onDelete" for pros
setState(() {
qThree.removeAt(index);
});
}
// END code from material_tag_editor
//final _user = User();
List<bool> isSelected;
int starIconColor =
0xffFFB900; // was 0xffFFB900; 0xffD49428 is from this image: https://images.liveauctioneers.com/houses/logos/lg/bartonsauction550_large.jpg?auto=webp&format=pjpg&width=140
@override
void initState() {
//isSelected = [true, false];
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
Cont'd (skipped AppBar):
body: SingleChildScrollView(
child: Container(
child: Builder(
builder: (context) => FormBuilder(
// was "builder: (context) => Form("
key: _newnewFormbuilderKey,
initialValue: {
'date': DateTime.now(),
},
child: Padding(
padding: const EdgeInsets.all(14.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
SizedBox(
height: 12.0,
),
RichText(
text: TextSpan(
style: TextStyle(
color: Colors.blue,
),
children: <TextSpan>[
TextSpan(
text:
'Q1 via TagEditor', // was 'What are 3 good or positive things about the house, property or neighborhood?', // [ 1 ]
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16.0,
),
),
TextSpan(
text: ' (optional)',
style: TextStyle(
fontWeight: FontWeight.normal,
fontStyle: FontStyle.italic,
fontSize: 14.0,
color: Colors.black54,
), // was 'misleading or inaccurate?',
),
],
),
),
// BEGIN code from material_tag_editor
Padding(
padding: const EdgeInsets.only(top: 16.0),
child: TagEditor(
length: qOne.length,
delimiters: [
','
], // was delimiters: [',', ' '], Also tried "return" ('\u2386',) and '\u{2386}'
hasAddButton: true,
textInputAction: TextInputAction
.next, // moves user from one field to the next!!!!
autofocus: false,
maxLines: 1,
// focusedBorder: OutlineInputBorder(
// borderSide: BorderSide(color: Colors.lightBlue),
// borderRadius: BorderRadius.circular(20.0),
// ),
inputDecoration: const InputDecoration(
// below was "border: InputBorder.none,"
isDense: true,
border: OutlineInputBorder(
borderRadius: const BorderRadius.all(
const Radius.circular(20.0),
),
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: Colors.lightBlue),
borderRadius: const BorderRadius.all(
const Radius.circular(20.0),
),
// above is per https://github.com/flutter/flutter/issues/5191
),
labelText: 'separate, with, commas',
labelStyle: TextStyle(
fontStyle: FontStyle.italic,
backgroundColor:
Color(0x65dffd02), // was Color(0xffDDFDFC),
color: Colors.black87, // was Color(0xffD82E6D),
fontSize: 14,
),
),
onTagChanged: (value) {
setState(() {
qOne.add(value);
});
},
tagBuilder: (context, index) => _Chip(
index: index,
label: qOne[index],
onDeleted: onDelete,
),
),
),
// END code from material_tag_editor
SuperDivider(),
Cont'd (skipped Q2 - Q9):
SuperDivider(),
RichText(
text: TextSpan(
style: TextStyle(
color: Colors.blue,
),
children: <TextSpan>[
TextSpan(
text:
'Q10 - via FormBuilder\'s FormBuilderTextField', // [ 9 ]
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16.0,
),
),
TextSpan(
text: ' (optional)',
style: TextStyle(
fontWeight: FontWeight.normal,
fontStyle: FontStyle.italic,
fontSize: 14.0,
color: Colors.black54,
), // was 'misleading or inaccurate?',
),
],
),
),
GavTextField(
maxCharLength: 1200,
fieldAttribute: 'qTen',
fieldLabelText:
'Be honest & kind.', // was 'Be honest, but kind.',
),
SuperDivider(),
Padding(
padding: const EdgeInsets.symmetric(vertical: 16.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
style: ElevatedButton.styleFrom(
primary: Colors.purple,
padding: EdgeInsets.symmetric(
horizontal: 50, vertical: 20),
textStyle: TextStyle(
fontSize: 20, fontWeight: FontWeight.bold)),
onPressed: () {
Alert(
context: context,
style: alertStyle,
title: 'Two quick things...',
desc: ' ',
content: Column(
children: <Widget>[
RichText(
text: TextSpan(
style: TextStyle(
color: Colors.blue,
),
children: <TextSpan>[
TextSpan(
text:
'Q11 - via FormBuilder\'s FormBuilderRadioGroup', // [ 10 ]
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16.0,
),
),
],
),
),
FormBuilderRadioGroup(
name: 'qEleven',
decoration: const InputDecoration(
border: InputBorder.none,
),
orientation: OptionsOrientation.vertical,
onChanged: _onChanged,
options: [
FormBuilderFieldOption(
value: 'N',
child: RichText(
text: TextSpan(
style: TextStyle(
color: Colors.black,
),
children: <TextSpan>[
TextSpan(
text: 'No', // [ 10 ]
style: TextStyle(
fontWeight: FontWeight.w500,
fontSize: 16.0,
),
),
],
),
),
),
FormBuilderFieldOption(
value: 'YesSometimes',
child: RichText(
text: TextSpan(
style: TextStyle(
color: Colors.black,
),
children: <TextSpan>[
TextSpan(
text:
'Yes, sometimes', // [ 10 ]
style: TextStyle(
fontWeight: FontWeight.w500,
fontSize: 16.0,
),
),
],
),
),
),
FormBuilderFieldOption(
value: 'YesDaily',
child: RichText(
text: TextSpan(
style: TextStyle(
color: Colors.black,
),
children: <TextSpan>[
TextSpan(
text: 'Yes, daily', // [ 10 ]
style: TextStyle(
fontWeight: FontWeight.w500,
fontSize: 16.0,
),
),
],
),
),
),
],
),
],
),
buttons: [
DialogButton(
onPressed: () {
Alert(
context: context,
style: alertStyle,
title: 'And lastly:',
desc: '',
content: Column(
children: <Widget>[
Row(
mainAxisAlignment:
MainAxisAlignment.start,
children: [
RichText(
text: TextSpan(
style: TextStyle(
color: Colors.blue,
),
children: <TextSpan>[
TextSpan(
text:
' Q12 - via FormBuilder\'s', // [ 10 ]
And finally (skipped Q12):
buttons: [
DialogButton(
// BEGIN submit form when button pressed per 4.0.2 Readme and video https://www.youtube.com/watch?v=7FBELQq808M
onPressed: () {
_newnewFormbuilderKey
.currentState
.save();
if (_newnewFormbuilderKey
.currentState
.validate()) {
print(_newnewFormbuilderKey
.currentState.value);
print(
' >>> Q1\'s value via separate print: {$qOne}',
);
print(
' >>> Q3\'s value via separate print: {$qThree}',
);
print(
' >>> Q11\'s value via separate print: {$qEleven}',
);
print(
' >>> Q12\'s value via separate print: {$qTwelve}',
);
//print(_newFormbuilderKey.currentState.privacyChoice.value);
} else {
print("validation failed");
}
},
// END submit form when button pressed per 4.0.2 Readme and video https://www.youtube.com/watch?v=7FBELQq808M
child: Text(
"Finish Posting",
style: TextStyle(
color: Colors.white,
fontSize: 20,
),
),
)
]).show();
},
child: Text(
"Next >",
style: TextStyle(
color: Colors.white,
fontSize: 20,
),
),
)
],
).show();
},
child: Text(
'Post',
style: TextStyle(
color: Colors.white,
fontSize: 20,
),
),
),
],
),
),
Thank you!
The flutter form builder package automatically fetches the ancestor Form to add field values in theirs. But since your form fields are now in dialog, they are not guaranteed to be inside the parent layout from which the dialog was invoked.
Ref from flutter documentation (Link)
This function takes a builder which typically builds a Dialog widget.
Content below the dialog is dimmed with a ModalBarrier. The widget returned by the
builder does not share a context with the location that showDialog is originally
called from. Use a StatefulBuilder or a custom StatefulWidget if the dialog needs
to update dynamically.
You can instead save the value of these fields in the state of your stateful widget and them combine all values later for future operations. Since you are already saving them in a variable qEleven
and qTwelve
you can use it later.
Here is the snippet of code doing this for qEleven (compare with this section of code in the question above):
FormBuilderRadioGroup(
name: 'qEleven',
decoration: const InputDecoration(
border: InputBorder.none,
),
orientation: OptionsOrientation.vertical,
onChanged: (val) {
print(val);
qEleven = val;
},
options: [
FormBuilderFieldOption(
value: 'N',
child: RichText(
text: TextSpan(
style: TextStyle(
color: Colors.black,
),
children: <TextSpan>[
TextSpan(
text: 'No', // [ 10 ]
style: TextStyle(
fontWeight: FontWeight.w500,
fontSize: 16.0,
),
),
],
),
),
),
And here is a screenshot that highlights the differences between to code in the question (left) and the code based on the answer (right):
Another thing that can be done, is to modify form builder package to also accept state, but it might break in case you are accessing an unmounted state.