Getting Started

Create your first form in 5 minutes

Getting Started with Vyuh Forms

This guide will walk you through creating your first form with Vyuh Forms. We'll build a simple contact form with validation and submission handling.

Prerequisites

Before starting, ensure you have:

  1. A Flutter project (Flutter 3.8+)
  2. Basic understanding of Flutter and Dart
  3. The vyuh_feature_forms package added to your dependencies

Installation

Add vyuh_feature_forms to your pubspec.yaml:

dependencies:
  vyuh_feature_forms: ^latest_version

Run:

flutter pub get

Step 1: Create a Basic Form

Let's create a simple contact form with name, email, and message fields:

import 'package:flutter/material.dart';
import 'package:vyuh_feature_forms/vyuh_feature_forms.dart';

class ContactFormScreen extends StatefulWidget {
  @override
  State<ContactFormScreen> createState() => _ContactFormScreenState();
}

class _ContactFormScreenState extends State<ContactFormScreen> {
  late final StepForm form;

  @override
  void initState() {
    super.initState();

    form = singleFormAsStep(
      title: 'Contact Us',
      form: FormBuilder(
        title: 'Send us a message',
        fields: [
          TextField(
            name: 'name',
            label: 'Your Name',
            hint: 'Enter your full name',
            validators: [required()],
          ),
          TextField(
            name: 'email',
            label: 'Email Address',
            hint: 'your@email.com',
            validators: [required(), email()],
          ),
          TextField(
            name: 'message',
            label: 'Message',
            hint: 'Tell us how we can help...',
            multiline: true,
            maxLines: 5,
            validators: [required(), minLength(10)],
          ),
        ],
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Contact Us')),
      body: FormRenderer(
        form: form,
        onSubmit: _handleSubmit,
      ),
    );
  }

  Future<void> _handleSubmit(Map<String, dynamic> data) async {
    // Simulate API call
    await Future.delayed(Duration(seconds: 2));

    print('Form submitted with data: $data');

    // Show success message
    if (mounted) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Message sent successfully!')),
      );
    }
  }
}

Step 2: Add Validation

Forms automatically validate when the user submits. You can also validate on change:

TextField(
  name: 'email',
  label: 'Email',
  validators: [
    required(message: 'Email is required'),
    email(message: 'Please enter a valid email'),
  ],
  validateOnChange: true, // Validate as user types
)

Built-in Validators

// Required field
required(message: 'This field is required')

// Email validation
email(message: 'Please enter a valid email')

// Minimum length
minLength(10, message: 'Minimum 10 characters required')

// Maximum length
maxLength(100, message: 'Maximum 100 characters allowed')

// Pattern matching
pattern(
  r'^[0-9]{3}-[0-9]{3}-[0-9]{4}$',
  message: 'Format: 123-456-7890'
)

// Min/max value (for numbers)
min(0, message: 'Value must be at least 0')
max(100, message: 'Value cannot exceed 100')

// URL validation
url(message: 'Please enter a valid URL')

Step 3: Handle Form State

Access form state using the FormStore:

class _ContactFormScreenState extends State<ContactFormScreen> {
  late final FormStore formStore;

  @override
  void initState() {
    super.initState();
    formStore = FormStore(form);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Observer(
        builder: (_) => FormRenderer(
          form: form,
          store: formStore,
          onSubmit: _handleSubmit,
        ),
      ),
      floatingActionButton: Observer(
        builder: (_) => FloatingActionButton.extended(
          onPressed: formStore.isValid ? () => formStore.submit() : null,
          label: Text('Submit'),
          icon: Icon(Icons.send),
        ),
      ),
    );
  }
}

Step 4: Add More Field Types

Expand your form with different field types:

