form_ui_builder 0.0.3 copy "form_ui_builder: ^0.0.3" to clipboard
form_ui_builder: ^0.0.3 copied to clipboard

A Flutter package for building dynamic, data-driven forms with conditional visibility, auto-calculations, multi-field validation, and real-time validation support.

example/lib/main.dart

import 'dart:convert';

import 'package:example/bloc/main_bloc.dart';
import 'package:example/mock_data.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:form_ui_builder/form_ui_builder.dart';

void main() async {
  // Initialize the form_ui_builder package dependencies
  // This must be called before using any form builder features
  await FormBuilder.initialize();

  runApp(const MyApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Form UI Builder Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Form UI Builder Demo'),
    );
  }
}

class MyHomePage extends StatelessWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  void _buildFormWithFacade(BuildContext context) {
    // NEW APPROACH: Using FormBuilder facade (Recommended)
    // This is the simplest way to create a form

    // Parse JSON string to List<Map<String, dynamic>>
    final jsonData = jsonDecode(mockJsonData) as List<dynamic>;
    final List<Map<String, dynamic>> childrenJson =
        jsonData[0]['childrens'][0]['childrens'].cast<Map<String, dynamic>>();

    final result = FormBuilder.buildFromJson(
      childrenJson,
      onSubmit: (result) {
        // Handle form submission
        if (context.mounted) {
          Navigator.of(context).pop();
          _showSuccessDialog(context, result);
        }
      },
    );

