flutter_formio 2.0.1 copy "flutter_formio: ^2.0.1" to clipboard
flutter_formio: ^2.0.1 copied to clipboard

Flutter widgets for rendering Form.io forms with full feature parity. Includes 46+ components, validation, wizard forms, calculations, and JavaScript support.

example/lib/main.dart

// ignore_for_file: avoid_print

import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_formio/flutter_formio.dart';

import 'custom_rating_component.dart';
import 'custom_textfield.dart';
import 'tr_localization.dart';

void main() {
  // ========================================
  // DEMO: Custom Component Registration
  // ========================================
  // This demonstrates the plugin system for registering custom components.
  // You can override default components or add completely new component types.

  // Example 1: Override the default 'textfield' component with a custom styled version
  ComponentFactory.register('textfield', const CustomTextFieldBuilder());

  // Example 2: Register a new custom component type 'rating'
  ComponentFactory.register('rating', const RatingComponentBuilder());

  // Alternative: Use FunctionComponentBuilder for simple cases
  // ComponentFactory.register(
  //   'rating',
  //   FunctionComponentBuilder((context) {
  //     return CustomRatingComponent(
  //       component: context.component,
  //       value: context.value,
  //       onChanged: context.onChanged,
  //     );
  //   }),
  // );

  // Or use initialize() to register multiple at once:
  // ComponentFactory.initialize({
  //   'textfield': const CustomTextFieldBuilder(),
  //   'rating': const RatingComponentBuilder(),
  // });

  // ========================================
  // Locale Configuration
  // ========================================
  // Use TurkishFormioLocalizations for Turkish, or create your own by implementing FormioLocalizations
  ComponentFactory.setLocale(const DefaultFormioLocalizations());

  runApp(const MyApp());
}

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

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  bool _isRedTheme = true;
  bool _isTurkish = false;

  ThemeData get _redRoundedTheme => ThemeData(
    colorScheme: ColorScheme.fromSeed(seedColor: Colors.red),
    useMaterial3: true,
    inputDecorationTheme: InputDecorationTheme(
      border: OutlineInputBorder(borderRadius: BorderRadius.circular(16)),
      enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(16), borderSide: BorderSide(color: Colors.red.shade300)),
      focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(16), borderSide: const BorderSide(color: Colors.red, width: 2)),
    ),
    elevatedButtonTheme: ElevatedButtonThemeData(style: ElevatedButton.styleFrom(shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)))),
  );

  ThemeData get _purpleSquareTheme => ThemeData(
    colorScheme: ColorScheme.fromSeed(seedColor: Colors.purple),
    useMaterial3: true,
    inputDecorationTheme: InputDecorationTheme(
      border: OutlineInputBorder(borderRadius: BorderRadius.circular(4)),
      enabledBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(4), borderSide: BorderSide(color: Colors.purple.shade300)),
      focusedBorder: OutlineInputBorder(borderRadius: BorderRadius.circular(4), borderSide: const BorderSide(color: Colors.purple, width: 2)),
    ),
    elevatedButtonTheme: ElevatedButtonThemeData(style: ElevatedButton.styleFrom(shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4)))),
  );

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Form.io Flutter Live Demo',
      theme: _isRedTheme ? _redRoundedTheme : _purpleSquareTheme,
      home: FormListPage(
        onThemeToggle: () {
          setState(() {
            _isRedTheme = !_isRedTheme;
          });
        },
        currentTheme: _isRedTheme ? 'Red Rounded' : 'Purple Square',
        onLanguageToggle: () {
          setState(() {
            _isTurkish = !_isTurkish;
            if (_isTurkish) {
              ComponentFactory.setLocale(const TurkishFormioLocalizations());
            } else {
              ComponentFactory.setLocale(const DefaultFormioLocalizations());
            }
          });
        },
        currentLanguage: _isTurkish ? 'TR' : 'EN',
      ),
    );
  }
}

class FormListPage extends StatefulWidget {
  final VoidCallback onThemeToggle;
  final String currentTheme;
  final VoidCallback onLanguageToggle;
  final String currentLanguage;

  const FormListPage({super.key, required this.onThemeToggle, required this.currentTheme, required this.onLanguageToggle, required this.currentLanguage});

  @override
  State<FormListPage> createState() => _FormListPageState();
}