FormBuilder(
  title: 'User Profile',
  fields: [
    // Text Input
    TextField(
      name: 'username',
      label: 'Username',
      validators: [required()],
    ),

    // Number Input
    NumberField(
      name: 'age',
      label: 'Age',
      validators: [required(), min(18), max(120)],
    ),

    // Date Picker
    DateField(
      name: 'birthdate',
      label: 'Birth Date',
      validators: [required()],
    ),

    // Dropdown
    SelectField(
      name: 'country',
      label: 'Country',
      options: [
        Option(value: 'us', label: 'United States'),
        Option(value: 'uk', label: 'United Kingdom'),
        Option(value: 'ca', label: 'Canada'),
      ],
      validators: [required()],
    ),

    // Checkbox
    BooleanField(
      name: 'terms',
      label: 'I accept the terms and conditions',
      validators: [
        custom((value) {
          if (value != true) {
            return 'You must accept the terms';
          }
          return null;
        }),
      ],
    ),

    // Radio Group
    RadioField(
      name: 'subscription',
      label: 'Subscription Plan',
      options: [
        Option(value: 'free', label: 'Free'),
        Option(value: 'pro', label: 'Pro ($9.99/mo)'),
        Option(value: 'enterprise', label: 'Enterprise (Contact us)'),
      ],
      validators: [required()],
    ),
  ],
)

Step 5: Prefill Form Data

Initialize forms with existing data:

FormRenderer(
  form: form,
  initialData: {
    'name': 'John Doe',
    'email': 'john@example.com',
    'country': 'us',
  },
  onSubmit: _handleSubmit,
)

Step 6: Handle Errors

Display errors and handle submission failures:

Future<void> _handleSubmit(Map<String, dynamic> data) async {
  try {
    await api.submitContactForm(data);

    if (mounted) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(
          content: Text('Message sent successfully!'),
          backgroundColor: Colors.green,
        ),
      );
      Navigator.of(context).pop();
    }
  } catch (error) {
    if (mounted) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(
          content: Text('Error: ${error.toString()}'),
          backgroundColor: Colors.red,
        ),
      );
    }
  }
}

Complete Example

Here's the complete contact form:

import 'package:flutter/material.dart';
import 'package:vyuh_feature_forms/vyuh_feature_forms.dart';
import 'package:flutter_mobx/flutter_mobx.dart';

class ContactFormScreen extends StatefulWidget {
  @override
  State<ContactFormScreen> createState() => _ContactFormScreenState();
}

class _ContactFormScreenState extends State<ContactFormScreen> {
  late final StepForm form;
  late final FormStore formStore;

  @override
  void initState() {
    super.initState();

    form = singleFormAsStep(
      title: 'Contact Us',
      form: FormBuilder(
        title: 'Send us a message',
        description: 'We\'ll get back to you within 24 hours',
        fields: [
          TextField(
            name: 'name',
            label: 'Your Name',
            hint: 'Enter your full name',
            validators: [required()],
          ),
          TextField(
            name: 'email',
            label: 'Email Address',
            hint: 'your@email.com',
            validators: [required(), email()],
          ),
          SelectField(
            name: 'topic',
            label: 'Topic',
            options: [
              Option(value: 'support', label: 'Technical Support'),
              Option(value: 'sales', label: 'Sales Inquiry'),
              Option(value: 'feedback', label: 'Feedback'),
              Option(value: 'other', label: 'Other'),
            ],
            validators: [required()],
          ),
          TextField(
            name: 'message',
            label: 'Message',
            hint: 'Tell us how we can help...',
            multiline: true,
            maxLines: 5,
            validators: [required(), minLength(10)],
          ),
        ],
      ),
    );

    formStore = FormStore(form);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Contact Us'),
        centerTitle: true,
      ),
      body: Observer(
        builder: (_) => FormRenderer(
          form: form,
          store: formStore,
          onSubmit: _handleSubmit,
        ),
      ),
    );
  }

  Future<void> _handleSubmit(Map<String, dynamic> data) async {
    try {
      // Simulate API call
      await Future.delayed(Duration(seconds: 2));

      print('Contact form submitted: $data');

      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text('Message sent successfully!'),
            backgroundColor: Colors.green,
          ),
        );
        Navigator.of(context).pop();
      }
    } catch (error) {
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text('Error: ${error.toString()}'),
            backgroundColor: Colors.red,
          ),
        );
      }
    }
  }

  @override
  void dispose() {
    formStore.dispose();
    super.dispose();
  }
}

What's Next?

Now that you've created your first form, explore more advanced features:

On this page