Skip to content

Collections

A PropertyCollection groups multiple properties into a single manageable unit with shared validation, condition evaluation, serialization, and UI rendering.

PropertyCollection

The core container for properties. Internally it builds a reactive_forms FormGroup from all property controls and listens to value changes for condition evaluation.

dart
final collection = PropertyCollection(
  groups: [
    PropertyGroup.collapsible(value: 'basic', title: 'Basic Info'),
    PropertyGroup.collapsible(value: 'advanced', title: 'Advanced'),
  ],
  properties: [
    StringProperty(key: 'title', label: 'Title', group: 'basic', required: true),
    StringProperty(key: 'description', label: 'Description', group: 'basic'),
    IntProperty(key: 'timeout', label: 'Timeout (sec)', group: 'advanced', defaultValue: 30),
  ],
);

Key Operations

OperationMethodDescription
Read valuegetValue<T>(key)Type-safe value access
Write valuesetValue<T>(key, value)Type-safe value set
Get propertygetProperty(key)Get the Property instance
Check existencehasProperty(key)Whether key exists
All propertiespropertiesList in insertion order
By groupgetPropertiesByGroup(value)Properties in a group
ValidatevalidateAll()Returns Map<String, String> of errors
Is validisValidTrue if all properties pass validation
Has changeshasChangesTrue if any value differs from default
SerializetoJson()All property values as Map<String, dynamic>
DeserializefromJson(json)Restore values from JSON map
ResetresetAll()Reset all properties to defaults
Cloneclone()Deep clone the entire collection
Copy fromcopyFrom(other)Replace contents from another collection
Disposedispose()Cancel all subscriptions

Value Streams

dart
// Stream of all value changes
collection.valueChanges.listen((values) {
  print('Values changed: $values');
});

// Current values snapshot
final Map<String, dynamic> current = collection.values;

PropertyGroup

Groups provide visual organization in the editor. There are three factory constructors:

Collapsible Group

Renders as an ExpansionTile that the user can expand/collapse:

dart
PropertyGroup.collapsible(
  value: 'advanced',
  title: 'Advanced Settings',
  initiallyCollapsed: true,  // start collapsed
);

Fixed Group

Renders as a non-collapsible section with a header:

dart
PropertyGroup.fixed(
  value: 'metadata',
  title: 'Metadata',
  showHeader: true,
);

General Group

Auto-created for ungrouped properties. You rarely need to create this manually:

dart
PropertyGroup.general(
  title: 'General',
  showHeader: false,  // no header when it's the only group
);

PropertyGroup Fields

FieldTypeDefaultDescription
valueStringRequiredUnique identifier
titleStringRequiredDisplay title
collapsiblebooltrueWhether the group can be collapsed
initiallyCollapsedboolfalseWhether to start collapsed
showHeaderbooltrueWhether to show the group header

PropertyCollectionBuilder

The fluent builder API eliminates boilerplate when constructing collections. It tracks groups, properties, and derivations, then builds them into a PropertyCollection.

Basic Usage

dart
final b = PropertyCollectionBuilder();

// Start a group context
b.group('basic', 'Basic Info');
b.string('title', 'Title', required: true);
b.string('description', 'Description');
b.integer('duration', 'Duration', defaultValue: 60);

// Start another group
b.group('enrollment', 'Enrollment');
b.boolean('open', 'Open Enrollment', defaultValue: true);
b.integer('capacity', 'Capacity', defaultValue: 30,
  visibleCondition: PropertyCollectionBuilder.whenTrue('open'));

// Build the collection
final collection = b.buildCollection();

Properties declared after a group() call are automatically assigned to that group. Use endGroup() to return to ungrouped:

dart
b.group('settings', 'Settings');
b.string('theme', 'Theme');
b.endGroup();
b.string('notes', 'Notes');  // ungrouped

Builder Methods

