Fixed Form With using FormKey

This commit is contained in:
Thomas Klein Langenhorst 2022-10-25 15:43:08 +02:00
parent 3ba62681c9
commit 51de713b0e
3 changed files with 112 additions and 97 deletions

View file

@ -37,59 +37,73 @@ class AddressFormExample extends StatelessWidget {
final _addressController = AddressController( final _addressController = AddressController(
zipCodeValidator: (text) { zipCodeValidator: (text) {
if (text != null) {
if (text.isEmpty) { if (text.isEmpty) {
return 'Can\'t be empty'; return 'Can\'t be empty';
} }
if (!RegExp(r'^[1-9][0-9]{3}\s?[a-zA-Z]{2}$').hasMatch(text)) { if (!RegExp(r'^[1-9][0-9]{3}\s?[a-zA-Z]{2}$').hasMatch(text)) {
return 'Invalid zipcode'; return 'Invalid zipcode';
} }
}
return null; return null;
}, },
housenumberValidator: (text) { housenumberValidator: (text) {
if (text != null) {
if (text.isEmpty) { if (text.isEmpty) {
return 'Can\'t be empty'; return 'Can\'t be empty';
} }
if (text.length >= 3 || int.tryParse(text) == null) { if (text.length >= 3 || int.tryParse(text) == null) {
return 'Invalid number'; return 'Invalid number';
} }
}
return null; return null;
}, },
suffixValidator: (text) { suffixValidator: (text) {
if (text != null) {
if (text.isNotEmpty && RegExp(r'/^[a-z]*$/').hasMatch(text)) { if (text.isNotEmpty && RegExp(r'/^[a-z]*$/').hasMatch(text)) {
return 'Invalid prefix'; return 'Invalid prefix';
} }
}
return null; return null;
}, },
streetValidator: (text) { streetValidator: (text) {
if (text != null) {
if (text.isEmpty) { if (text.isEmpty) {
return 'Can\'t be empty'; return 'Can\'t be empty';
} }
}
return null;
},
cityValidator: (text) {
if (text != null) {
if (text.isEmpty) {
return 'Can\'t be empty';
}
}
return null; return null;
}, },
onAutoComplete: (address) { onAutoComplete: (address) {
return address; return address;
}, },
cityValidator: (text) {
if (text.isEmpty) {
return 'Can\'t be empty';
}
return null;
},
); );
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar(), appBar: AppBar(
title: const Text('Address Form'),
),
body: Column( body: Column(
children: [ children: [
AddressForm( AddressForm(
onSubmit: (value) => value, controller: _addressController), onSubmit: (value) => value,
TextButton( controller: _addressController,
),
ElevatedButton(
onPressed: () { onPressed: () {
_addressController.validate(); _addressController.validate();
}, },
child: Text('Test'), child: const Text('Validate'),
) )
], ],
), ),

View file

@ -47,7 +47,8 @@ class AddressForm extends StatefulWidget {
class _AddressFormState extends State<AddressForm> { class _AddressFormState extends State<AddressForm> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Flexible( return Form(
key: widget._addressController._formKey,
child: Column( child: Column(
children: [ children: [
AddressFormTextField( AddressFormTextField(
@ -55,21 +56,23 @@ class _AddressFormState extends State<AddressForm> {
controller: widget._addressController._zipcodeController, controller: widget._addressController._zipcodeController,
fieldDecoration: widget.zipCodeDecoration, fieldDecoration: widget.zipCodeDecoration,
), ),
Flexible( Row(
child: Row(
children: [ children: [
AddressFormTextField( Expanded(
child: AddressFormTextField(
validator: widget._addressController.housenumberValidator, validator: widget._addressController.housenumberValidator,
controller: widget._addressController._housenumberController, controller: widget._addressController._housenumberController,
fieldDecoration: widget.housenumberDecoration, fieldDecoration: widget.housenumberDecoration,
), ),
AddressFormTextField( ),
Expanded(
child: AddressFormTextField(
validator: widget._addressController.suffixValidator, validator: widget._addressController.suffixValidator,
controller: widget._addressController._suffixController, controller: widget._addressController._suffixController,
fieldDecoration: widget.suffixDecoration, fieldDecoration: widget.suffixDecoration,
), ),
],
), ),
],
), ),
AddressFormTextField( AddressFormTextField(
validator: widget._addressController.streetValidator, validator: widget._addressController.streetValidator,
@ -88,37 +91,26 @@ class _AddressFormState extends State<AddressForm> {
} }
class AddressFormTextField extends StatelessWidget { class AddressFormTextField extends StatelessWidget {
AddressFormTextField( const AddressFormTextField({
{super.key, super.key,
required this.fieldDecoration, required this.fieldDecoration,
required this.controller, required this.controller,
required this.validator}); this.validator,
});
final TextEditingController controller; final TextEditingController controller;
final InputDecoration fieldDecoration; final InputDecoration fieldDecoration;
final String? Function(String) validator; final String? Function(String?)? validator;
late InputDecoration _addressFieldDecoration;
String? get _errorText => validator(controller.value.text);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ValueListenableBuilder<TextEditingValue>( return Container(
valueListenable: controller,
builder: (context, value, _) {
_addressFieldDecoration =
fieldDecoration.copyWith(errorText: _errorText);
return Flexible(
child: Container(
margin: const EdgeInsets.all(10), margin: const EdgeInsets.all(10),
child: TextField( child: TextFormField(
controller: controller, controller: controller,
decoration: _addressFieldDecoration, decoration: fieldDecoration,
validator: validator,
), ),
),
);
},
); );
} }
} }
@ -127,17 +119,19 @@ class AddressController extends ChangeNotifier {
/// An optional value to initialize the form field to, or null otherwise. /// An optional value to initialize the form field to, or null otherwise.
final AddressModel? initialValue; final AddressModel? initialValue;
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
/// When the form changes, the function passes the current `AddressModel` as an argument and gives the possibility to manipulate and return a `AddressModel`. /// When the form changes, the function passes the current `AddressModel` as an argument and gives the possibility to manipulate and return a `AddressModel`.
final FutureOr<AddressModel> Function(AddressModel)? onAutoComplete; final FutureOr<AddressModel> Function(AddressModel)? onAutoComplete;
AddressController( AddressController(
{this.initialValue, {this.initialValue,
this.onAutoComplete, this.onAutoComplete,
required this.zipCodeValidator, this.zipCodeValidator,
required this.housenumberValidator, this.housenumberValidator,
required this.suffixValidator, this.suffixValidator,
required this.streetValidator, this.streetValidator,
required this.cityValidator}) { this.cityValidator}) {
_model = initialValue ?? _model = initialValue ??
const AddressModel( const AddressModel(
zipcode: null, zipcode: null,
@ -156,11 +150,11 @@ class AddressController extends ChangeNotifier {
late AddressModel _model; late AddressModel _model;
final String? Function(String) zipCodeValidator; final String? Function(String?)? zipCodeValidator;
final String? Function(String) housenumberValidator; final String? Function(String?)? housenumberValidator;
final String? Function(String) suffixValidator; final String? Function(String?)? suffixValidator;
final String? Function(String) streetValidator; final String? Function(String?)? streetValidator;
final String? Function(String) cityValidator; final String? Function(String?)? cityValidator;
late final _zipcodeController = late final _zipcodeController =
TextEditingController(text: initialValue?.zipcode); TextEditingController(text: initialValue?.zipcode);
@ -175,15 +169,7 @@ class AddressController extends ChangeNotifier {
AddressModel get model => _model; AddressModel get model => _model;
bool validate() { bool validate() {
if (zipCodeValidator.call(_zipcodeController.text) == null && return _formKey.currentState!.validate();
streetValidator.call(_streetController.text) == null &&
housenumberValidator.call(_housenumberController.text) == null &&
suffixValidator.call(_suffixController.text) == null &&
cityValidator.call(_cityController.text) == null) {
return true;
} else {
return false;
}
} }
void _update() async { void _update() async {

View file

@ -4,8 +4,13 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_address_form/flutter_address_form.dart'; import 'package:flutter_address_form/flutter_address_form.dart';
void main() { void main() {
late RegExp zipcodeRegExp;
setUp(() {
// Testing with Dutch ZipCode
zipcodeRegExp = RegExp(r'^[1-9][0-9]{3}\s?[a-zA-Z]{2}$');
});
testWidgets('Render App with AddressForm Widget', (tester) async { testWidgets('Render App with AddressForm Widget', (tester) async {
final RegExp zipcodeRegExp = RegExp(r'^[1-9][0-9]{3}\s?[a-zA-Z]{2}$');
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
home: Scaffold( home: Scaffold(
@ -14,44 +19,54 @@ void main() {
onSubmit: (value) => value, onSubmit: (value) => value,
controller: AddressController( controller: AddressController(
zipCodeValidator: (text) { zipCodeValidator: (text) {
if (text != null) {
if (text.isEmpty) { if (text.isEmpty) {
return 'Can\'t be empty'; return 'Can\'t be empty';
} }
if (!zipcodeRegExp.hasMatch(text)) { if (!zipcodeRegExp.hasMatch(text)) {
return 'Invalid zipcode'; return 'Invalid zipcode';
} }
}
return null; return null;
}, },
housenumberValidator: (text) { housenumberValidator: (text) {
if (text != null) {
if (text.isEmpty) { if (text.isEmpty) {
return 'Can\'t be empty'; return 'Can\'t be empty';
} }
if (text.length >= 3 || int.tryParse(text) == null) { if (text.length >= 3 || int.tryParse(text) == null) {
return 'Invalid number'; return 'Invalid number';
} }
}
return null; return null;
}, },
suffixValidator: (text) { suffixValidator: (text) {
if (text != null) {
if (text.isNotEmpty && RegExp(r'/^[a-z]*$/').hasMatch(text)) { if (text.isNotEmpty && RegExp(r'/^[a-z]*$/').hasMatch(text)) {
return 'Invalid prefix'; return 'Invalid prefix';
} }
}
return null; return null;
}, },
streetValidator: (text) { streetValidator: (text) {
if (text != null) {
if (text.isEmpty) { if (text.isEmpty) {
return 'Can\'t be empty'; return 'Can\'t be empty';
} }
}
return null;
},
cityValidator: (text) {
if (text != null) {
if (text.isEmpty) {
return 'Can\'t be empty';
}
}
return null; return null;
}, },
onAutoComplete: (address) { onAutoComplete: (address) {
return address; return address;
}, },
cityValidator: (text) {
if (text.isEmpty) {
return 'Can\'t be empty';
}
return null;
},
), ),
), ),
), ),