From 51de713b0eff19ba1a5ce7bb3138383dcb05267d Mon Sep 17 00:00:00 2001 From: Thomas Klein Langenhorst Date: Tue, 25 Oct 2022 15:43:08 +0200 Subject: [PATCH] Fixed Form With using FormKey --- example/lib/main.dart | 62 ++++++++++++-------- lib/src/address_form.dart | 90 ++++++++++++----------------- test/flutter_address_form_test.dart | 57 +++++++++++------- 3 files changed, 112 insertions(+), 97 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index 2bf2b41..d0d3c95 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -37,59 +37,73 @@ class AddressFormExample extends StatelessWidget { final _addressController = AddressController( zipCodeValidator: (text) { - if (text.isEmpty) { - return 'Can\'t be empty'; - } - if (!RegExp(r'^[1-9][0-9]{3}\s?[a-zA-Z]{2}$').hasMatch(text)) { - return 'Invalid zipcode'; + if (text != null) { + if (text.isEmpty) { + return 'Can\'t be empty'; + } + if (!RegExp(r'^[1-9][0-9]{3}\s?[a-zA-Z]{2}$').hasMatch(text)) { + return 'Invalid zipcode'; + } } return null; }, housenumberValidator: (text) { - if (text.isEmpty) { - return 'Can\'t be empty'; - } - if (text.length >= 3 || int.tryParse(text) == null) { - return 'Invalid number'; + if (text != null) { + if (text.isEmpty) { + return 'Can\'t be empty'; + } + if (text.length >= 3 || int.tryParse(text) == null) { + return 'Invalid number'; + } } return null; }, suffixValidator: (text) { - if (text.isNotEmpty && RegExp(r'/^[a-z]*$/').hasMatch(text)) { - return 'Invalid prefix'; + if (text != null) { + if (text.isNotEmpty && RegExp(r'/^[a-z]*$/').hasMatch(text)) { + return 'Invalid prefix'; + } } return null; }, streetValidator: (text) { - if (text.isEmpty) { - return 'Can\'t be empty'; + if (text != null) { + if (text.isEmpty) { + return 'Can\'t be empty'; + } + } + return null; + }, + cityValidator: (text) { + if (text != null) { + if (text.isEmpty) { + return 'Can\'t be empty'; + } } return null; }, onAutoComplete: (address) { return address; }, - cityValidator: (text) { - if (text.isEmpty) { - return 'Can\'t be empty'; - } - return null; - }, ); @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(), + appBar: AppBar( + title: const Text('Address Form'), + ), body: Column( children: [ AddressForm( - onSubmit: (value) => value, controller: _addressController), - TextButton( + onSubmit: (value) => value, + controller: _addressController, + ), + ElevatedButton( onPressed: () { _addressController.validate(); }, - child: Text('Test'), + child: const Text('Validate'), ) ], ), diff --git a/lib/src/address_form.dart b/lib/src/address_form.dart index 5621ae4..5b5d10b 100644 --- a/lib/src/address_form.dart +++ b/lib/src/address_form.dart @@ -47,7 +47,8 @@ class AddressForm extends StatefulWidget { class _AddressFormState extends State { @override Widget build(BuildContext context) { - return Flexible( + return Form( + key: widget._addressController._formKey, child: Column( children: [ AddressFormTextField( @@ -55,21 +56,23 @@ class _AddressFormState extends State { controller: widget._addressController._zipcodeController, fieldDecoration: widget.zipCodeDecoration, ), - Flexible( - child: Row( - children: [ - AddressFormTextField( + Row( + children: [ + Expanded( + child: AddressFormTextField( validator: widget._addressController.housenumberValidator, controller: widget._addressController._housenumberController, fieldDecoration: widget.housenumberDecoration, ), - AddressFormTextField( + ), + Expanded( + child: AddressFormTextField( validator: widget._addressController.suffixValidator, controller: widget._addressController._suffixController, fieldDecoration: widget.suffixDecoration, ), - ], - ), + ), + ], ), AddressFormTextField( validator: widget._addressController.streetValidator, @@ -88,37 +91,26 @@ class _AddressFormState extends State { } class AddressFormTextField extends StatelessWidget { - AddressFormTextField( - {super.key, - required this.fieldDecoration, - required this.controller, - required this.validator}); + const AddressFormTextField({ + super.key, + required this.fieldDecoration, + required this.controller, + this.validator, + }); final TextEditingController controller; final InputDecoration fieldDecoration; - final String? Function(String) validator; - - late InputDecoration _addressFieldDecoration; - - String? get _errorText => validator(controller.value.text); + final String? Function(String?)? validator; @override Widget build(BuildContext context) { - return ValueListenableBuilder( - valueListenable: controller, - builder: (context, value, _) { - _addressFieldDecoration = - fieldDecoration.copyWith(errorText: _errorText); - return Flexible( - child: Container( - margin: const EdgeInsets.all(10), - child: TextField( - controller: controller, - decoration: _addressFieldDecoration, - ), - ), - ); - }, + return Container( + margin: const EdgeInsets.all(10), + child: TextFormField( + controller: controller, + decoration: fieldDecoration, + validator: validator, + ), ); } } @@ -127,17 +119,19 @@ class AddressController extends ChangeNotifier { /// An optional value to initialize the form field to, or null otherwise. final AddressModel? initialValue; + final GlobalKey _formKey = GlobalKey(); + /// 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 Function(AddressModel)? onAutoComplete; AddressController( {this.initialValue, this.onAutoComplete, - required this.zipCodeValidator, - required this.housenumberValidator, - required this.suffixValidator, - required this.streetValidator, - required this.cityValidator}) { + this.zipCodeValidator, + this.housenumberValidator, + this.suffixValidator, + this.streetValidator, + this.cityValidator}) { _model = initialValue ?? const AddressModel( zipcode: null, @@ -156,11 +150,11 @@ class AddressController extends ChangeNotifier { late AddressModel _model; - final String? Function(String) zipCodeValidator; - final String? Function(String) housenumberValidator; - final String? Function(String) suffixValidator; - final String? Function(String) streetValidator; - final String? Function(String) cityValidator; + final String? Function(String?)? zipCodeValidator; + final String? Function(String?)? housenumberValidator; + final String? Function(String?)? suffixValidator; + final String? Function(String?)? streetValidator; + final String? Function(String?)? cityValidator; late final _zipcodeController = TextEditingController(text: initialValue?.zipcode); @@ -175,15 +169,7 @@ class AddressController extends ChangeNotifier { AddressModel get model => _model; bool validate() { - if (zipCodeValidator.call(_zipcodeController.text) == null && - 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; - } + return _formKey.currentState!.validate(); } void _update() async { diff --git a/test/flutter_address_form_test.dart b/test/flutter_address_form_test.dart index 71896d2..202383a 100644 --- a/test/flutter_address_form_test.dart +++ b/test/flutter_address_form_test.dart @@ -4,8 +4,13 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_address_form/flutter_address_form.dart'; 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 { - final RegExp zipcodeRegExp = RegExp(r'^[1-9][0-9]{3}\s?[a-zA-Z]{2}$'); await tester.pumpWidget( MaterialApp( home: Scaffold( @@ -14,44 +19,54 @@ void main() { onSubmit: (value) => value, controller: AddressController( zipCodeValidator: (text) { - if (text.isEmpty) { - return 'Can\'t be empty'; - } - if (!zipcodeRegExp.hasMatch(text)) { - return 'Invalid zipcode'; + if (text != null) { + if (text.isEmpty) { + return 'Can\'t be empty'; + } + if (!zipcodeRegExp.hasMatch(text)) { + return 'Invalid zipcode'; + } } return null; }, housenumberValidator: (text) { - if (text.isEmpty) { - return 'Can\'t be empty'; - } - if (text.length >= 3 || int.tryParse(text) == null) { - return 'Invalid number'; + if (text != null) { + if (text.isEmpty) { + return 'Can\'t be empty'; + } + if (text.length >= 3 || int.tryParse(text) == null) { + return 'Invalid number'; + } } return null; }, suffixValidator: (text) { - if (text.isNotEmpty && RegExp(r'/^[a-z]*$/').hasMatch(text)) { - return 'Invalid prefix'; + if (text != null) { + if (text.isNotEmpty && RegExp(r'/^[a-z]*$/').hasMatch(text)) { + return 'Invalid prefix'; + } } return null; }, streetValidator: (text) { - if (text.isEmpty) { - return 'Can\'t be empty'; + if (text != null) { + if (text.isEmpty) { + return 'Can\'t be empty'; + } + } + return null; + }, + cityValidator: (text) { + if (text != null) { + if (text.isEmpty) { + return 'Can\'t be empty'; + } } return null; }, onAutoComplete: (address) { return address; }, - cityValidator: (text) { - if (text.isEmpty) { - return 'Can\'t be empty'; - } - return null; - }, ), ), ),