from_data 0.3.4 copy "from_data: ^0.3.4" to clipboard
from_data: ^0.3.4 copied to clipboard

Flutter library for generating dynamic forms driven by schema data with automatic field rendering, validation, and state management.

from_data #

A powerful Flutter library for building dynamic, schema-driven forms with automatic field rendering, validation, and state management. Define your forms using JSON or Dart maps and get a fully functional, production-ready form UI.

What is from_data? #

from_data is a schema-driven form builder that allows you to:

  • Define forms declaratively using JSON or Dart maps instead of writing repetitive form code
  • Generate UI automatically from schema definitions
  • Manage form state with a built-in controller
  • Validate inputs with flexible validation rules
  • Support any data type (String, int, Map, List, Object, etc.) without restrictions
  • Extend and customize with custom field builders

Use Cases #

1. Dynamic Forms from API/Schema #

Build forms based on schema data received from your backend:

// Load form schema from API
final schemaResponse = await http.get('/api/form-schema');
final schema = FormSchema.fromJson(jsonDecode(schemaResponse.body));

// Render form automatically
FromDataForm(schema: schema)

2. Multi-step Registration/Wizard Forms #

Create complex multi-step forms with different fields per step:

final step1Schema = FormSchema.fromJson(step1Config);
final step2Schema = FormSchema.fromJson(step2Config);
// Easy to switch between steps

3. Configuration Forms #

Build admin panels or settings forms where field structure changes dynamically:

// Form structure depends on user role or permissions
final fields = getFieldsForUser(userRole);
final schema = FormSchema(fields: fields);

4. Data Collection Apps #

Create surveys, questionnaires, or data entry forms with various field types:

// Survey with text, dropdowns, checkboxes, dates
final surveySchema = FormSchema.fromJson(surveyConfig);

5. CRUD Forms #

Build create/edit forms with conditional fields and validation:

// Same schema for create and edit, just different initial values
FromDataForm(
  schema: formSchema,
  initialValues: editMode ? existingData : null,
)

Features #

  • 12 Field Types: text, number, email, password, multiline, dropdown, checkbox, switch, date, time, datetime
  • Universal Data Type Support: Accepts any data type (String, int, double, bool, Map, List, Object, etc.) without restrictions
  • React Native-style API: Convenient methods like append(), get(), getAll(), set(), delete(), has(), clear()
  • Automatic State Management: Built-in controller handles form values, validation, and change notifications
  • Flexible Validation: Min/max length, min/max value, pattern matching, and custom validators
  • Customizable: Override field rendering with custom builders and layout hooks
  • Read-only Mode: Display forms in view-only mode
  • Auto-validation: Configure when validation triggers (on user interaction, on submit, etc.)

Getting Started #

Installation #

Add from_data to your pubspec.yaml:

flutter pub add from_data

Or manually:

dependencies:
  from_data: ^0.2.0

Then import it:

import 'package:from_data/from_data.dart';

Usage Guide #

Basic Usage #

Create a form schema and render it:

final schema = FormSchema.fromJson({
  'title': 'User Profile',
  'description': 'Please fill in your information',
  'fields': [
    {
      'id': 'name',
      'label': 'Full Name',
      'type': 'text',
      'required': true,
      'validations': [
        {
          'type': 'minLength',
          'value': 3,
          'message': 'Name must be at least 3 characters',
        },
      ],
    },
    {
      'id': 'email',
      'label': 'Email Address',
      'type': 'email',
      'required': true,
      'validations': [
        {
          'type': 'pattern',
          'pattern': r'^[^@]+@[^@]+\.[^@]+$',
          'message': 'Please enter a valid email',
        },
      ],
    },
    {
      'id': 'newsletter',
      'label': 'Subscribe to newsletter',
      'type': 'switcher',
      'initialValue': true,
    },
  ],
});

class MyForm extends StatelessWidget {
  const MyForm({super.key});

  @override
  Widget build(BuildContext context) {
    return FromDataForm(
      schema: schema,
      onChanged: (values) {
        print('Form values changed: $values');
      },
      autovalidateMode: AutovalidateMode.onUserInteraction,
    );
  }
}

Using Form Controller #

Access and manipulate form values programmatically:

class MyFormPage extends StatefulWidget {
  const MyFormPage({super.key});

  @override
  State<MyFormPage> createState() => _MyFormPageState();
}

class _MyFormPageState extends State<MyFormPage> {
  final _formKey = GlobalKey<FromDataFormState>();
  late final FromDataController _controller;

  @override
  void initState() {
    super.initState();
    _controller = FromDataController();
  }

  void _handleSubmit() {
    if (_formKey.currentState?.validate() ?? false) {
      final values = _formKey.currentState!.values;
      // Submit values to API
      print('Submitting: $values');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: FromDataForm(
        key: _formKey,
        controller: _controller,
        schema: schema,
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _handleSubmit,
        child: const Icon(Icons.send),
      ),
    );
  }
}

