Functionally working Adressform

This commit is contained in:
Thomas Klein Langenhorst 2022-10-19 11:52:57 +02:00
parent e00b961414
commit 1107729c79
5 changed files with 229 additions and 147 deletions

View file

@ -25,19 +25,61 @@ class MyApp extends StatelessWidget {
// is not restarted. // is not restarted.
primarySwatch: Colors.blue, primarySwatch: Colors.blue,
), ),
home: const AddressFormExample(), home: AddressFormExample(),
); );
} }
} }
class AddressFormExample extends StatelessWidget { class AddressFormExample extends StatelessWidget {
const AddressFormExample({Key? key}) : super(key: key); AddressFormExample({Key? key}) : super(key: key);
final RegExp zipcodeRegExp = RegExp(r'^[1-9][0-9]{3}\s?[a-zA-Z]{2}$');
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar(), appBar: AppBar(),
body: AddressForm(), body: AddressForm(
zipCodeValidator: (text) {
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';
}
return null;
},
suffixValidator: (text) {
if (text.isNotEmpty && RegExp(r'/^[a-z]*$/').hasMatch(text)) {
return 'Invalid prefix';
}
return null;
},
streetValidator: (text) {
if (text.isEmpty) {
return 'Can\'t be empty';
}
return null;
},
cityValidator: (text) {
if (text.isEmpty) {
return 'Can\'t be empty';
}
return null;
},
controller: AddressController(onAutoComplete: (address) {
return address;
}),
),
); );
} }
} }

View file

@ -1,40 +0,0 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_address_form/src/models/address.dart';
class AddressController extends ChangeNotifier {
Address address = const Address();
final TextEditingController zipcode = TextEditingController();
final TextEditingController houseNumber = TextEditingController();
final TextEditingController suffix = TextEditingController();
final TextEditingController street = TextEditingController();
final TextEditingController city = TextEditingController();
final Function()? onChangeInputCallback;
AddressController(this.onChangeInputCallback);
Address get getAddress => address;
void setAddress(
String zipcode,
String street,
int housenumber,
String suffix,
String city,
) {
address = address.copyWith(
zipcode: zipcode,
street: street,
housenumber: housenumber,
suffix: suffix,
city: city,
);
notifyListeners();
}
void onChangeInput() {
onChangeInputCallback?.call();
}
}

View file

