Last active
December 10, 2020 02:05
-
-
Save PlugFox/981a8d2145a9878dae2b52aebd9fae88 to your computer and use it in GitHub Desktop.
Simple form managment
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/// http://dartpad.dev/981a8d2145a9878dae2b52aebd9fae88 | |
import 'package:flutter/material.dart'; | |
import 'package:flutter/services.dart'; | |
void main() => runApp(App()); | |
class App extends StatelessWidget { | |
@override | |
Widget build(BuildContext context) => const MaterialApp( | |
title: 'FORM DATA', | |
home: Scaffold( | |
body: SafeArea( | |
child: Center( | |
child: Card( | |
margin: EdgeInsets.all(16), | |
elevation: 4, | |
clipBehavior: Clip.antiAlias, | |
shape: RoundedRectangleBorder( | |
borderRadius: BorderRadius.all( | |
Radius.circular( | |
8, | |
), | |
), | |
), | |
child: Padding( | |
padding: EdgeInsets.all(18), | |
child: FoxyForm(), | |
), | |
), | |
), | |
), | |
), | |
); | |
} | |
/// Виджет формы | |
@immutable | |
class FoxyForm extends StatefulWidget { | |
const FoxyForm({Key key}) : super(key: key); | |
@override | |
State<FoxyForm> createState() => _FoxyFormState(); | |
} | |
class _FoxyFormState extends State<FoxyForm> { | |
final ValueNotifier<bool> _allFilled = ValueNotifier(false); | |
FormData _formData; | |
FieldStatus<String, FormData> _nameField; | |
FieldStatus<String, FormData> _ageField; | |
Iterable<FieldStatus> _fields; | |
final FocusNode _buttonFocusNode = FocusNode(); | |
@override | |
void initState() { | |
super.initState(); | |
_formData = FormData( | |
name: 'Иван', | |
age: '15', | |
); | |
_nameField = FieldStatus<String, FormData>( | |
formData: _formData, | |
value: (data) => data.name, | |
onChanged: (data, name) => data.name = name, | |
validator: (data, name) => name?.isEmpty ?? true ? 'Поле имени не должно быть пустым' : null, | |
); | |
_ageField = FieldStatus<String, FormData>( | |
formData: _formData, | |
value: (data) => data.age, | |
onChanged: (data, age) => data.age = age, | |
validator: (data, age) => age?.isEmpty ?? true ? 'Поле возраста не должно быть пустым' : null, | |
); | |
_fields = <FieldStatus>[_nameField, _ageField]; | |
_setValidationStatus(); | |
} | |
@override | |
void dispose() { | |
_allFilled.dispose(); | |
super.dispose(); | |
} | |
@override | |
Widget build(BuildContext context) => Form( | |
autovalidateMode: AutovalidateMode.onUserInteraction, | |
onChanged: _setValidationStatus, | |
child: Builder( | |
builder: (context) => SingleChildScrollView( | |
child: Column( | |
mainAxisSize: MainAxisSize.min, | |
children: <Widget>[ | |
TextFormField( | |
autofocus: true, | |
focusNode: _nameField.focusNode, | |
onEditingComplete: _nextEmpty, | |
initialValue: _formData.name, | |
onSaved: _nameField.onChanged, | |
onChanged: _nameField.onChanged, | |
validator: _nameField.validator, | |
keyboardType: TextInputType.name, | |
inputFormatters: [ | |
FilteringTextInputFormatter.allow(RegExp('[A-Za-zА-Яа-я]+')), | |
], | |
decoration: const InputDecoration( | |
labelText: 'Name', | |
), | |
), | |
const SizedBox(height: 13), | |
TextFormField( | |
focusNode: _ageField.focusNode, | |
onEditingComplete: _nextEmpty, | |
initialValue: _formData.age, | |
onSaved: _ageField.onChanged, | |
onChanged: _ageField.onChanged, | |
validator: _ageField.validator, | |
keyboardType: TextInputType.number, | |
inputFormatters: [ | |
FilteringTextInputFormatter.digitsOnly, | |
], | |
decoration: const InputDecoration( | |
labelText: 'Age', | |
), | |
), | |
const SizedBox(height: 13), | |
ValueListenableBuilder<bool>( | |
valueListenable: _allFilled, | |
builder: (context, value, _) => RaisedButton( | |
focusNode: _buttonFocusNode, | |
onPressed: value ? () => _submit(context) : null, | |
child: const Text('SUBMIT FORM'), | |
), | |
), | |
], | |
), | |
), | |
), | |
); | |
void _nextEmpty() { | |
final fieldNode = _fields.firstWhere((field) => !field.filled, orElse: () => null)?.focusNode; | |
if (fieldNode == null) { | |
_unfocusAll(); | |
//_buttonFocusNode.requestFocus(); | |
} else { | |
fieldNode.requestFocus(); | |
} | |
} | |
void _unfocusAll() => | |
_fields.map<FocusNode>((field) => field.focusNode).forEach((node) => node.unfocus()); | |
void _setValidationStatus() => _allFilled.value = _fields.every((field) => field.filled); | |
void _submit(BuildContext context) { | |
_unfocusAll(); | |
final form = Form.of(context)..save(); | |
if (!form.validate()) { | |
_nextEmpty(); | |
return; | |
} | |
Scaffold.of(context).showSnackBar( | |
SnackBar( | |
content: Text('SUBMIT: $_formData'), | |
), | |
); | |
} | |
} | |
/// Данные формы | |
class FormData { | |
String name; | |
String age; | |
FormData({this.name = '', this.age = ''}); | |
@override | |
String toString() => '{"name": "$name", "age": "$age"}'; | |
} | |
/// Класс в который инкапсулировано изменение и верификация поля формы | |
@immutable | |
class FieldStatus<FieldType, FormDataType> { | |
/// Данные общие для формы | |
final FormDataType _formData; | |
/// Заполнено ли поле | |
bool get filled => !hasValidator || validator(value(_formData)) == null; | |
/// Получение текущего значения поля | |
final FieldType Function(FormDataType data) value; | |
/// Нода для перехода к полям (например незаполненным при [onEditingComplete] | |
final FocusNode focusNode; | |
/// При изменении поля | |
ValueChanged<FieldType> get onChanged => (value) => _onChanged(_formData, value); | |
final void Function(FormDataType data, FieldType value) _onChanged; | |
/// Проверка поля | |
/// validator может быть опциональный (для необязательных полей его не указывать) | |
FormFieldValidator<FieldType> get validator => (value) => hasValidator ? _validator(_formData, value) : null; | |
final String Function(FormDataType data, FieldType value) _validator; | |
/// Обладает ли валидатором | |
final bool hasValidator; | |
FieldStatus({ | |
@required FormDataType formData, | |
@required this.value, | |
@required void Function(FormDataType data, FieldType value) onChanged, | |
String Function(FormDataType data, FieldType value) validator, | |
FocusNode focusNode, | |
}) : _formData = formData, | |
focusNode = focusNode ?? FocusNode(), | |
_onChanged = onChanged, | |
_validator = validator, | |
hasValidator = validator != null; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment