axon_form_flutter 0.0.8
axon_form_flutter: ^0.0.8 copied to clipboard
A cross-platform Flutter plugin for building and rendering dynamic forms with configurable fields, validation, and file uploads.
Axon Form - Dynamic Form System for Flutter #
A powerful and flexible dynamic form generation system for Flutter that renders forms from Axon JSON using a native FFI core.
๐จ Key Features #
- ๐ Dynamic Form Generation from JSON configuration
- ๐ฏ Provider-based State Management with
AxonFormProvider - โ Built-in Validation via native Axon engine
- ๐จ Customizable Rendering with page/field/navigation builders
- ๐ง 10 Input Field Types out of the box
- ๐ฑ Multi-page Forms with default or custom navigation
- ๐ Production-oriented FFI Architecture
๐๏ธ Architecture Overview #
Core Components #
- JSON Configuration - Defines pages, nodes, edges, and conditions
- Native Core (FFI) - Handles graph init, validation, visibility, and values
- State Management -
AxonFormProvider+ChangeNotifier - Rendering Layer -
AxonForm+ page/field builders - Field Widgets - Modular widgets for each field type
Architecture Diagram #
JSON Config
โ
AxonFormFFI.initialize()
โ
AxonFormGraph + AxonFormProvider
โ
AxonForm / FormBuilder / PageBuilder
โ
AxonFormFieldBuilder
โ
Field Widgets (text, number, date, ...)
๐ฏ State Management Approach #
AxonFormProvider (ChangeNotifier) #
AxonFormProvider is the form state orchestrator:
- Stores graph/page state and current page index
- Retrieves field values through native core
- Triggers field/page validation through native core
- Handles dynamic visibility updates through native event listeners
- Notifies UI consumers reactively
AxonFormController #
AxonFormController is the public controller wrapper:
controller.getFieldValue('field_id');
controller.setFieldValue('field_id', value);
controller.validatePage('page_id');
controller.getOptions('field_id');
controller.nextPage();
controller.prevPage();
๐ Supported Input Field Types #
| Field Type | Enum Value | JSON Value | Widget | Description |
|---|---|---|---|---|
| Text | FieldType.text |
"text" |
AxonTextInput |
Single-line text input |
| Number | FieldType.number |
"number" |
AxonNumberInput |
Digits-only numeric input |
| Date | FieldType.date |
"date" |
AxonDateInput |
Date picker |
| Password | FieldType.password |
"password" |
AxonPasswordInput |
Obscured input with toggle |
| Radio | FieldType.radio |
"radio" |
AxonRadioInput |
Single selection from options |
| Dropdown | FieldType.dropdown |
"dropdown" |
AxonDropdownInput |
Dropdown selection |
| Address Dropdown | FieldType.addressDropdown |
"address_dropdown" |
AxonAddressDropdownInput |
Cascading address selection |
| Checkbox | FieldType.checkbox |
"checkbox" |
AxonCheckboxInput |
Boolean input |
| Multi-select | FieldType.multiSelect |
"multi_select" |
AxonMultiSelectInput |
Multiple option selection |
| File | FieldType.file |
"file" |
AxonFileInput |
File picker input |
๐ Getting Started #
Requirements #
- Flutter
>=3.3.0 - Dart SDK
^3.8.0
Installation #
Add to your app pubspec.yaml:
dependencies:
axon_form_flutter:
path: ../axon_form_flutter
Then run:
flutter pub get
iOS Setup #
The native Axon core ships as a compiled .xcframework and is only referenced
through Dart FFI (dart:ffi symbol lookups) rather than any Swift/Objective-C
import. Xcode's linker has no static reference to those symbols, so its default
dead code stripping will strip them out of the release binary and FFI calls will
fail to resolve at runtime unless it's disabled.
In Xcode, open the iOS project (ios/Runner.xcworkspace) and for the Runner
target:
- Go to Build Settings โ switch to the Combined tab โ search for
strip. - Under Linking - General, set Dead Code Stripping to No for all configurations (Debug, Profile, Release).
Without this, the app may build and run fine in Debug but fail to find the native Axon symbols in Release/Profile builds (e.g. App Store or TestFlight builds).
Basic Usage #
import 'dart:convert';
import 'package:axon_form_flutter/axon_form_flutter.dart';
import 'package:flutter/material.dart';
class MyFormScreen extends StatelessWidget {
const MyFormScreen({super.key});
@override
Widget build(BuildContext context) {
return FutureBuilder<String>(
future: DefaultAssetBundle.of(context).loadString('assets/example.json'),
builder: (context, snapshot) {
if (snapshot.connectionState != ConnectionState.done) {
return const Center(child: CircularProgressIndicator());
}
if (!snapshot.hasData) {
return const Center(child: Text('Error loading form JSON'));
}
final formJson = jsonDecode(snapshot.data!) as Map<String, dynamic>;
return AxonForm.json(
formJson,
onSubmit: (result) {
debugPrint('Form submitted: $result');
},
);
},
);
}
}
Alternative Constructor #
AxonForm.file(
'assets/example.json',
onSubmit: (result) => debugPrint('$result'),
)
๐ JSON Configuration Structure #
Expected Top-level Keys #
layout.pagesnodesedges- optional
condition_groups - optional
address(foraddress_dropdown)
Minimal Example #
{
"layout": {
"pages": [
{
"id": "p1",
"order": 0,
"title": "Basic Info",
"description": "Enter your details",
"field_ids": ["f_name", "f_gender"]
}
]
},
"nodes": [
{
"id": "p1",
"type": "page",
"field_name": "",
"field_type": "",
"label": ""
},
{
"id": "f_name",
"type": "input",
"field_type": "text",
"field_name": "name",
"label": "Full Name",
"validation_rules": [
{"type": "required", "message": "Name is required"}
]
},
{
"id": "f_gender",
"type": "input",
"field_type": "radio",
"field_name": "gender",
"label": "Gender"
},
{"id": "opt_m", "type": "value", "label": "Male", "value": "m"},
{"id": "opt_f", "type": "value", "label": "Female", "value": "f"}
],
"edges": [
{
"id": "e1",
"source_node": "f_gender",
"target_node": "opt_m",
"type": "has_options"
},
{
"id": "e2",
"source_node": "f_gender",
"target_node": "opt_f",
"type": "has_options"
}
]
}
Full example: example/assets/example.json.
โ Validation Rules #
Validation rule enums available in Flutter:
requiredemailminLengthmaxLengthpatternminmax
Validation is executed by native Axon core during:
- field updates
- page transitions
- submit
๐จ Customization #
Custom Field / Page / Navigation Builders #
AxonForm.json(
formJson,
onSubmit: (result) {},
fieldBuilder: (context, node) {
if (node.id == 'f_name') {
return Text('Custom field for ${node.label}');
}
return null; // fallback to built-in field
},
pageBuilder: (context, page, nodes, defaultFieldBuilder) {
return null; // fallback to built-in page
},
pageNavigatorBuilder: (context, pages, currentPage, pageCount, next, prev) {
return null; // fallback to default navigator
},
)
ThemeExtension-based Styling #
MaterialApp(
theme: ThemeData(
extensions: const [
AxonFormTextInputStyle(
decoration: InputDecoration(border: OutlineInputBorder()),
),
AxonFormNumberInputStyle(
decoration: InputDecoration(border: OutlineInputBorder()),
),
AxonFormDateInputStyle(
inputDecoration: InputDecoration(border: OutlineInputBorder()),
),
AxonFormDropdownInputStyle(isExpanded: true),
],
),
home: AxonForm.json(formJson, onSubmit: (result) {}),
)
๐ฆ Package Exports #
Import once:
import 'package:axon_form_flutter/axon_form_flutter.dart';
This includes:
AxonForm,AxonFormController- models (
AxonFormNode,AxonFormPage,AxonFormGraph) - enums (
FieldType,ValidationRuleType) - built-in field widgets
- style extensions
๐งช Run the Example #
cd example
flutter pub get
flutter run