class _FormListPageState extends State<FormListPage> {
  List<FormModel> forms = [];
  bool isLoading = true;
  String? errorMessage;
  bool _loadFromAssets = false;

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

  Future<void> _loadForms() async {
    setState(() {
      isLoading = true;
      errorMessage = null;
    });

    try {
      if (_loadFromAssets) {
        final String response = await rootBundle.loadString('assets/example.json');
        final List<dynamic> data = jsonDecode(response);
        final fetchedForms = data.map((e) => FormModel.fromJson(e)).toList();

        setState(() {
          forms = fetchedForms;
          isLoading = false;
        });
        print('✅ Loaded ${forms.length} forms from assets');
      } else {
        ApiClient.setBaseUrl(Uri.parse('https://examples.form.io'));
        final formService = FormService(ApiClient());
        final fetchedForms = await formService.fetchForms();

        setState(() {
          forms = fetchedForms;
          isLoading = false;
        });

        print('✅ Loaded ${forms.length} forms from API');
      }
    } catch (e) {
      setState(() {
        errorMessage = 'Failed to load forms: $e';
        isLoading = false;
      });
      print('❌ Error loading forms: $e');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Form.io Live Demo'),
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        actions: [
          IconButton(
            icon: Icon(_loadFromAssets ? Icons.cloud_download : Icons.storage),
            onPressed: () {
              setState(() {
                _loadFromAssets = !_loadFromAssets;
              });
              _loadForms();
            },
            tooltip: _loadFromAssets ? 'Switch to API' : 'Switch to Assets',
          ),
          TextButton(onPressed: widget.onLanguageToggle, child: Text(widget.currentLanguage, style: const TextStyle(fontWeight: FontWeight.bold))),
          IconButton(icon: const Icon(Icons.palette), onPressed: widget.onThemeToggle, tooltip: 'Switch Theme: ${widget.currentTheme}'),
          IconButton(icon: const Icon(Icons.refresh), onPressed: _loadForms, tooltip: 'Reload forms'),
        ],
      ),
      body: _buildBody(),
    );
  }

  Widget _buildBody() {
    if (isLoading) {
      return Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [const CircularProgressIndicator(), const SizedBox(height: 16), Text(_loadFromAssets ? 'Loading forms from Assets...' : 'Loading forms from API...')],
        ),
      );
    }

    if (errorMessage != null) {
      return Center(
        child: Padding(
          padding: const EdgeInsets.all(24.0),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              const Icon(Icons.error_outline, size: 64, color: Colors.red),
              const SizedBox(height: 16),
              Text(errorMessage!, textAlign: TextAlign.center, style: const TextStyle(color: Colors.red)),
              const SizedBox(height: 16),
              ElevatedButton.icon(onPressed: _loadForms, icon: const Icon(Icons.refresh), label: const Text('Retry')),
            ],
          ),
        ),
      );
    }

    if (forms.isEmpty) {
      return const Center(child: Text('No forms available'));
    }

    return ListView.builder(
      padding: const EdgeInsets.all(16),
      itemCount: forms.length,
      itemBuilder: (context, index) {
        final form = forms[index];
        return Card(
          margin: const EdgeInsets.only(bottom: 12),
          child: ListTile(
            leading: CircleAvatar(
              backgroundColor: Theme.of(context).colorScheme.primaryContainer,
              child: Text('${index + 1}', style: TextStyle(color: Theme.of(context).colorScheme.onPrimaryContainer, fontWeight: FontWeight.bold)),
            ),
            title: Text(form.title.isNotEmpty ? form.title : 'Untitled Form', style: const TextStyle(fontWeight: FontWeight.bold)),
            subtitle: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [const SizedBox(height: 4), Text('Path: /${form.path}'), Text('Components: ${form.components.length}')]),
            trailing: const Icon(Icons.arrow_forward_ios, size: 16),
            onTap: () => _openForm(form),
          ),
        );
      },
    );
  }

  void _openForm(FormModel form) {
    Navigator.push(context, MaterialPageRoute(builder: (_) => FormDetailPage(form: form)));
  }
}

class FormDetailPage extends StatefulWidget {
  final FormModel form;

  const FormDetailPage({super.key, required this.form});

  @override
  State<FormDetailPage> createState() => _FormDetailPageState();
}

