formflow 0.1.0
formflow: ^0.1.0 copied to clipboard
A Flutter package for building typed multi-step forms with validation, persistence, and progress tracking.
formflow #
A Flutter package for building typed multi-step form flows with validation, flexible navigation, progress tracking, and optional persistence.
Features #
- Pure Dart controller: no dependency on Riverpod, Bloc, Provider, or GetX
- Typed step data: each step owns its data shape via generics
- Per-step validation: block or allow progression with a clean result type
- Skippable steps: optional steps that bypass validation
- Lifecycle hooks:
onEnter/onExitper step - Progress tracking:
0.0-1.0progress value + three built-in indicator styles - Optional persistence resume interrupted flows via
SharedPreferencesor a custom backend FormFlowBuilderandStreamBuilder-based listener, compatible with any state management layer
Installation #
dependencies:
formflow: ^0.1.0
Quick Start #
1. Define your steps #
class NameStep extends FormStep<String> {
String name = '';
@override String get id => 'name';
@override String get title => 'Your Name';
@override
StepValidationResult validate() {
if (name.trim().isEmpty) return StepValidationResult.invalid('Name is required.');
return StepValidationResult.valid();
}
@override
String get data => name;
}
2. Create the controller #
final flow = FormFlowUtil(
steps: [NameStep(), EmailStep(), ReviewStep()],
onComplete: (data) => submitForm(data),
);
3. Build the UI #
FormFlowBuilder(
controller: flow,
builder: (context, state) {
return Column(
children: [
FormFlowProgress(
state: state,
titles: flow.steps.map((s) => s.title).toList(),
style: FormFlowProgressStyle.steps,
),
_buildCurrentStep(state),
if (state.lastValidationResult?.isInvalid == true)
Text(state.lastValidationResult!.error ?? ''),
Row(
children: [
if (!state.isFirstStep)
OutlinedButton(onPressed: flow.back, child: const Text('Back')),
FilledButton(
onPressed: flow.next,
child: Text(state.isLastStep ? 'Submit' : 'Next'),
),
],
),
],
);
},
)
Navigation #
flow.next(); // validate + advance (or complete if last step)
flow.back(); // go back, no validation
flow.jumpTo(2); // jump to step index 2, no validation
flow.validate(); // validate current step without advancing
flow.reset(); // clear all data, return to step 0
All navigation methods return bool as true if navigation occurred, false if blocked (validation failed, bounds exceeded, or already complete).
Step data #
// Get typed data for a specific step after next() has been called
final name = flow.dataFor<String>('name');
final info = flow.dataFor<PersonalInfoData>('personal_info');
// All merged step data
final allData = flow.state.data; // Map<String, dynamic>
Progress #
flow.progress // double 0.0 - 1.0
flow.state.isFirstStep
flow.state.isLastStep
flow.state.isComplete
Three built-in progress indicator styles:
// Linear bar
FormFlowProgress(state: state, style: FormFlowProgressStyle.linear)
// Animated dots
FormFlowProgress(state: state, style: FormFlowProgressStyle.dots)
// Numbered step row with titles
FormFlowProgress(
state: state,
titles: flow.steps.map((s) => s.title).toList(),
style: FormFlowProgressStyle.steps,
)




Skippable steps #
class OptionalStep extends FormStep<void> {
@override bool get isSkippable => true;
@override StepValidationResult validate() => StepValidationResult.valid();
// ...
}
Persistence #
// Use the built-in SharedPreferences backend
final flow = FormFlowUtil(
steps: [...],
storage: SharedPrefsStorage(),
persistenceKey: 'onboarding_flow',
onComplete: (_) {},
);
// Restore on app start
await flow.restore();
Implement FormFlowStorage to use Hive, SQLite, secure storage, or any other backend:
class HiveFlowStorage implements FormFlowStorage {
@override
Future<void> save(String key, Map<String, dynamic> data) async { ... }
@override
Future<Map<String, dynamic>?> load(String key) async { ... }
@override
Future<void> clear(String key) async { ... }
}
State management integration #
FormFlowUtil emits state via a broadcast Stream. Wire it into anything:
// Riverpod
final flowProvider = StreamProvider((ref) => flow.stream);
// Bloc
flow.stream.listen((state) => add(FlowStateChanged(state)));
// ValueNotifier / setState
flow.stream.listen((state) => setState(() => _state = state));
API Reference #
FormFlowUtil #
| Method / Property | Description |
|---|---|
next() |
Validate + advance. Returns bool. |
back() |
Go back. Returns bool. |
jumpTo(index) |
Jump to step. Returns bool. |
validate() |
Validate without navigating. |
updateData() |
Capture current step data into state. |
dataFor<T>(stepId) |
Typed data for a step. |
reset() |
Clear all data, return to step 0. |
restore() |
Load persisted state from storage. |
dispose() |
Close the stream. Call in dispose(). |
state |
Current FormFlowState snapshot. |
stream |
Broadcast stream of FormFlowState. |
progress |
double 0.0-1.0. |
currentStep |
The active FormStep. |
steps |
All steps (unmodifiable list). |
FormStep<T> #
| Member | Description |
|---|---|
id |
Unique step identifier (required). |
title |
Human-readable step name (required). |
subtitle |
Optional description. |
isSkippable |
Skip validation on next(). Default false. |
validate() |
Returns StepValidationResult. |
data |
Current typed data snapshot. |
onEnter() |
Called when flow navigates to this step. |
onExit() |
Called when flow navigates away from this step. |
FormFlowState #
| Property | Type | Description |
|---|---|---|
currentIndex |
int |
Zero-based active step index |
stepCount |
int |
Total steps |
progress |
double |
0.0-1.0 |
isFirstStep |
bool |
|
isLastStep |
bool |
|
isComplete |
bool |
|
isCurrentStepValid |
bool |
|
lastValidationResult |
StepValidationResult? |
Null until first validation |
data |
Map<String, dynamic> |
Merged data from all steps |
License #
MIT - see LICENSE for details.
Support #
If FormFlow helps you ship faster, a ⭐ on GitHub goes a long way!
Issues: GitHub Issues
Made with ❤️ for the Flutter community.