flutterx_forms 2.0.1
flutterx_forms: ^2.0.1 copied to clipboard
Declarative Flutter forms with typed fields, validators, number, date, file and list inputs, a form controller and drop support.
example/lib/main.dart
import 'package:flutter/material.dart';
import 'package:flutterx_forms/flutterx_forms.dart';
void main() => runApp(const ExampleApp());
/// Shows a form with a text, a number, a date, a file and a list field, and the values it submits.
class ExampleApp extends StatelessWidget {
/// Creates the example app.
const ExampleApp({super.key});
@override
Widget build(BuildContext context) => MaterialApp(
theme: ThemeData(primarySwatch: Colors.green).copyWith(
outlinedButtonTheme: OutlinedButtonThemeData(
style: OutlinedButton.styleFrom(
iconSize: 20,
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(4))),
),
),
inputDecorationTheme: InputDecorationTheme(
border: const OutlineInputBorder(borderRadius: BorderRadius.all(Radius.circular(8))),
focusedBorder: OutlineInputBorder(
borderRadius: const BorderRadius.all(Radius.circular(12)),
borderSide: BorderSide(color: Colors.deepPurple.shade400, width: 3),
),
),
),
home: const Scaffold(
body: SafeArea(child: SingleChildScrollView(child: ExampleForm())),
),
);
}
/// The form of the example: its fields are created and disposed by the [FormController] mixin.
class ExampleForm extends StatefulWidget {
/// Creates the form.
const ExampleForm({super.key});
@override
State<ExampleForm> createState() => _ExampleFormState();
}
class _ExampleFormState extends State<ExampleForm> with FormController<ExampleForm> {
late final XFormField<String> _name = field();
late final XFormField<int?> _age = field();
late final XFormField<DateTime?> _birthday = field();
late final XFormField<XFile?> _photo = field();
late final XFormField<List<String>> _tags = field();
String _submitted = '';
@override
Widget build(BuildContext context) => Padding(
padding: const EdgeInsets.all(16),
child: buildForm(
autovalidateMode: AutovalidateMode.onUserInteraction,
children: [
XTextFormField(
field: _name,
decoration: const InputDecoration(labelText: 'Name'),
textInputAction: TextInputAction.next,
validator: validateFirst([
nonBlank(() => 'The name is required'),
minLen(2, () => 'At least 2 characters'),
maxLen(20, () => 'At most 20 characters'),
]),
),
const SizedBox(height: 16),
XIntFormField(
field: _age,
decoration: const InputDecoration(labelText: 'Age'),
max: 120,
validator: gte<int>(18, () => 'You must be 18 or older'),
),
const SizedBox(height: 16),
XDateTimeFormField(
field: _birthday,
decoration: const InputDecoration(labelText: 'Birthday'),
lastDate: DateTime(2100),
),
const SizedBox(height: 16),
XFileFormField(
field: _photo,
decoration: const InputDecoration(labelText: 'Photo'),
mimeTypes: const {'image/*'},
),
const SizedBox(height: 16),
XListFormField<String>(
field: _tags,
decoration: const InputDecoration(labelText: 'Tags'),
maxLength: 3,
addButtonText: 'Add tag',
itemInitialValue: (values, index) => '',
validator: validateFirst([listNonEmpty<String>(() => 'Add at least one tag').cast()]),
itemBuilder: (state, field, values, index) => XTextFormField(
field: field,
initialValue: values[index],
decoration: InputDecoration(labelText: 'Tag ${index + 1}'),
validator: nonBlank(() => 'The tag is required'),
),
),
const SizedBox(height: 16),
Row(
children: [
FilledButton(onPressed: submitForm, child: const Text('Submit')),
const SizedBox(width: 8),
OutlinedButton(onPressed: resetForm, child: const Text('Reset')),
],
),
if (_submitted.isNotEmpty) Padding(padding: const EdgeInsets.only(top: 16), child: Text(_submitted)),
],
),
);
@override
void onFormSubmit() => setState(
() => _submitted =
'${_name.value}, ${_age.value}, ${_birthday.value}, ${_photo.value?.name}, ${_tags.value.join('/')}',
);
}