Field Types #

Text Fields

{
  'id': 'name',
  'label': 'Name',
  'type': 'text',          // Single-line text
  // or 'email'            // Email keyboard
  // or 'password'         // Password (obscured)
  // or 'multiline'        // Multi-line text
}

Number Field

{
  'id': 'age',
  'label': 'Age',
  'type': 'number',
  'validations': [
    {'type': 'min', 'value': 18, 'message': 'Must be 18+'},
    {'type': 'max', 'value': 100, 'message': 'Must be under 100'},
  ],
}

Date/Time Pickers

{
  'id': 'birthDate',
  'label': 'Birth Date',
  'type': 'date',          // Date picker
  // or 'time'             // Time picker
  // or 'datetime'         // Date & Time picker
}
{
  'id': 'country',
  'label': 'Country',
  'type': 'dropdown',
  'options': [
    {'value': 'us', 'label': 'United States'},
    {'value': 'uk', 'label': 'United Kingdom'},
    {'value': 'ca', 'label': 'Canada'},
  ],
}

Checkbox & Switch

{
  'id': 'acceptTerms',
  'label': 'I accept the terms',
  'type': 'checkbox',      // Checkbox
  // or 'switcher'         // Switch/Toggle
  'required': true,
}

Validation #

Built-in Validation Types

{
  'validations': [
    // Minimum length
    {
      'type': 'minLength',
      'value': 5,
      'message': 'Must be at least 5 characters',
    },
    // Maximum length
    {
      'type': 'maxLength',
      'value': 50,
      'message': 'Must be less than 50 characters',
    },
    // Minimum value (for numbers)
    {
      'type': 'min',
      'value': 0,
      'message': 'Must be positive',
    },
    // Maximum value (for numbers)
    {
      'type': 'max',
      'value': 100,
      'message': 'Must be less than 100',
    },
    // Pattern matching (regex)
    {
      'type': 'pattern',
      'pattern': r'^[A-Z][a-z]+$',
      'message': 'Must start with uppercase letter',
    },
  ],
}

Required Fields

{
  'id': 'email',
  'label': 'Email *',
  'type': 'email',
  'required': true,  // Field is required
}

React Native-style API #

The controller provides methods similar to React Native form:

final controller = FromDataController();

// Append values to fields (auto-creates lists)
controller.append('tags', 'flutter');
controller.append('tags', 'dart');  
// Result: tags = ['flutter', 'dart']

// Get values
final tags = controller.get('tags');          // Get single field
final allData = controller.getAll();          // Get all fields

// Set values
controller.set('name', 'John');
controller.set('age', 30);

// Check existence
if (controller.has('name')) {
  print('Name exists: ${controller.get('name')}');
}

// Delete field
controller.delete('age');

// Clear fields
controller.clear(fieldIds: ['tags']);  // Clear specific fields
controller.clear();                     // Clear all fields

Working with Any Data Type #

final schema = FormSchema.fromJson({
  'fields': [
    {
      'id': 'country',
      'label': 'Country',
      'type': 'dropdown',
      'options': [
        {
          'value': {'code': 'us', 'name': 'United States'},  // Object value
          'label': 'United States',
        },
        {
          'value': {'code': 'uk', 'name': 'United Kingdom'},
          'label': 'United Kingdom',
        },
      ],
    },
  ],
});

// Get selected value (entire object)
final country = controller.get('country');
print(country);  // {'code': 'us', 'name': 'United States'}
{
  'id': 'planId',
  'label': 'Plan ID',
  'type': 'dropdown',
  'options': [
    {'value': 1, 'label': 'Basic'},      // Integer value
    {'value': 2, 'label': 'Premium'},
    {'value': 3, 'label': 'Enterprise'},
  ],
}

Setting Values from Objects

final user = {
  'firstName': 'John',
  'lastName': 'Doe',
  'age': 30,
  'address': {'street': '123 Main St', 'city': 'NYC'},
};

// Extract and map values
controller.setValuesFromObject(user, {
  'name': (obj) => '${obj['firstName']} ${obj['lastName']}',
  'age': (obj) => obj['age'],
  'address': (obj) => obj['address'],  // Store entire object
});

// Get typed values
final address = controller.getAs<Map>('address');
final age = controller.getAs<int>('age');

Creating Options from Objects

final users = [
  {'id': 1, 'name': 'John'},
  {'id': 2, 'name': 'Jane'},
  {'id': 3, 'name': 'Bob'},
];

// Convert to FormFieldOptions
final options = users.map((user) => FormFieldOption.fromObject(
  value: user,              // Store entire object
  labelKey: 'name',         // Use 'name' as label
)).toList();

Pre-populating Form Values #

final initialValues = {
  'name': 'John Doe',
  'email': 'john@example.com',
  'age': 30,
  'plan': 'premium',
};

