Unverified Commit 949023b2 authored by Todd Volkert's avatar Todd Volkert Committed by GitHub

Add Form.onSaved (#30643)

When submitting data to a server, callers need a callback that will
get invoked after all the individual form fields are saved. If they
have a button that submits the form, they could just do this logic
in the click handler for the button (save the form, then submit to
the server), but if they have more ways than one to submit the form
(i.e. hitting enter while in a text form field), then it becomes
more convoluted and calls for a unified callback that will get
notified when the form is submitted.
parent cb78274c
...@@ -72,6 +72,7 @@ class Form extends StatefulWidget { ...@@ -72,6 +72,7 @@ class Form extends StatefulWidget {
this.autovalidate = false, this.autovalidate = false,
this.onWillPop, this.onWillPop,
this.onChanged, this.onChanged,
this.onSaved,
}) : assert(child != null), }) : assert(child != null),
super(key: key); super(key: key);
...@@ -118,6 +119,13 @@ class Form extends StatefulWidget { ...@@ -118,6 +119,13 @@ class Form extends StatefulWidget {
/// will rebuild. /// will rebuild.
final VoidCallback onChanged; final VoidCallback onChanged;
/// Called when the form is saved (after all the form fields have been saved).
///
/// See also:
///
/// * [FormState.save]
final VoidCallback onSaved;
@override @override
FormState createState() => FormState(); FormState createState() => FormState();
} }
...@@ -172,6 +180,8 @@ class FormState extends State<Form> { ...@@ -172,6 +180,8 @@ class FormState extends State<Form> {
void save() { void save() {
for (FormFieldState<dynamic> field in _fields) for (FormFieldState<dynamic> field in _fields)
field.save(); field.save();
if (widget.onSaved != null)
widget.onSaved();
} }
/// Resets every [FormField] that is a descendant of this [Form] back to its /// Resets every [FormField] that is a descendant of this [Form] back to its
......
...@@ -6,9 +6,10 @@ import 'package:flutter_test/flutter_test.dart'; ...@@ -6,9 +6,10 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
void main() { void main() {
testWidgets('onSaved callback is called', (WidgetTester tester) async { testWidgets('onSaved callbacks are called', (WidgetTester tester) async {
final GlobalKey<FormState> formKey = GlobalKey<FormState>(); final GlobalKey<FormState> formKey = GlobalKey<FormState>();
String fieldValue; String fieldValue;
bool fieldModifiedSinceFormOnSaved;
Widget builder() { Widget builder() {
return MediaQuery( return MediaQuery(
...@@ -19,8 +20,12 @@ void main() { ...@@ -19,8 +20,12 @@ void main() {
child: Material( child: Material(
child: Form( child: Form(
key: formKey, key: formKey,
onSaved: () { fieldModifiedSinceFormOnSaved = false; },
child: TextFormField( child: TextFormField(
onSaved: (String value) { fieldValue = value; }, onSaved: (String value) {
fieldValue = value;
fieldModifiedSinceFormOnSaved = true;
},
), ),
), ),
), ),
...@@ -38,6 +43,7 @@ void main() { ...@@ -38,6 +43,7 @@ void main() {
formKey.currentState.save(); formKey.currentState.save();
// pump'ing is unnecessary because callback happens regardless of frames // pump'ing is unnecessary because callback happens regardless of frames
expect(fieldValue, equals(testValue)); expect(fieldValue, equals(testValue));
expect(fieldModifiedSinceFormOnSaved, isFalse);
} }
await checkText('Test'); await checkText('Test');
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment