flutter-form-builder-ecosystem / flutter_form_builder

Simple form maker for Flutter Framework
https://pub.dev/packages/flutter_form_builder
MIT License
1.49k stars 542 forks source link

[Question] Step-by-step form #523

Closed LefebvreIlyas closed 2 years ago

LefebvreIlyas commented 4 years ago

Hello !

I need to build a form composed of several fields divided into sections (steps). Since there can be many section fields, I use a ListView.builder, and each section is a page in a PageView.builder.

Here is the structure:

In order not to lose the data I store them in a buffer dictionary. But when the user returns to a section, the fields he has filled in are now empty. It would be necessary to make a setState, but this is very expensive.

Sample
Sample code ```dart import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart'; final List _sections = _generateSectionFields(); void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, home: HomePage(), ); } } class HomePage extends StatefulWidget { @override _HomePageState createState() => _HomePageState(); } class _HomePageState extends State { final GlobalKey _formKey = GlobalKey(); final Map _fieldsData = {}; @override Widget build(BuildContext context) { return Scaffold( body: Column( children: [ Expanded( child: FormBuilder( key: _formKey, onChanged: () { _formKey.currentState.fields.forEach((fieldName, fieldState) { print('$fieldName\t${fieldState.value}'); _fieldsData[fieldName] = fieldState.value; }); }, child: PageView.builder( itemCount: _sections.length, itemBuilder: (context, sectionIndex) { return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text( ' SECTION $sectionIndex / ${_sections.length}', style: Theme.of(context).textTheme.headline6, ), Expanded( child: ListView.builder( itemCount: _sections[sectionIndex], itemBuilder: (context, index) { final String name = 'TEXT_FIELD_S${sectionIndex}_I$index'; return FormBuilderTextField( name: name, decoration: InputDecoration( hintText: name, labelText: name, ), ); }, ), ) ], ); }, ), ), ), // To have enough scrollable Expanded( child: Placeholder(), ) ], ), floatingActionButton: Row( mainAxisSize: MainAxisSize.min, children: [ FloatingActionButton( child: Icon(Icons.dynamic_form), onPressed: () { showFieldsInfo( context: context, title: 'In form state', fields: _formKey.currentState.fields.map((key, fieldState) { return MapEntry(key, fieldState.value); }), ); }, ), FloatingActionButton( child: Icon(Icons.storage), tooltip: 'In map', onPressed: () { showFieldsInfo( context: context, title: 'In map', fields: _fieldsData, ); }, ), ], ), ); } } void showFieldsInfo({ BuildContext context, Map fields, String title, }) { showDialog( context: context, builder: (context) { return AlertDialog( title: Text(title), content: SizedBox( height: MediaQuery.of(context).size.height * 0.80, width: MediaQuery.of(context).size.width * 0.80, child: ListView.builder( itemCount: fields.length, itemBuilder: (BuildContext context, int index) { final String key = fields.keys.toList()[index]; final dynamic value = fields[key]; return Text('$key\t$value'); // return ListTile( // title: Text(key), // subtitle: Text(value?.toString() ?? 'NO VALUE'), // ); }, ), ), ); }, ); } List _generateSectionFields() { final Random random = new Random(); return List.generate( random.nextInt(15) + 5, (index) => random.nextInt(15) + 15, ); } ```

Does anyone have a better implementation / idea?

When the user navigates through the form through the different fields in a more advanced example, this error occurs:

Error ``` Error: A FocusNode was used after being disposed. Once you have called dispose() on a FocusNode, it can no longer be used. at Object.throw_ [as throw] (http://localhost:49921/dart_sdk.js:4193:11) at http://localhost:49921/packages/flutter/src/foundation/change_notifier.dart.lib.js:66:21 at focus_manager.FocusNode.new.[_debugAssertNotDisposed] (http://localhost:49921/packages/flutter/src/foundation/change_notifier.dart.lib.js:69:23) at focus_manager.FocusNode.new.notifyListeners (http://localhost:49921/packages/flutter/src/foundation/change_notifier.dart.lib.js:96:51) at focus_manager.FocusNode.new.[_notify] (http://localhost:49921/packages/flutter/src/widgets/widget_span.dart.lib.js:46860:12) at focus_manager.FocusManager.new.[_applyFocusChange] (http://localhost:49921/packages/flutter/src/widgets/widget_span.dart.lib.js:47674:22) at Object._microtaskLoop (http://localhost:49921/dart_sdk.js:36245:13) at _startMicrotaskLoop (http://localhost:49921/dart_sdk.js:36251:13) at http://localhost:49921/dart_sdk.js:31999:9 ```
amoslai5128 commented 3 years ago

You are right, using setState would be expensive when a Formbuilder containing many textfields in a StatefulWidget, it sometime costs you an unexpected rebuild. You can consider using state management method like States_rebuilder, Bloc.

LefebvreIlyas commented 3 years ago

Yes that's true, but I just wanted to use FormBuilder. Even if I use Cubit for the state management of the app.

nirbar89 commented 3 years ago

@LefebvreIlyas did you solve this? can you share?

LefebvreIlyas commented 3 years ago

Sorry for the late reply @nirbar89.

I use flutter_form_builder to dispatch the data in the different fields BUT I use a cubit to collect the new values of the different fields and make the validation. And so at each change I rebuild the view, which corresponds to the idea of @amoslai5128.

You can replace the cubit by something else of course, the principle remains the same.

The solution is quite reactive whether on mobile or web.

If my message is not very clear, tell me and I will make a code example to illustrate my solution.

Have a nice day.

nirbar89 commented 3 years ago

Sorry for the late reply @nirbar89.

I use flutter_form_builder to dispatch the data in the different fields BUT I use a cubit to collect the new values of the different fields and make the validation. And so at each change I rebuild the view, which corresponds to the idea of @amoslai5128.

You can replace the cubit by something else of course, the principle remains the same.

The solution is quite reactive whether on mobile or web.

If my message is not very clear, tell me and I will make a code example to illustrate my solution.

Have a nice day.

Hey, thank you very much.

I will be more then happy to see an example

amoslai5128 commented 3 years ago

Sorry for the late reply @nirbar89. I use flutter_form_builder to dispatch the data in the different fields BUT I use a cubit to collect the new values of the different fields and make the validation. And so at each change I rebuild the view, which corresponds to the idea of @amoslai5128. You can replace the cubit by something else of course, the principle remains the same. The solution is quite reactive whether on mobile or web. If my message is not very clear, tell me and I will make a code example to illustrate my solution. Have a nice day.

Hey, thank you very much.

I will be more then happy to see an example

@LefebvreIlyas @nirbar89

I've seen an example by using States_rebuilders, every textfeild has a state to rebuild independently and hooks up outside the widget tree.

Example: https://github.com/GIfatahTH/states_rebuilder/tree/master/examples/ex_002_2_form_validation_with_reactive_model_with_functional_injection

On.form(
  () => Column(
    children: <Widget>[
        TextField(
            focusNode: email.focusNode,
            controller: email.controller,
            onSubmitted: (_) {
              password.focusNode.requestFocus();
            },
        ),
        TextField(
            focusNode: password.focusNode,
            controller: password.controller,
            onSubmitted: (_) {
              form.submitFocusNode.requestFocus();
            },
        ),    
    ],
  ),
).listenTo(form),
deandreamatias commented 2 years ago

This still a issue?

deandreamatias commented 2 years ago

Due to lack of response and an old error, I will close this issue. If the bug still exists, feel free to open a new issue