FromDataForm(
  schema: schema,
  initialValues: initialValues,  // Pre-fill form
  onChanged: (values) {
    print('Current values: $values');
  },
)

Form Submission #

class MyForm extends StatefulWidget {
  const MyForm({super.key});

  @override
  State<MyForm> createState() => _MyFormState();
}

class _MyFormState extends State<MyForm> {
  final _formKey = GlobalKey<FromDataFormState>();

  void _handleSubmit() async {
    final formState = _formKey.currentState;
    if (formState == null) return;

    // Validate form
    if (formState.validate()) {
      final values = formState.values;
      
      // Submit to API
      try {
        final response = await http.post(
          Uri.parse('/api/submit'),
          body: jsonEncode(values),
        );
        
        if (response.statusCode == 200) {
          ScaffoldMessenger.of(context).showSnackBar(
            const SnackBar(content: Text('Form submitted successfully!')),
          );
        }
      } catch (e) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('Error: $e')),
        );
      }
    } else {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text('Please fix the errors')),
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: FromDataForm(
        key: _formKey,
        schema: schema,
        autovalidateMode: AutovalidateMode.onUserInteraction,
      ),
      bottomNavigationBar: Padding(
        padding: const EdgeInsets.all(16),
        child: FilledButton(
          onPressed: _handleSubmit,
          child: const Text('Submit'),
        ),
      ),
    );
  }
}

Custom Field Builders #

Override rendering for specific field types:

FromDataForm(
  schema: schema,
  customBuilders: {
    FormFieldType.text: (context, fieldContext) {
      // Custom text field widget
      return TextFormField(
        initialValue: fieldContext.value?.toString(),
        decoration: InputDecoration(
          labelText: fieldContext.schema.label,
          prefixIcon: const Icon(Icons.person),
        ),
        onChanged: fieldContext.onChanged,
      );
    },
  },
)

Custom Layout Builder #

Customize field layout:

FromDataForm(
  schema: schema,
  fieldLayoutBuilder: (context, schema, child) {
    // Wrap each field with custom layout
    return Card(
      margin: const EdgeInsets.symmetric(vertical: 8),
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: child,
      ),
    );
  },
)

Read-only Mode #

Display form in view-only mode:

FromDataForm(
  schema: schema,
  readOnly: true,  // All fields become read-only
  initialValues: existingData,
)

Advanced Examples #

See the complete example app in example/lib/main.dart for:

  • Comprehensive form with all field types
  • Date/Time picker demos
  • React Native-style API interactive demo
  • Universal data type support examples

Run the example:

cd example
flutter run

API Reference #

FormSchema #

Main schema class for defining form structure.

FormSchema({
  required List<FormFieldSchema> fields,
  String? title,
  String? description,
  Map<String, dynamic> metadata = const {},
})

// From JSON
FormSchema.fromJson(Map<String, dynamic> json)
FormSchema.fromJsonString(String jsonString)

FormFieldSchema #

Defines a single form field.

FormFieldSchema({
  required String id,
  required String label,
  required FormFieldType type,
  String? hint,
  String? description,
  bool required = false,
  List<FormFieldOption> options = const [],
  List<ValidationRule> validations = const [],
  bool readOnly = false,
  String? placeholder,
  Map<String, dynamic> metadata = const {},
  dynamic initialValue,
})

FromDataController #

Manages form state and values.

FromDataController({
  FormSchema? schema,
  Map<String, dynamic>? values,
})

// Methods
dynamic get(String fieldId)
Map<String, dynamic> getAll()
void set(String fieldId, dynamic value)
void append(String fieldId, dynamic value)
void delete(String fieldId)
bool has(String fieldId)
void clear({List<String>? fieldIds})
T? getAs<T>(String fieldId)
void setValuesFromList<T>(List<T> items, Map<String, dynamic> Function(T, int) mapper)
void setValuesFromObject<T>(T object, Map<String, dynamic Function(T)> fieldMap)

FromDataForm #

Widget that renders the form.

FromDataForm({
  required FormSchema schema,
  FromDataController? controller,
  Map<String, dynamic>? initialValues,
  ValueChanged<Map<String, dynamic>>? onChanged,
  FieldLayoutBuilder? fieldLayoutBuilder,
  Map<FormFieldType, FromDataCustomBuilder>? customBuilders,
  AutovalidateMode autovalidateMode = AutovalidateMode.disabled,
  bool scrollable = true,
  EdgeInsetsGeometry? padding,
  bool readOnly = false,
})

Additional Information #

We welcome contributions and ideas! Feel free to open an issue or pull request.

  • Example app: example/lib/main.dart
  • Report issues: Open a GitHub issue in this repository
  • License: See LICENSE
11
likes
140
points
18
downloads

Publisher

unverified uploader

Weekly Downloads

Flutter library for generating dynamic forms driven by schema data with automatic field rendering, validation, and state management.

Homepage
Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

flutter

More

Packages that depend on from_data