Skip to content

Design Philosophy

The Vyuh Property System is built on five core principles that guide every design decision.

1. Type Safety First

Every property carries its type through the entire pipeline. Property<T> wraps a typed value, PropertyControl<T> provides reactive access, PropertyEditor<T> renders the UI, and JsonConverter<T> handles serialization. There are no Map<String, dynamic> leaking between layers.

dart
// The type T flows through the entire chain
final duration = IntProperty(
  key: 'duration',
  label: 'Duration',
  defaultValue: 60,
  validators: [PropertyValidators.min(1)],  // PropertyValidator<num>
);

// Type-safe access
int value = duration.value;        // int, not dynamic
duration.value = 90;               // compile-time checked
String json = duration.toJson();   // uses IntJsonConverter

This eliminates an entire class of runtime errors. If a property is declared as IntProperty, it cannot accidentally hold a string, and its editor will always render a number input.

2. Reactive by Default

Property values are reactive. When a value changes, the UI updates. When a dependent value changes, conditions re-evaluate automatically. This reactivity is powered by reactive_forms internally, but exposed through the simpler PropertyControl<T> and ValueNotifier<bool> abstractions.

dart
// Visibility reacts to value changes automatically
b.boolean('showAdvanced', 'Show Advanced', defaultValue: false);
b.integer('threadCount', 'Thread Count',
  visibleCondition: PropertyCollectionBuilder.whenTrue('showAdvanced'),
);

You never need to write manual listeners for condition evaluation. The PropertyCollection watches for value changes on the underlying FormGroup and re-evaluates all dependent conditions automatically.

3. Registry-Based Extension

The PropertySystem class acts as a central registry for editors, converters, and custom type support. Default types are registered automatically on first use. New types can be added at any time:

dart
// Register a custom type with editor and converter
PropertySystem.register<Color>(
  editor: ColorPropertyEditor(),
  converter: ColorJsonConverter(),
);

// Now ColorProperty works with the same pipeline
final bg = Property<Color>(/* ... */);
bg.createEditor(context: context);  // uses ColorPropertyEditor
bg.toJson();                        // uses ColorJsonConverter

The registry supports both type-based lookup (PropertySystem.getEditor<int>()) and custom key lookup (PropertySystem.getEditor<String>('readonly:string')) for specialized editors.

4. Declarative Conditions

Visibility and enabled state are expressed as declarative condition objects, not imperative code. This makes conditions serializable, composable, and testable:

dart
// Declarative: what, not how
visibleCondition: AndCondition([
  EqualsCondition<bool>('advanced', true),
  ComparisonCondition<int>(
    propertyKey: 'capacity',
    compareValue: 10,
    operator: ComparisonOperator.greaterThan,
  ),
])

// Composable with convenience methods
visibleCondition: PropertyCollectionBuilder.whenAll([
  PropertyCollectionBuilder.whenTrue('advanced'),
  PropertyCollectionBuilder.whenGreaterThan<int>('capacity', 10),
])

Conditions declare their dependencies explicitly, so the system only evaluates conditions when relevant values change.

5. Framework Independence

The Property System has no dependency on vyuh_core. It is a standalone Flutter package that can be used in any Flutter application. This means:

  • No framework lock-in -- Use it in a Vyuh CDX app or a plain Flutter project
  • Minimal dependencies -- Only flutter and reactive_forms
  • Clear abstraction boundary -- The reactive_forms dependency is wrapped behind PropertyControl<T>, so consumers never need to import it directly
  • Testable in isolation -- Property logic can be tested without a full Vyuh platform

The optional reactive_forms_interop.dart import is available for advanced scenarios where direct FormControl or ReactiveTextField access is needed, but it is strictly opt-in.

Design Consequences

These principles lead to several architectural characteristics:

CharacteristicHow It Manifests
Single source of truthEach property owns its control. No separate state management layer needed.
Fluent builderPropertyCollectionBuilder provides a concise API while preserving full type safety.
Automatic editor selectionproperty.createEditor() looks up the editor from the registry. No manual widget mapping.
Condition evaluationDriven by FormGroup.valueChanges, conditions update without explicit triggers.
Serialization paritytoJson() / fromJson() use the same converter registry, ensuring round-trip consistency.
Deep cloningPropertyCollection.clone() and copyWith() enable safe snapshotting for undo/redo.

Next Steps