Architecture
The Property System architecture follows a layered pipeline from property declaration through reactive control to editor rendering and JSON serialization.
Conceptual Overview
The Property Pipeline
Every property goes through a three-stage pipeline: Declare, Control, Render.
| Stage | Class | Responsibility |
|---|---|---|
| Declare | Property<T> | Defines the property metadata: key, label, default value, validators, conditions, and group |
| Control | PropertyControl<T> | Wraps reactive_forms.FormControl<T> to provide reactive value access, validation state, and disabled/enabled control |
| Render | PropertyEditor<T> | Looks up the registered editor for type T from PropertySystem and creates a Flutter widget |
| Serialize | JsonConverter<T> | Converts between typed values and JSON-compatible representations |
How a Property Creates Its Editor
When property.createEditor(context:) is called:
- The property asks
PropertySystem.getEditor<T>(customKey)for an editor - The registry returns a
PropertyEditor<T>instance (or throws if none registered) - The editor's
createEditor()method receives thePropertyControl<T>, label, and help text - The editor returns a Flutter widget bound to the control
The PropertySystem Registry
PropertySystem is a static registry that maps types to their editors and converters. It auto-initializes with defaults for String, int, double, bool, DateTime, and their nullable variants on first access.
Registration can happen in two ways:
// Type-based (default for all properties of type T)
PropertySystem.register<int>(
editor: const IntPropertyEditor(),
converter: const IntJsonConverter(),
);
// Custom key (for specialized variants)
PropertySystem.register<String>(
key: 'readonly:string',
editor: const ReadOnlyPropertyEditor<String>(),
);Custom key registration takes priority over type-based registration when a property specifies customKey.
PropertyCollection Architecture
A PropertyCollection groups multiple properties into a single unit with shared validation, condition evaluation, and serialization:
When any property value changes:
- The
FormGroup.valueChangesstream fires - The collection checks the dependency map to find which properties depend on the changed key
- Each dependent property's conditions are re-evaluated
- Visibility and enabled state are updated via
ValueNotifier<bool>andPropertyControlmethods
Layered Architecture
| Layer | Contents |
|---|---|
| Application | Your property definitions, settings panels, configuration screens |
| Builder | PropertyCollectionBuilder, PropertyCollection, PropertyGroup |
| Core | Property<T>, PropertyControl<T>, conditions, validators, derivations |
| Editor | PropertyCollectionEditor, PropertyEditor<T> implementations, reactive_forms widgets |
| Serialization | JsonConverter<T>, built-in converters for all default types |
Package Structure
vyuh_property_system/
├── lib/
│ ├── property.dart # Property<T> abstract class + type exports
│ ├── property_control.dart # PropertyControl<T>, PropertyListControl<T>
│ ├── property_editor.dart # PropertyEditor<T> interface
│ ├── property_system.dart # PropertySystem registry
│ ├── property_validator.dart # PropertyValidator<T>, PropertyValidators
│ ├── property_collection.dart # barrel export for collection/
│ ├── reactive_forms_interop.dart # opt-in reactive_forms access
│ ├── collection/
│ │ ├── property_collection.dart # PropertyCollection, PropertyGroup
│ │ ├── property_collection_builder.dart # Fluent builder API
│ │ ├── property_collection_editor.dart # Editor widget
│ │ └── property_derivation.dart # PropertyDerivation
│ ├── conditions/
│ │ └── property_condition.dart # All condition types
│ ├── json_conversion/
│ │ ├── json_converter.dart # JsonConverter<T>, BaseJsonConverter
│ │ └── converters/ # Built-in converters per type
│ └── types/
│ ├── string/ # StringProperty, OptionalStringProperty, editors
│ ├── int/ # IntProperty, OptionalIntProperty, editors
│ ├── double/ # DoubleProperty, OptionalDoubleProperty, editors
│ ├── bool/ # BoolProperty, OptionalBoolProperty, editors
│ ├── datetime/ # DateTimeProperty, OptionalDateTimeProperty, editors
│ ├── enum/ # EnumProperty<T>, EnumOption<T>, editor
│ ├── list/ # ListProperty<T>, editor, context
│ ├── union/ # UnionProperty, UnionOption, editor
│ └── readonly/ # ReadOnlyProperty<T>, editorreactive_forms Integration
The Property System uses reactive_forms internally but does not expose it by default. The wrapping strategy:
| Property System | reactive_forms |
|---|---|
PropertyControl<T> | wraps FormControl<T> |
PropertyListControl<T> | wraps FormArray<T> |
PropertyCollection.reactiveFormGroup | exposes FormGroup |
PropertyValidator<T> | bridged to Validator<dynamic> |
For advanced scenarios, import reactive_forms_interop.dart to access:
control.formControl-- unwrap toFormControl<T>collection.formGroup-- unwrap toFormGrouplistControl.formArray-- unwrap toFormArray<T>defaultValidationMessages-- error message extractors- Re-exported
ReactiveTextField,ReactiveForm,Validators, etc.
Next Steps
- Property Types -- All built-in types and their usage
- Collections -- PropertyCollection, builder, and editor
- Conditions -- Reactive visibility and enabled state