Skip to content

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.

StageClassResponsibility
DeclareProperty<T>Defines the property metadata: key, label, default value, validators, conditions, and group
ControlPropertyControl<T>Wraps reactive_forms.FormControl<T> to provide reactive value access, validation state, and disabled/enabled control
RenderPropertyEditor<T>Looks up the registered editor for type T from PropertySystem and creates a Flutter widget
SerializeJsonConverter<T>Converts between typed values and JSON-compatible representations

How a Property Creates Its Editor

When property.createEditor(context:) is called:

  1. The property asks PropertySystem.getEditor<T>(customKey) for an editor
  2. The registry returns a PropertyEditor<T> instance (or throws if none registered)
  3. The editor's createEditor() method receives the PropertyControl<T>, label, and help text
  4. 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:

dart
// 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:

  1. The FormGroup.valueChanges stream fires
  2. The collection checks the dependency map to find which properties depend on the changed key
  3. Each dependent property's conditions are re-evaluated
  4. Visibility and enabled state are updated via ValueNotifier<bool> and PropertyControl methods

Layered Architecture

LayerContents
ApplicationYour property definitions, settings panels, configuration screens
BuilderPropertyCollectionBuilder, PropertyCollection, PropertyGroup
CoreProperty<T>, PropertyControl<T>, conditions, validators, derivations
EditorPropertyCollectionEditor, PropertyEditor<T> implementations, reactive_forms widgets
SerializationJsonConverter<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>, editor

reactive_forms Integration

The Property System uses reactive_forms internally but does not expose it by default. The wrapping strategy:

Property Systemreactive_forms
PropertyControl<T>wraps FormControl<T>
PropertyListControl<T>wraps FormArray<T>
PropertyCollection.reactiveFormGroupexposes FormGroup
PropertyValidator<T>bridged to Validator<dynamic>

For advanced scenarios, import reactive_forms_interop.dart to access:

  • control.formControl -- unwrap to FormControl<T>
  • collection.formGroup -- unwrap to FormGroup
  • listControl.formArray -- unwrap to FormArray<T>
  • defaultValidationMessages -- error message extractors
  • Re-exported ReactiveTextField, ReactiveForm, Validators, etc.

Next Steps