@ -1,111 +1,83 @@
import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_address_form/src/address_controller.dart';
import 'package:flutter_address_form/src/models/address.dart';
class AddressForm extends StatefulWidget { import 'package:flutter_address_form/src/models/address_model.dart';
const AddressForm({Key? key}) : super(key: key);
@override class AddressForm extends StatelessWidget {
State<AddressForm> createState() => _AddressFormState(); AddressForm({
Key? key,
this.zipCodeLabel = const Text('Zipcode'),
this.housenumberLabel = const Text('Housenumber'),
this.suffixLabel = const Text('Suffix'),
this.streetLabel = const Text('Street'),
this.cityLabel = const Text('City'),
required this.zipCodeValidator,
required this.housenumberValidator,
required this.suffixValidator,
required this.streetValidator,
required this.cityValidator,
AddressController? controller,
}) {
_addressController =
controller ?? AddressController(onAutoComplete: (model) => model);
} }
class _AddressFormState extends State<AddressForm> { final Widget zipCodeLabel;
final AddressController _addressController = AddressController(() {}); final Widget housenumberLabel;
final Widget suffixLabel;
final Widget streetLabel;
final Widget cityLabel;
final RegExp _zipcodeRegExp = RegExp(r'^[1-9][0-9]{3}\s?[a-zA-Z]{2}$'); 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;
@override late final AddressController _addressController;
void initState() {
_addressController;
super.initState();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Column( return Column(
children: [ children: [
AddressFormTextField( AddressFormTextField(
controller: _addressController.zipcode, controller: _addressController._zipcodeController,
validator: (text) { validator: zipCodeValidator,
if (text.isEmpty) { label: zipCodeLabel,
return 'Can\'t be empty';
}
if (!_zipcodeRegExp.hasMatch(text)) {
return 'Invalid zipcode';
}
return null;
},
label: const Text('Postcode'),
), ),
Flexible( Flexible(
child: Row( child: Row(
children: [ children: [
AddressFormTextField( AddressFormTextField(
controller: _addressController.houseNumber, controller: _addressController._housenumberController,
validator: (text) { validator: housenumberValidator,
if (text.isEmpty) { label: housenumberLabel,
return 'Can\'t be empty';
}
if (text.length >= 3 || int.tryParse(text) == null) {
return 'Invalid number';
}
return null;
},
label: const Text('Huisnummer'),
), ),
AddressFormTextField( AddressFormTextField(
controller: _addressController.suffix, controller: _addressController._suffixController,
validator: (text) { validator: suffixValidator,
if (text.isEmpty) { label: suffixLabel,
return 'Can\'t be empty';
}
if (RegExp(r'/^[a-z]*$/').hasMatch(text) &&
text.length != 1) {
return 'Invalid prefix';
}
return null;
},
label: const Text('Toevoeging'),
), ),
], ],
), ),
), ),
AddressFormTextField( AddressFormTextField(
controller: _addressController.street, controller: _addressController._streetController,
validator: (text) { validator: streetValidator,
if (text.isEmpty) { label: streetLabel,
return 'Can\'t be empty';
}
return null;
},
label: const Text('Straatnaam'),
), ),
AddressFormTextField( AddressFormTextField(
controller: _addressController.city, controller: _addressController._cityController,
validator: (text) { validator: cityValidator,
if (text.isEmpty) { label: cityLabel,
return 'Can\'t be empty';
}
return null;
},
label: const Text('Woonplaats'),
), ),
TextButton(
onPressed: () {},
child: Text('Test'),
)
], ],
); );
} }
@override
void dispose() {
super.dispose();
}
} }
class AddressFormTextField extends StatefulWidget { class AddressFormTextField extends StatelessWidget {
final Widget label; final Widget label;
final TextEditingController controller; final TextEditingController controller;
final String? Function(String) validator; final String? Function(String) validator;
@ -116,33 +88,20 @@ class AddressFormTextField extends StatefulWidget {
required this.validator, required this.validator,
}) : super(key: key); }) : super(key: key);
@override String? get _errorText => validator(controller.value.text);
State<AddressFormTextField> createState() => _AddressFormTextFieldState();
}
class _AddressFormTextFieldState extends State<AddressFormTextField> {
String? get _errorText {
final text = widget.controller.value.text;
return widget.validator(text);
}
@override
void initState() {
super.initState();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ValueListenableBuilder<TextEditingValue>( return ValueListenableBuilder<TextEditingValue>(
valueListenable: widget.controller, valueListenable: controller,
builder: (context, value, _) { builder: (context, value, _) {
return Flexible( return Flexible(
child: Container( child: Container(
margin: const EdgeInsets.all(10), margin: const EdgeInsets.all(10),
child: TextField( child: TextField(
controller: widget.controller, controller: controller,
decoration: InputDecoration( decoration: InputDecoration(
label: widget.label, label: label,
border: const OutlineInputBorder(), border: const OutlineInputBorder(),
errorText: _errorText), errorText: _errorText),
), ),
@ -151,3 +110,77 @@ class _AddressFormTextFieldState extends State<AddressFormTextField> {
}); });
} }
} }
class AddressController extends ChangeNotifier {
final AddressModel? initialValue;
final FutureOr<AddressModel> Function(AddressModel)? onAutoComplete;
AddressController({this.initialValue, this.onAutoComplete}) {
_model = initialValue ??
const AddressModel(
zipcode: null,
street: null,
housenumber: null,
suffix: null,
city: null,
);
_zipcodeController.addListener(_update);
_streetController.addListener(_update);
_housenumberController.addListener(_update);
_suffixController.addListener(_update);
_cityController.addListener(_update);
}
late AddressModel _model;
late final _zipcodeController =
TextEditingController(text: initialValue?.zipcode);
late final _streetController =
TextEditingController(text: initialValue?.street);
late final _housenumberController =
TextEditingController(text: initialValue?.housenumber.toString());
late final _suffixController =
TextEditingController(text: initialValue?.suffix);
late final _cityController = TextEditingController(text: initialValue?.city);
AddressModel get model => _model;
void _update() async {
AddressModel updatedModel = _model.copyWith(
zipcode: _zipcodeController.text,
street: _streetController.text,
housenumber: int.tryParse(_housenumberController.text),
suffix: _suffixController.text,
city: _cityController.text);
_model = await onAutoComplete?.call(updatedModel) ?? updatedModel;
if (_model.zipcode != updatedModel.zipcode) {
_zipcodeController.text = _model.zipcode ?? '';
}
if (_model.street != updatedModel.street) {
_streetController.text = _model.street ?? '';
}
if (_model.housenumber != updatedModel.housenumber) {
_housenumberController.text = _model.housenumber?.toString() ?? '';
}
if (_model.suffix != updatedModel.suffix) {
_suffixController.text = _model.suffix ?? '';
}
if (_model.city != updatedModel.city) {
_cityController.text = _model.city ?? '';
}
notifyListeners();
}
@override
void dispose() {
super.dispose();
_zipcodeController.dispose();
_streetController.dispose();
_housenumberController.dispose();
_suffixController.dispose();
_cityController.dispose();
}
}

View file

@ -1,8 +1,8 @@
import 'package:flutter/material.dart' show immutable; import 'package:flutter/material.dart' show immutable;
@immutable @immutable
class Address { class AddressModel {
const Address({ const AddressModel({
this.zipcode, this.zipcode,
this.street, this.street,
this.housenumber, this.housenumber,
@ -16,14 +16,14 @@ class Address {
final String? suffix; final String? suffix;
final String? city; final String? city;
Address copyWith({ AddressModel copyWith({
String? zipcode, String? zipcode,
String? street, String? street,
int? housenumber, int? housenumber,
String? suffix, String? suffix,
String? city, String? city,
}) => }) =>
Address( AddressModel(
zipcode: zipcode ?? this.zipcode, zipcode: zipcode ?? this.zipcode,
street: street ?? this.street, street: street ?? this.street,
housenumber: housenumber ?? this.housenumber, housenumber: housenumber ?? this.housenumber,

View file

@ -1,12 +1,59 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; 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() {
test('adds one to input values', () { testWidgets('Render App with AddressForm Widget', (tester) async {
final calculator = Calculator(); final RegExp zipcodeRegExp = RegExp(r'^[1-9][0-9]{3}\s?[a-zA-Z]{2}$');
expect(calculator.addOne(2), 3); await tester.pumpWidget(
expect(calculator.addOne(-7), -6); MaterialApp(
expect(calculator.addOne(0), 1); home: Scaffold(
appBar: AppBar(),
body: AddressForm(
zipCodeValidator: (text) {
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';
}
return null;
},
suffixValidator: (text) {
if (text.isNotEmpty && RegExp(r'/^[a-z]*$/').hasMatch(text)) {
return 'Invalid prefix';
}
return null;
},
streetValidator: (text) {
if (text.isEmpty) {
return 'Can\'t be empty';
}
return null;
},
cityValidator: (text) {
if (text.isEmpty) {
return 'Can\'t be empty';
}
return null;
},
controller: AddressController(onAutoComplete: (address) {
return address;
}),
),
),
),
);
await tester.pump();
}); });
} }