Custom Widget Dynamic Form Not Capturing User Inputs

Custom Code

I'm working on creating a dynamic form using a custom widget, envisioning a user experience similar to that of Google Forms. The form elements are dynamically generated from a list of definitions stored in a Firestore document, which is then passed to the dynamic page. However, I'm encountering issues with capturing the user's inputs. Upon form submission, the app state updates but only reflects the initial form definitions, omitting the user's inputs. Note that I use the terms "checklist" and "form" interchangeably, which might clarify any confusing terminology. I have included what I believe will be most helpful without overwhelming you. I hope I have provided sufficient context, but if not, please let me know. Here's my current implementation of the DynamicFormListView custom widget (excluded from compilation)


// Automatic FlutterFlow imports
import '/backend/backend.dart';
import '/backend/schema/structs/index.dart';
import '/actions/actions.dart' as action_blocks;
import '/flutter_flow/flutter_flow_theme.dart';
import '/flutter_flow/flutter_flow_util.dart';
import '/custom_code/widgets/index.dart'; // Imports other custom widgets
import '/custom_code/actions/index.dart'; // Imports custom actions
import '/flutter_flow/custom_functions.dart'; // Imports custom functions
import 'package:flutter/material.dart';
// Begin custom widget code
// DO NOT REMOVE OR MODIFY THE CODE ABOVE!

import '/components/short_text_input/short_text_input_widget.dart';
import '/components/radio_input/radio_input_widget.dart';
import '/custom_code/actions/capture_form_data.dart';

class DynamicFormListView extends StatefulWidget {
  const DynamicFormListView({
    Key? key,
    this.width,
    this.height,
    required this.formElements,
  }) : super(key: key);

  final double? width;
  final double? height;
  final List<dynamic> formElements;

  @override
  State<DynamicFormListView> createState() => _DynamicFormListViewState();
}

class _DynamicFormListViewState extends State<DynamicFormListView> {
  List<dynamic> _formData = [];

  @override
  void initState() {
    super.initState();
    _formData = List.from(widget.formElements);
    print('Initial form data: $_formData'); // Debug print
  }

  Future<void> _updateFormData(int index, String value) async {
    print('Updating form data: index=$index, value=$value'); // Debug print
    if (index >= 0 && index < _formData.length) {
      setState(() {
        _formData[index] = {
          ..._formData[index],
          'value': value,
        };
      });
      print('Updated form data: $_formData'); // Debug print
    } else {
      print('Invalid index: $index'); // Debug print
    }
  }

  Future<void> _submitForm() async {
    try {
      print('Submitting form data: $_formData'); // Debug print
      await captureFormData(_formData);
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('Form submitted successfully!')),
        );
      }
    } catch (e) {
      print('Error submitting form: $e');
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(content: Text('Error submitting form. Please try again.')),
        );
      }
    }
  }

  Widget _buildFormElement(Map<String, dynamic> element, int index) {
    print('Building form element: $element'); // Debug print
    switch (element['type']) {
      case 'short_text':
        return ShortTextInputWidget(
          label: element['label'] ?? '',
          required: element['required'] ?? false,
          id: element['id'] ?? '',
          onChanged: (value) async {
            print('ShortTextInput changed: $value'); // Debug print
            await _updateFormData(index, value);
          },
        );
      case 'radio':
        return RadioInputWidget(
          label: element['label'] ?? '',
          options: List<String>.from(element['options'] ?? []),
          required: element['required'] ?? false,
          id: element['id'] ?? '',
          onChanged: (value) async {
            print('RadioInput changed: $value'); // Debug print
            await _updateFormData(index, value);
          },
        );
      default:
        return Text('Unsupported element type: ${element['type']}');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      width: widget.width,
      height: widget.height,
      child: Column(
        children: [
          Expanded(
            child: ListView.builder(
              itemCount: _formData.length,
              itemBuilder: (context, index) {
                final element = _formData[index];
                return _buildFormElement(element, index);
              },
            ),
          ),
          TextButton(
            onPressed: _submitForm,
            child: Text('Submit'),
          ),
        ],
      ),
    );
  }
}

And here's the implementation of the captureFormData custom action


// Automatic FlutterFlow imports
import '/backend/backend.dart';
import '/backend/schema/structs/index.dart';
import '/actions/actions.dart' as action_blocks;
import '/flutter_flow/flutter_flow_theme.dart';
import '/flutter_flow/flutter_flow_util.dart';
import '/custom_code/actions/index.dart'; // Imports other custom actions
import '/flutter_flow/custom_functions.dart'; // Imports custom functions
import 'package:flutter/material.dart';
// Begin custom action code
// DO NOT REMOVE OR MODIFY THE CODE ABOVE!

Future captureFormData(List<dynamic> formData) async {
  print(
      'App state before update: ${FFAppState().dynamicFormData}'); // Debug print
  FFAppState().update(() {
    FFAppState().dynamicFormData = formData;
  });
  print(
      'App state after update: ${FFAppState().dynamicFormData}'); // Debug print
}

Here is how a checklist document is structured in the checklists collection


{
  "etqUaJ7QJqbTfpdAtx0M": {
    "creator": {
      "__ref__": "fh_users/05b4mZGsbWWikLo0okr3nQqfHAF3"
    },
    "description": "description",
    "elements": [
      {
        "id": "123",
        "label": "Name",
        "order": 1,
        "required": true,
        "type": "short_text"
      },
      {
        "id": "124",
        "label": "Gender",
        "options": ["Male", "Female", "Other"],
        "order": 1,
        "required": true,
        "type": "radio"
      },
      {
        "id": "12555",
        "label": "Here is your next short text input",
        "order": 3,
        "required": true,
        "type": "short_text"
      },
      {
        "id": "18829i",
        "label": "Category",
        "options": ["IT", "Other", "HVAC", "Equipment", "Pools"],
        "order": 1,
        "required": true,
        "type": "radio"
      }
    ],
    "title": "Test Checklist"
  }
}

When I submit the form, the app state updates, yet it only retains the initial form definitions and omits the user's inputs. The debug prints confirm that the `_formData` within the widget updates accurately, but this update does not appear to be mirrored in the final app state. I have attached four screenshots: 1. The configuration of the custom widget parameters and the integration of the form elements (which functions correctly). 2. The setup of the component's parameters, particularly the callback action, for the two form elements. 3. The state before pressing the submit button on the form, including browser console log output. 4. The state after pressing the submit button on the form, also including browser console log output.

What have you tried so far?
  1. I have tried adjusting callback action component parameter for the form elements (shortText and Radio), but I really don't know what to do with that and how to set that up so it matches the logic in the code

  2. I've added debug prints throughout the code to track the state of the form data.

  3. I've verified that the `_updateFormData` function is being called and updating the local dynamicFormData state.

  4. I've checked that the `captureFormData` function is being called with the updated dynamicFormData

Did you check FlutterFlow's Documentation for this topic?
Yes
1