MethodReturnsDescription
group(value, title, ...)PropertyCollectionBuilderStart a group context
endGroup()PropertyCollectionBuilderEnd current group context
string(key, label, ...)StringPropertyAdd a string property
optionalText(key, label, ...)OptionalStringPropertyAdd a nullable string
integer(key, label, ...)IntPropertyAdd an integer property
optionalInteger(key, label, ...)OptionalIntPropertyAdd a nullable integer
decimal(key, label, ...)DoublePropertyAdd a double property
optionalDecimal(key, label, ...)OptionalDoublePropertyAdd a nullable double
boolean(key, label, ...)BoolPropertyAdd a boolean property
optionalBoolean(key, label, ...)OptionalBoolPropertyAdd a nullable boolean
dateTime(key, label, ...)DateTimePropertyAdd a DateTime property
optionalDateTime(key, label, ...)OptionalDateTimePropertyAdd a nullable DateTime
enumeration<T>(key, label, ...)EnumProperty<T>Add an enum property
list<T>(key, label, ...)ListProperty<T>Add a list property
readonly<T>(key, label, ...)ReadOnlyProperty<T>Add a read-only property
derivedProperty(target, ...)PropertyCollectionBuilderAdd a derivation rule
buildCollection()PropertyCollectionBuild the final collection
buildList()List<Property>Build just the property list
groupsList<PropertyGroup>Get configured groups

Condition Convenience Methods

The builder provides static methods for creating conditions inline:

dart
PropertyCollectionBuilder.when<T>(key, value)        // EqualsCondition
PropertyCollectionBuilder.whenNot<T>(key, value)     // NotEqualsCondition
PropertyCollectionBuilder.whenIn<T>(key, values)     // InCondition
PropertyCollectionBuilder.whenNotIn<T>(key, values)  // NotInCondition
PropertyCollectionBuilder.whenTrue(key)              // EqualsCondition<bool>(true)
PropertyCollectionBuilder.whenFalse(key)             // EqualsCondition<bool>(false)
PropertyCollectionBuilder.whenHasValue(key)          // HasValueCondition
PropertyCollectionBuilder.whenAll(conditions)        // AndCondition
PropertyCollectionBuilder.whenAny(conditions)        // OrCondition
PropertyCollectionBuilder.whenGreaterThan<T>(key, v) // ComparisonCondition
PropertyCollectionBuilder.whenLessThan<T>(key, v)    // ComparisonCondition
PropertyCollectionBuilder.whenCustom(deps, fn)       // CustomCondition

PropertyCollectionEditor

The editor widget renders a PropertyCollection as a form. It supports grouped and ungrouped layouts, scrolling, and shrink-wrapping.

dart
PropertyCollectionEditor(
  collection: collection,
  showGroups: true,           // render groups with headers
  shrinkWrap: true,           // wrap content height
  scrollable: true,           // enable scrolling
  disposeCollection: true,    // dispose collection on widget dispose
  onChanged: () => print('Changed'),
);

You can also use the collection's convenience method:

dart
collection.createEditor(
  context: context,
  showGroups: true,
  shrinkwrap: true,
  scrollable: true,
);

Single-Line Editor

For compact layouts, render all editable properties in a horizontal row:

dart
collection.createSingleLineEditor(
  context: context,
  flexValues: [2, 1, 1],  // optional flex weights
);

How Rendering Works

  1. The editor builds a flat list of items (properties and group headers)
  2. Each property is wrapped in an _IsolatedPropertyEditor that uses ValueListenableBuilder<bool> on the property's visibleNotifier
  3. When visibility changes, only the affected property rebuilds -- not the entire form
  4. Groups render as ExpansionTile (collapsible) or Column (fixed)

PropertyDerivation

A derivation auto-computes a target property's value from one or more source properties:

dart
b.string('title', 'Title', required: true);
b.string('slug', 'URL Slug', required: true);

// Auto-derive slug from title
b.derivedProperty('slug',
  from: {'title'},
  transformer: (values) =>
    PropertyValidators.toIdentifier(values['title'] as String?),
);

Behavior

  • When a source property changes, the transformer runs and updates the target
  • If the user manually edits the target, auto-derivation stops (marked dirty)
  • If the user clears the target, auto-derivation resumes
  • Set initiallyDirty: true to prevent derivation when loading existing data

PropertyDerivation Fields

FieldTypeDescription
targetPropertyStringKey of the property to auto-compute
sourcePropertiesSet<String>Keys of properties to watch
transformerFunction(Map<String, dynamic>)Computes the new value from all property values
initiallyDirtyboolWhether to skip derivation initially

Adding Derivations

Derivations can be added via the builder or directly on a collection:

dart
// Via builder
b.derivedProperty('slug', from: {'title'}, transformer: (v) => ...);
final collection = b.buildCollection();

// Directly on collection
collection.addDerivations([
  PropertyDerivation(
    targetProperty: 'slug',
    sourceProperties: {'title'},
    transformer: (values) => ...,
  ),
]);

To mark all derivations as dirty (e.g., after loading data):

dart
collection.markDerivationsAsDirty();

Next Steps