just_form 0.0.5
just_form: ^0.0.5 copied to clipboard
A Flutter package for building forms and validation.
Just Form #
This README are UNDER CONSTRUCTION #
Example code may be not work. i'm in rush generate it with AI couse my team need to use it asap. will be back soon
A powerful and flexible Flutter form management package that provides automatic field registration, validation, state management using BLoC pattern, and built-in field widgets.
Table of Contents #
- Just Form
- This README are UNDER CONSTRUCTION
ScreenShots #
| Basic Usage | Todo |
|---|---|
| [Fix Height] | [Fix Height] |
Features #
✅ Automatic form field registration and lifecycle management
✅ Real-time field value and error tracking
✅ Cross-field validation support
✅ Custom field widget creation
✅ Flexible rebuild triggers (value, error, attributes)
✅ Field attribute manipulation
✅ Built-in field widgets (TextField, DateField, DropdownField, etc.)
✅ Selective field rebuilding with JustBuilder
Getting Started #
Installation #
Add just_form to your pubspec.yaml:
dependencies:
just_form: ^0.0.1
Basic Usage #
The simplest form requires just a JustFormBuilder and some fields:
import 'package:flutter/material.dart';
import 'package:just_form/just_form_builder.dart';
import 'package:just_form/field/just_text_field.dart';
class MyForm extends StatelessWidget {
@override
Widget build(BuildContext context) {
return JustFormBuilder(
builder: (context) {
return Column(
children: [
// Email field
JustTextField(
name: 'email',
decoration: InputDecoration(
hintText: 'Enter email',
labelText: "Email"
),
),
// Password field
JustTextField(
name: 'password',
decoration: InputDecoration(
hintText: 'Enter password',
labelText: "Password"
),
),
// Submit button
ElevatedButton(
onPressed: () {
final formController = context.justForm;
formController.validate();
print('Form values: ${formController.getValues()}');
print('Form errors: ${formController.getErrors()}');
},
child: Text('Submit'),
),
],
);
},
);
}
}
Validation #
Built-in Validators #
Just Form supports any FormFieldValidator<T> from Flutter. Use packages like validatorless or create your own:
JustTextField(
name: 'email',
validators: [
(value) {
if (value == null || value.isEmpty) {
return 'Email is required';
}
if (!value.contains('@')) {
return 'Please enter a valid email';
}
return null;
},
],
builder: (context, controller) {
return TextField(
onChanged: (value) => controller.value = value,
decoration: InputDecoration(errorText: controller.error),
);
},
)
Cross-Field Validation #
Validate multiple fields together using JustValidator:
JustFormBuilder(
validators: [
JustValidator(
triggers: ['password', 're-password'],
validator: (formValues) {
if (formValues?["password"] != formValues?["re-password"]) {
return "not_match";
}
return null;
},
targets: [
JustTargetError(
field: 're-password',
message: (error) => 'Passwords do not match',
),
],
),
],
builder: (context) {
return Column(
children: [
JustTextField(
name: 'password',
decoration: InputDecoration(
hintText: 'Enter password',
labelText: "Password"
),
),
JustTextField(
name: 're-password',
decoration: InputDecoration(
hintText: 'Retype Enter password',
labelText: "Retype Password"
),
),
],
);
},
)
Custom Validation in JustFormBuilder #
Unlike field-level or form-level validators, the package doesn’t ship with built‑in support for custom/async validation. But you can still hook into the form API directly to set errors programmatically.
You can access the form context and manually set an error on any field:
context.justForm.field("username")?.setError("Username already taken");
context.justForm→ gives you access to the form instance..field("fieldName")→ retrieves the field controller by name..setError("message")→ attaches a custom error message to that field.
When to Use
This pattern is useful for validations that:
- Require async checks (e.g., API calls to check if a username/email is already registered).
- Depend on external state (e.g., server-side rules, business logic).
- Take longer than synchronous validation (e.g., debounce + network request).
Key Difference #
- Default validation → tied to the field (
validator:insideJustTextField). - Cross validation → tied to the form (
JustValidatorinsideJustFormBuilder). - Custom validation → not provided by the package, but you can manually set errors via
context.justForm.field(...).setError(...).
Form Fields #
Creating Custom Fields #
Create your own field widgets by extending JustField<T>:
class MyCustomField extends StatelessWidget {
final String name;
final String? initialValue;
final void Function(String?)? onChanged;
const MyCustomField({
required this.name,
this.initialValue,
this.onChanged,
});
@override
Widget build(BuildContext context) {
return JustField<String>(
name: name,
initialValue: initialValue,
builder: (context, controller) {
return Container(
padding: EdgeInsets.all(8),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
borderRadius: BorderRadius.circular(8),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextField(
onChanged: (value) {
controller.value = value;
onChanged?.call(value);
},
decoration: InputDecoration(
hintText: 'Enter value',
border: InputBorder.none,
errorText: controller.error,
),
),
],
),
);
},
);
}
}
// Usage
MyCustomField(
name: 'custom-field',
initialValue: 'Hello',
onChanged: (value) => print('Changed: $value'),
)
Advanced Features #
Manipulating Attributes #
Attributes are custom metadata you can attach to fields:
JustTextField(
name: 'email',
initialAttributes: {
'touched': false,
'validating': false,
'lastValidated': null,
},
builder: (context, controller) {
return TextField(
onChanged: (value) {
controller.value = value;
// Mark field as touched
controller.setAttribute('touched', true);
},
onFocus: (hasFocus) {
if (hasFocus) {
controller.setAttribute('validating', true);
}
},
decoration: InputDecoration(
hintText: 'Enter email',
errorText: controller.error,
filled: controller.attributes['touched'] == true,
fillColor: Colors.blue.withOpacity(0.1),
),
);
},
)
Access attributes in JustBuilder:
JustBuilder(
fields: ['email'],
rebuildOnAttributeChanged: true,
builder: (context, state) {
final email = state['email'];
final touched = email?.attributes['touched'] ?? false;
return Column(
children: [
if (touched) Text('Field was touched'),
if (email?.attributes['validating'] == true)
CircularProgressIndicator(),
],
);
},
)
Form Control #
Access and manipulate the entire form:
ElevatedButton(
onPressed: () {
final formController = context.read<JustFormController>();
// Get all values
print('Values: ${formController.values}');
// Get all errors
print('Errors: ${formController.errors}');
// Validate the form
final isValid = await formController.validate();
// Set field value
formController.fields()['email']?.value = 'new@email.com';
// Reset form
formController.reset();
// Get specific field
final emailField = formController.fields()['email'];
print('Email value: ${emailField?.value}');
},
child: Text('Check Form'),
)
Selective Rebuilds #
Use JustBuilder to rebuild only when specific fields change:
JustBuilder(
fields: ['email', 'password'],
rebuildOnValueChanged: true,
rebuildOnErrorChanged: true,
builder: (context, state) {
final email = state['email'];
final password = state['password'];
return Column(
children: [
if (email?.error != null)
Text('Email error: ${email?.error}'),
if (password?.error != null)
Text('Password error: ${password?.error}'),
// Rebuild only when email or password changes
],
);
},
)
Examples #
Check the /example folder for complete working examples:
- basic_example.dart - Complete form with various field types
- basic_example_nested_form.dart - Nested forms and complex structures
- todo.dart - Dynamic list of fields example
To run the example:
cd example
flutter run
API Reference #
JustFormBuilder #
The main widget for building forms.
Properties:
builder- Required. Widget builder functioncontroller- Optional. External form controllerinitialValues- Map of initial field valuesvalidators- List of form-level validatorsonFieldRegistered- Callback when field registersonValuesChanged- Callback when any field value changesonErrorsChanged- Callback when errors change
JustField #
Generic field widget for managing individual form fields.
Properties:
name- Required. Unique field identifierbuilder- Required. Field widget builderinitialValue- Initial field valuevalidators- Field-level validatorskeepValueOnDestroy- Preserve value on widget removalrebuildOnValueChanged- Rebuild on value changesrebuildOnErrorChanged- Rebuild on error changesrebuildOnAttributeChanged- Rebuild on attribute changesinitialAttributes- Custom metadata
JustBuilder #
Selective field listener and rebuilder.
Properties:
fields- List of field names to monitorallFields- Monitor all fieldsbuilder- Required. Widget builder functionrebuildOnValueChanged- Rebuild on value changesrebuildOnErrorChanged- Rebuild on error changesrebuildOnAttributeChanged- Rebuild on attribute changes
JustFormController - Quick Reference #
| Method/Property | Purpose | Returns | Example |
|---|---|---|---|
getValues() |
Get all form field values | Map<String, dynamic> |
context.justForm.getValues()['email'] |
getErrors() |
Get all validation errors (only non-null) | Map<String, String?> |
context.justForm.getErrors()['email'] |
isValid() |
Check if form has no errors | bool |
if (context.justForm.isValid()) { ... } |
validate() |
Trigger validation on all fields | void |
context.justForm.validate() |
patchValues(values) |
Update multiple field values at once | void |
context.justForm.patchValues({'email': 'new@email.com'}) |
field<T>(name) |
Get a specific field controller | JustFieldController<T>? |
context.justForm.field('email')?.value = 'x@y.com' |
fields() |
Get all field controllers as a map | Map<String, JustFieldController> |
context.justForm.fields()['email']?.value |
addValuesChangedListener(fn) |
Listen to value changes | void |
context.justForm.addValuesChangedListener((values) { ... }) |
addErrorsChangedListener(fn) |
Listen to error changes | void |
context.justForm.addErrorsChangedListener((errors) { ... }) |
dispose() |
Clean up resources | void |
context.justForm.dispose() |
Quick Access #
| Way to Access Controller | Code |
|---|---|
| Inside widget | final controller = context.justForm; |
| With read | final controller = context.read<JustFormController>(); |
| With watch (rebuilds) | final controller = context.watch<JustFormController>(); |
Common Tasks #
| Task | Code |
|---|---|
| Get form data to submit | final data = controller.getValues(); |
| Check if valid before submit | if (controller.isValid()) { submitForm(); } |
| Show all errors | controller.validate(); |
| Update a field | controller.field('email')?.value = 'new@email.com'; |
| Set field error manually | controller.field('email')?.setError('Custom error'); |
| Get specific field value | final email = controller.field('email')?.value; |
| Listen to changes | controller.addValuesChangedListener((values) { print(values); }); |
JustFieldController #
Controller for individual field management.
Properties:
value- Get/set field valueerror- Get/set field errorattributes- Get field attributes
Methods:
setValue(T value)- Set field valuesetError(String? error)- Set field errorsetAttribute(String key, dynamic value)- Set attributegetState()- Get current field state
License #
This package is licensed under the MIT License. See LICENSE file for details.
Contributing #
Contributions are welcome! Please feel free to submit a Pull Request.
Support #
For issues, questions, or suggestions, please open an issue on the GitHub repository.