Example: Course Settings
A complete course configuration panel for a Learning Management System, demonstrating groups, conditions, derivations, validation, and serialization.
Domain Model
| Field | Type | Description |
|---|---|---|
title | String | Course display name |
slug | String | URL-safe identifier (auto-derived from title) |
description | String | Brief overview |
level | Enum | beginner / intermediate / advanced |
duration | Int | Duration in hours |
startDate | DateTime | When the course begins |
enrollmentType | Enum | open / limited / invitation |
capacity | Int | Max students (only when limited) |
enableWaitlist | Bool | Waitlist enabled (only when limited + capacity > 0) |
invitationCode | String | Required code (only when invitation) |
prerequisites | List<String> | Required prior courses |
schedulingMode | Union | fixed / flexible / self-paced (each with sub-properties) |
featured | Bool | Show on homepage |
tags | List<String> | Search keywords |
createdBy | ReadOnly<String> | System field |
Enums
dart
enum CourseLevel { beginner, intermediate, advanced }
enum EnrollmentType { open, limited, invitation }Full Implementation
dart
import 'package:vyuh_property_system/vyuh_property_system.dart';
PropertyCollection buildCourseSettings({String? createdBy}) {
final b = PropertyCollectionBuilder();
// ── Basic Information ──────────────────────────────────────
b.group('basic', 'Basic Information');
b.string('title', 'Course Title',
required: true,
validators: [
PropertyValidators.minLength(3),
PropertyValidators.maxLength(200),
],
help: 'The display name shown to students',
);
b.string('slug', 'URL Slug',
required: true,
validators: [PropertyValidators.identifier()],
help: 'Auto-generated from title. Edit to customize.',
);
b.derivedProperty('slug',
from: {'title'},
transformer: (values) =>
PropertyValidators.toIdentifier(values['title'] as String?),
);
b.string('description', 'Description',
help: 'Brief overview of the course content (shown in listings)',
validators: [PropertyValidators.maxLength(500)],
);
b.enumeration<CourseLevel>('level', 'Course Level',
options: [
EnumOption(CourseLevel.beginner, 'Beginner',
description: 'No prior knowledge required'),
EnumOption(CourseLevel.intermediate, 'Intermediate',
description: 'Some experience recommended'),
EnumOption(CourseLevel.advanced, 'Advanced',
description: 'Requires solid foundation'),
],
defaultValue: CourseLevel.beginner,
);
// ── Schedule ───────────────────────────────────────────────
b.group('schedule', 'Schedule');
b.integer('duration', 'Duration (hours)',
defaultValue: 1,
validators: [PropertyValidators.min(1), PropertyValidators.max(500)],
);
b.dateTime('startDate', 'Start Date');
// Scheduling mode: union of fixed / flexible / self-paced
// (UnionProperty is not available via builder, so we add it manually)
// ── Enrollment ─────────────────────────────────────────────
b.group('enrollment', 'Enrollment Settings');
b.enumeration<EnrollmentType>('enrollmentType', 'Enrollment Type',
options: [
EnumOption(EnrollmentType.open, 'Open Enrollment',
description: 'Anyone can enroll'),
EnumOption(EnrollmentType.limited, 'Limited Capacity',
description: 'First-come, first-served up to capacity'),
EnumOption(EnrollmentType.invitation, 'Invitation Only',
description: 'Requires an invitation code'),
],
defaultValue: EnrollmentType.open,
);
b.integer('capacity', 'Max Capacity',
defaultValue: 30,
validators: [PropertyValidators.min(1), PropertyValidators.max(10000)],
visibleCondition: PropertyCollectionBuilder.when<EnrollmentType>(
'enrollmentType', EnrollmentType.limited,
),
);
b.boolean('enableWaitlist', 'Enable Waitlist',
defaultValue: false,
help: 'Allow students to join a waitlist when the course is full',
visibleCondition: PropertyCollectionBuilder.whenAll([
PropertyCollectionBuilder.when<EnrollmentType>(
'enrollmentType', EnrollmentType.limited,
),
PropertyCollectionBuilder.whenGreaterThan<int>('capacity', 0),
]),
);
b.string('invitationCode', 'Invitation Code',
required: true,
validators: [PropertyValidators.minLength(6)],
help: 'Students must enter this code to enroll',
visibleCondition: PropertyCollectionBuilder.when<EnrollmentType>(
'enrollmentType', EnrollmentType.invitation,
),
);
// ── Content ────────────────────────────────────────────────
b.group('content', 'Content');
b.list<String>('prerequisites', 'Prerequisites',
help: 'Course titles that should be completed before this one',
);
b.list<String>('tags', 'Tags',
help: 'Keywords for search and categorization',
);
// ── Display ────────────────────────────────────────────────
b.group('display', 'Display Options', initiallyCollapsed: true);
b.boolean('featured', 'Feature on Homepage',
defaultValue: false,
help: 'Show this course prominently on the main page',
);
// ── System ─────────────────────────────────────────────────
b.group('system', 'System',
collapsible: false,
showHeader: true,
);
b.readonly<String>('createdBy', 'Created By',
defaultValue: createdBy ?? 'system',
);
return b.buildCollection();
}Adding UnionProperty
For the scheduling mode, add a UnionProperty directly to the collection:
dart
PropertyCollection buildCourseSettingsWithScheduling() {
final collection = buildCourseSettings();
// Add scheduling mode as a union property
final scheduling = UnionProperty(
key: 'schedulingMode',
label: 'Scheduling Mode',
group: 'schedule',
defaultSelectedKey: 'fixed',
options: [
UnionOption(
key: 'fixed',
title: 'Fixed Schedule',
description: 'Course runs on specific dates',
property: StringProperty(
key: 'fixedDates',
label: 'Schedule Description',
help: 'e.g., "Mon/Wed/Fri 10:00-12:00"',
),
),
UnionOption(
key: 'flexible',
title: 'Flexible',
description: 'Complete within a time window',
property: IntProperty(
key: 'completionDays',
label: 'Days to Complete',
defaultValue: 30,
validators: [PropertyValidators.min(1)],
),
),
UnionOption(
key: 'selfPaced',
title: 'Self-Paced',
description: 'No time constraints',
property: BoolProperty(
key: 'withMentoring',
label: 'With Mentoring',
defaultValue: false,
),
),
],
);
collection.addProperty(scheduling);
return collection;
}Usage in a Widget
dart
class CourseSettingsScreen extends StatefulWidget {
final Map<String, dynamic>? existingData;
const CourseSettingsScreen({super.key, this.existingData});
@override
State<CourseSettingsScreen> createState() => _CourseSettingsScreenState();
}
class _CourseSettingsScreenState extends State<CourseSettingsScreen> {
late final PropertyCollection _collection;
@override
void initState() {
super.initState();
_collection = buildCourseSettings(createdBy: 'admin');
if (widget.existingData != null) {
_collection.fromJson(widget.existingData!);
_collection.markDerivationsAsDirty();
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Course Settings'),
actions: [
TextButton(
onPressed: _save,
child: const Text('Save'),
),
],
),
body: Padding(
padding: const EdgeInsets.all(16),
child: PropertyCollectionEditor(
collection: _collection,
showGroups: true,
disposeCollection: false,
),
),
);
}
void _save() {
final errors = _collection.validateAll();
if (errors.isNotEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(errors.values.first)),
);
return;
}
final json = _collection.toJson();
// POST to API
print('Saving: $json');
}
@override
void dispose() {
_collection.dispose();
super.dispose();
}
}Sample JSON Output
json
{
"title": "Flutter Fundamentals",
"slug": "flutter_fundamentals",
"description": "Learn Flutter from the ground up",
"level": "beginner",
"duration": 40,
"startDate": "2026-04-01T09:00:00.000",
"enrollmentType": "limited",
"capacity": 50,
"enableWaitlist": true,
"invitationCode": "",
"prerequisites": ["Dart Basics"],
"tags": ["flutter", "mobile", "dart"],
"featured": true,
"createdBy": "admin"
}Next Steps
- Dashboard Config Example -- Dashboard widget configuration
- Building Forms Guide -- Step-by-step form building
- Property Types Reference -- All built-in types