Skip to content

Building Forms

This guide walks through building a complete course enrollment form step by step, covering field creation, validation, layout, sections, and conditional logic.

Step 1: Define the Form Structure

Plan the fields and their types before writing code:

FieldTypeValidationWidth
First Nametextrequired, min 2 charshalf
Last Nametextrequired, min 2 charshalf
Emailtextrequired, email formathalf
Phonephonerequiredhalf
Courseselectrequiredfull
Enrollment Typeselectrequiredhalf
Start DatedateTimerequired, future datehalf
Payment Methodselectrequired when paidfull
Notificationsbooleannonefull
Scheduleselectnonefull

Step 2: Create the Basic Form

dart
import 'package:vyuh_feature_forms/dsl/form_builder.dart';

final form = FormBuilder(title: 'Course Enrollment')
  .text('first_name', title: 'First Name')
    .required('First name is required')
    .minLength(2)
  .text('last_name', title: 'Last Name')
    .required('Last name is required')
    .minLength(2)
  .text('email', title: 'Email Address')
    .required('Email is required')
    .email('Please enter a valid email')
  .phone('phone', title: 'Phone Number')
    .required('Phone number is required')
  .build();

At this point you have a form with 4 fields in a 2-column layout.

Step 3: Add Course Selection

dart
  // ... after phone field
  .select('course', title: 'Course')
    .option('flutter_101', title: 'Flutter 101')
    .option('dart_advanced', title: 'Advanced Dart')
    .option('mobile_arch', title: 'Mobile Architecture')
    .option('ui_design', title: 'UI Design Principles')
    .required('Please select a course')

Step 4: Add Enrollment and Date

dart
  .select('enrollment_type', title: 'Enrollment Type')
    .option('open', title: 'Open Enrollment (Free)')
    .option('paid', title: 'Paid Enrollment')
    .required('Please select enrollment type')
  .dateTime('start_date', title: 'Preferred Start Date')
    .dateOnly()
    .required('Start date is required')

Step 5: Add Conditional Payment

The payment method should only appear when enrollment type is "paid":

dart
  .select('payment_method', title: 'Payment Method')
    .option('credit_card', title: 'Credit Card')
    .option('bank_transfer', title: 'Bank Transfer')
    .option('invoice', title: 'Invoice')
    .showWhen(When.fieldEquals('enrollment_type', 'paid'))
    .required('Payment method is required for paid enrollment')

Step 6: Group Preferences in a Section

dart
  .section('Preferences', (s) => s
    .boolean('notifications', title: 'Email Notifications')
      .help('Receive updates about course schedule and materials')
    .select('schedule', title: 'Preferred Schedule')
      .option('morning', title: 'Morning (9am - 12pm)')
      .option('afternoon', title: 'Afternoon (1pm - 5pm)')
      .option('evening', title: 'Evening (6pm - 9pm)'),
    collapsible: true,
    description: 'Optional preferences for your enrollment',
  )

Step 7: Build and Use

dart
  .build();

// Validate and submit
void submit() {
  if (form.validate()) {
    final values = form.currentValues;
    // values = {
    //   'first_name': 'Alice',
    //   'last_name': 'Smith',
    //   'email': 'alice@example.com',
    //   'phone': '+1-555-0100',
    //   'course': 'flutter_101',
    //   'enrollment_type': 'paid',
    //   'start_date': DateTime(...),
    //   'payment_method': 'credit_card',
    //   'notifications': true,
    //   'schedule': 'morning',
    // }
    enrollStudent(values);
  }
}

Working with Form Values

Reading Values

dart
// All values
final all = form.currentValues;

// Single value
final email = form.getFieldValue('email') as String?;

Setting Values

dart
// Single field
form.setFieldValue('enrollment_type', 'paid');

// Multiple fields
form.patchValues({
  'first_name': 'Bob',
  'last_name': 'Jones',
  'email': 'bob@example.com',
});

Listening for Changes

dart
form.addListener(() {
  print('Form changed: ${form.currentValues}');
  print('Valid: ${form.isValid}');
});

Using MobX Observable Validity

dart
// In a Flutter widget
Observer(
  builder: (_) => ElevatedButton(
    onPressed: form.valid.value ? _submit : null,
    child: Text('Submit'),
  ),
)

Pre-populating for Edit Mode

dart
// Load existing enrollment
final existing = await api.getEnrollment(enrollmentId);

final form = FormBuilder(title: 'Edit Enrollment')
  .text('first_name', title: 'First Name').required()
  .text('email', title: 'Email').required().email()
  // ... other fields
  .build(initialValues: {
    'first_name': existing.firstName,
    'email': existing.email,
    'course': existing.courseId,
    'enrollment_type': existing.type,
  });

Disposing

Always dispose forms when done to clean up subscriptions:

dart
@override
void dispose() {
  form.dispose();
  super.dispose();
}

Next Steps