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 ​