class _FormDetailPageState extends State<FormDetailPage> {
  Map<String, dynamic> formData = {};
  bool showJson = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.form.title.isNotEmpty ? widget.form.title : 'Form'),
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        actions: [IconButton(icon: Icon(showJson ? Icons.visibility_off : Icons.code), onPressed: () => setState(() => showJson = !showJson), tooltip: showJson ? 'Hide JSON' : 'Show JSON')],
      ),
      body: showJson ? _buildJsonView() : _buildFormView(),
    );
  }

  Widget _buildFormView() {
    return SingleChildScrollView(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          // Form info card
          Card(
            color: Theme.of(context).colorScheme.primaryContainer,
            child: Padding(
              padding: const EdgeInsets.all(16),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  Text('Form Information', style: Theme.of(context).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold)),
                  const SizedBox(height: 8),
                  Text('ID: ${widget.form.id}'),
                  Text('Path: /${widget.form.path}'),
                  Text('Components: ${widget.form.components.length}'),
                ],
              ),
            ),
          ),
          const SizedBox(height: 16),

          // Form renderer with optional custom callbacks
          FormRenderer(
            form: widget.form,
            initialData: formData,
            onChanged: (data) {
              setState(() => formData = data);
              print('📝 Form data changed: ${data.keys.join(', ')}');
            },
            onSubmit: (data) {
              print('✅ Form submitted!');
              print('📦 Submission data: ${jsonEncode(data)}');

              ScaffoldMessenger.of(context).showSnackBar(
                SnackBar(
                  content: Text('Form "${widget.form.title}" submitted successfully!'),
                  backgroundColor: Colors.green,
                  action: SnackBarAction(
                    label: 'View',
                    textColor: Colors.white,
                    onPressed: () {
                      showDialog(
                        context: context,
                        builder:
                            (context) => AlertDialog(
                              title: const Text('Submission Data'),
                              content: SingleChildScrollView(child: SelectableText(const JsonEncoder.withIndent('  ').convert(data), style: const TextStyle(fontFamily: 'monospace'))),
                              actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('Close'))],
                            ),
                      );
                    },
                  ),
                ),
              );
            },
            onError: (error) {
              print('❌ Form submission error: $error');
              ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Submission failed: $error'), backgroundColor: Colors.red));
            },
          ),
        ],
      ),
    );
  }

  Widget _buildJsonView() {
    final formJson = const JsonEncoder.withIndent('  ').convert(widget.form.toJson());
    final dataJson = formData.isNotEmpty ? const JsonEncoder.withIndent('  ').convert(formData) : '';

    return SingleChildScrollView(
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.stretch,
        children: [
          Row(
            children: [
              Expanded(child: Text('Form Definition (JSON)', style: Theme.of(context).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold))),
              IconButton(
                icon: const Icon(Icons.copy),
                tooltip: 'Copy Form JSON',
                onPressed: () {
                  Clipboard.setData(ClipboardData(text: formJson));
                  ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Form JSON copied to clipboard'), duration: Duration(seconds: 2)));
                },
              ),
            ],
          ),
          const SizedBox(height: 16),
          Card(child: Padding(padding: const EdgeInsets.all(16), child: SelectableText(formJson, style: const TextStyle(fontFamily: 'monospace', fontSize: 12)))),
          const SizedBox(height: 16),
          if (formData.isNotEmpty) ...[
            Row(
              children: [
                Expanded(child: Text('Current Form Data', style: Theme.of(context).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold))),
                IconButton(
                  icon: const Icon(Icons.copy),
                  tooltip: 'Copy Form Data JSON',
                  onPressed: () {
                    Clipboard.setData(ClipboardData(text: dataJson));
                    ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Form data copied to clipboard'), duration: Duration(seconds: 2)));
                  },
                ),
              ],
            ),
            const SizedBox(height: 16),
            Card(color: Colors.green.shade50, child: Padding(padding: const EdgeInsets.all(16), child: SelectableText(dataJson, style: const TextStyle(fontFamily: 'monospace', fontSize: 12)))),
          ],
        ],
      ),
    );
  }
}
0
likes
120
points
65
downloads

Publisher

unverified uploader

Weekly Downloads

Flutter widgets for rendering Form.io forms with full feature parity. Includes 46+ components, validation, wizard forms, calculations, and JavaScript support.

Documentation

API reference

License

unknown (license)

Dependencies

dio, flutter, flutter_html, flutter_js, formio_api, intl

More

Packages that depend on flutter_formio