    result.fold(
      (failure) {
        // Handle error
        showSnackBar(context, failure.message);
      },
      (formScreen) {
        // Navigate to form screen
        Navigator.of(
          context,
        ).push(MaterialPageRoute(builder: (_) => formScreen));
      },
    );
  }

  void _buildFormWithBloc(BuildContext context) {
    // OLD APPROACH: Using BLoC directly (More control)
    // Use this when you need more control over the BLoC lifecycle
    Navigator.of(context).push(
      MaterialPageRoute(
        builder: (context) => BlocProvider(
          create: (_) => MainBloc(),
          child: const LegacyFormBuilderExample(),
        ),
      ),
    );
  }

  void _showSuccessDialog(BuildContext context, FormSubmissionResult result) {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('Form Submitted Successfully! ✅'),
        content: SingleChildScrollView(
          child: Column(
            mainAxisSize: MainAxisSize.min,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              const Text(
                'Submitted Values:',
                style: TextStyle(fontWeight: FontWeight.bold),
              ),
              const SizedBox(height: 8),
              ...result.valueMap.entries.map((entry) {
                final fieldConfig = result.parameterMap[entry.key];
                final fieldName =
                    fieldConfig?.elementLabel.en ?? 'Field ${entry.key}';
                return Padding(
                  padding: const EdgeInsets.symmetric(vertical: 4),
                  child: Text('$fieldName: ${entry.value.value}'),
                );
              }),
            ],
          ),
        ),
        actions: [
          TextButton(
            onPressed: () => Navigator.of(context).pop(),
            child: const Text('OK'),
          ),
        ],
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(title),
      ),
      body: Center(
        child: SingleChildScrollView(
          padding: const EdgeInsets.all(24.0),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              const Icon(
                Icons.dynamic_form,
                size: 64,
                color: Colors.deepPurple,
              ),
              const SizedBox(height: 24),
              const Text(
                'Form UI Builder',
                style: TextStyle(fontSize: 28, fontWeight: FontWeight.bold),
              ),
              const SizedBox(height: 8),
              const Text(
                'Choose your approach to build dynamic forms',
                textAlign: TextAlign.center,
                style: TextStyle(fontSize: 16, color: Colors.grey),
              ),
              const SizedBox(height: 48),

              // NEW APPROACH Card
              Card(
                elevation: 4,
                child: Padding(
                  padding: const EdgeInsets.all(24.0),
                  child: Column(
                    children: [
                      const Row(
                        children: [
                          Icon(Icons.auto_awesome, color: Colors.green),
                          SizedBox(width: 8),
                          Text(
                            'New: FormBuilder Facade',
                            style: TextStyle(
                              fontSize: 18,
                              fontWeight: FontWeight.bold,
                            ),
                          ),
                          SizedBox(width: 8),
                          Chip(
                            label: Text(
                              'Recommended',
                              style: TextStyle(fontSize: 10),
                            ),
                            backgroundColor: Colors.green,
                            labelStyle: TextStyle(color: Colors.white),
                          ),
                        ],
                      ),
                      const SizedBox(height: 12),
                      const Text(
                        'Simplest way to build forms. One-line form creation with automatic BLoC setup.',
                        textAlign: TextAlign.center,
                        style: TextStyle(color: Colors.grey),
                      ),
                      const SizedBox(height: 16),
                      ElevatedButton.icon(
                        onPressed: () => _buildFormWithFacade(context),
                        icon: const Icon(Icons.rocket_launch),
                        label: const Text('Build with Facade'),
                        style: ElevatedButton.styleFrom(
                          backgroundColor: Colors.green,
                          foregroundColor: Colors.white,
                          padding: const EdgeInsets.symmetric(
                            horizontal: 32,
                            vertical: 16,
                          ),
                          textStyle: const TextStyle(fontSize: 16),
                        ),
                      ),
                      const SizedBox(height: 8),
                      const Text(
                        '70% less boilerplate code',
                        style: TextStyle(
                          fontSize: 12,
                          fontStyle: FontStyle.italic,
                          color: Colors.green,
                          fontWeight: FontWeight.bold,
                        ),
                      ),
                    ],
                  ),
                ),
              ),

              const SizedBox(height: 24),

              // OLD APPROACH Card
              Card(
                elevation: 4,
                child: Padding(
                  padding: const EdgeInsets.all(24.0),
                  child: Column(
                    children: [
                      const Row(
                        children: [
                          Icon(Icons.settings, color: Colors.blue),
                          SizedBox(width: 8),
                          Text(
                            'Classic: BLoC Approach',
                            style: TextStyle(
                              fontSize: 18,
                              fontWeight: FontWeight.bold,
                            ),
                          ),
                        ],
                      ),
                      const SizedBox(height: 12),
                      const Text(
                        'Manual BLoC setup for maximum control. Use when you need fine-grained lifecycle management.',
                        textAlign: TextAlign.center,
                        style: TextStyle(color: Colors.grey),
                      ),
                      const SizedBox(height: 16),
                      OutlinedButton.icon(
                        onPressed: () => _buildFormWithBloc(context),
                        icon: const Icon(Icons.code),
                        label: const Text('Build with BLoC'),
                        style: OutlinedButton.styleFrom(
                          padding: const EdgeInsets.symmetric(
                            horizontal: 32,
                            vertical: 16,
                          ),
                          textStyle: const TextStyle(fontSize: 16),
                        ),
                      ),
                    ],
                  ),
                ),
              ),

              const SizedBox(height: 32),
              const Divider(),
              const SizedBox(height: 16),
              const Text(
                'Both approaches produce the same form\nwith identical functionality.',
                textAlign: TextAlign.center,
                style: TextStyle(
                  fontSize: 14,
                  fontStyle: FontStyle.italic,
                  color: Colors.grey,
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

// Legacy example showing the old approach with manual BLoC setup
class LegacyFormBuilderExample extends StatelessWidget {
  const LegacyFormBuilderExample({super.key});

  @override
  Widget build(BuildContext context) {
    return BlocConsumer<MainBloc, MainState>(
      listener: (context, state) {
        if (state.status == MainStatus.failure) {
          showSnackBar(context, state.errorMessage);
        }

        if (state.formLogicBundle.parameterMap.isNotEmpty &&
            !state.hasNavigated) {
          context.read<MainBloc>().add(
            const SetHasNavigatedEvent(hasNavigated: true),
          );
          Navigator.of(context)
              .push(
                MaterialPageRoute(
                  builder: (context) => BlocProvider(
                    create: (_) => FormUiBuilderBloc(
                      buildFormLogicBundle: getIt(),
                      validateTextFieldValue: getIt(),
                      updateFieldWithDependencies: getIt(),
                      validateFieldOnBlur: getIt(),
                      updateVisibilityState: getIt(),
                      validationOnSubmit: getIt(),
                      initialFormLogicBundle: state.formLogicBundle,
                    ),
                    child: FormUiBuilderScreen(
                      onSubmit: (result) {
                        Navigator.of(context).pop(result);
                      },
                    ),
                  ),
                ),
              )
              .then((result) {
                // Reset the form state when user returns
                if (context.mounted) {
                  context.read<MainBloc>().add(const ResetFormEvent());

                  // Handle FormSubmissionResult
                  if (result != null) {
                    _showSuccessDialog(context, result);
                  }
                }
              });
        }
      },
      builder: (context, state) {
        return Scaffold(
          appBar: AppBar(title: const Text('Classic BLoC Approach')),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                const Padding(
                  padding: EdgeInsets.all(16.0),
                  child: Text(
                    'Choose Form Data Source:',
                    style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
                  ),
                ),
                const SizedBox(height: 20),
                ElevatedButton.icon(
                  onPressed: () {
                    context.read<MainBloc>().add(const GetParameterEvent());
                  },
                  icon: const Icon(Icons.code),
                  label: const Text('Build UI from JSON'),
                  style: ElevatedButton.styleFrom(
                    padding: const EdgeInsets.symmetric(
                      horizontal: 32,
                      vertical: 16,
                    ),
                    textStyle: const TextStyle(fontSize: 16),
                  ),
                ),
                const SizedBox(height: 16),
                ElevatedButton.icon(
                  onPressed: () {
                    context.read<MainBloc>().add(
                      const GetParameterFromMapEvent(),
                    );
                  },
                  icon: const Icon(Icons.map),
                  label: const Text('Build UI from Parameter Map'),
                  style: ElevatedButton.styleFrom(
                    padding: const EdgeInsets.symmetric(
                      horizontal: 32,
                      vertical: 16,
                    ),
                    textStyle: const TextStyle(fontSize: 16),
                  ),
                ),
              ],
            ),
          ),
        );
      },
    );
  }

  void _showSuccessDialog(BuildContext context, FormSubmissionResult result) {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text('Form Submitted Successfully! ✅'),
        content: SingleChildScrollView(
          child: Column(
            mainAxisSize: MainAxisSize.min,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              const Text(
                'Submitted Values:',
                style: TextStyle(fontWeight: FontWeight.bold),
              ),
              const SizedBox(height: 8),
              ...result.valueMap.entries.map((entry) {
                final fieldConfig = result.parameterMap[entry.key];
                final fieldName =
                    fieldConfig?.elementLabel.en ?? 'Field ${entry.key}';
                return Padding(
                  padding: const EdgeInsets.symmetric(vertical: 4),
                  child: Text('$fieldName: ${entry.value.value}'),
                );
              }),
            ],
          ),
        ),
        actions: [
          TextButton(
            onPressed: () => Navigator.of(context).pop(),
            child: const Text('OK'),
          ),
        ],
      ),
    );
  }
}
0
likes
0
points
47
downloads

Publisher

unverified uploader

Weekly Downloads

A Flutter package for building dynamic, data-driven forms with conditional visibility, auto-calculations, multi-field validation, and real-time validation support.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

collection, dio, equatable, flutter, flutter_bloc, fpdart, get_it, internet_connection_checker_plus, intl, synchronized

More

Packages that depend on form_ui_builder