flutter-form-builder-ecosystem / flutter_form_builder

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

[FormBuilderDropdown]: <If onChanged Function is added, the FormBuilderDropdown stop working> #1252

Closed CarmonaCarlos closed 1 year ago

CarmonaCarlos commented 1 year ago

Is there an existing issue for this?

Package/Plugin version

9.0.0

Flutter doctor

Flutter doctor [√] Flutter (Channel stable, 3.10.0, on Microsoft Windows [Version 10.0.22621.1702], locale es-MX) • Flutter version 3.10.0 on channel stable at C:\Users\Carlos\flutter_sdk\flutter • Upstream repository https://github.com/flutter/flutter.git • Framework revision 84a1e904f4 (6 days ago), 2023-05-09 07:41:44 -0700 • Engine revision d44b5a94c9 • Dart version 3.0.0 • DevTools version 2.23.1 [√] Windows Version (Installed version of Windows is version 10 or higher) [√] Android toolchain - develop for Android devices (Android SDK version 31.0.0) • Android SDK at C:\Users\Carlos\AppData\Local\Android\sdk • Platform android-33, build-tools 31.0.0 • Java binary at: C:\Program Files\Android\Android Studio\jbr\bin\java • Java version OpenJDK Runtime Environment (build 17.0.6+0-b2043.56-9586694) • All Android licenses accepted. [√] Chrome - develop for the web • Chrome at C:\Program Files\Google\Chrome\Application\chrome.exe [√] Visual Studio - develop for Windows (Visual Studio Community 2022 17.5.5) • Visual Studio at C:\Program Files\Microsoft Visual Studio\2022\Community • Visual Studio Community 2022 version 17.5.33627.172 • Windows 10 SDK version 10.0.22621.0 [√] Android Studio (version 2022.2) • Android Studio at C:\Program Files\Android\Android Studio • Flutter plugin can be installed from: https://plugins.jetbrains.com/plugin/9212-flutter • Dart plugin can be installed from: https://plugins.jetbrains.com/plugin/6351-dart • Java version OpenJDK Runtime Environment (build 17.0.6+0-b2043.56-9586694) [√] VS Code (version 1.78.2) • VS Code at C:\Users\Carlos\AppData\Local\Programs\Microsoft VS Code • Flutter extension version 3.64.0 [√] Connected device (3 available) • Windows (desktop) • windows • windows-x64 • Microsoft Windows [Version 10.0.22621.1702] • Chrome (web) • chrome • web-javascript • Google Chrome 113.0.5672.93 • Edge (web) • edge • web-javascript • Microsoft Edge 113.0.1774.42 [√] Network resources • All expected network resources are available. • No issues found!

Minimal code example

Code sample ```dart import 'package:flutter/material.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:form_builder_validators/form_builder_validators.dart'; void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({Key? key}) : super(key: key); @override Widget build(BuildContext context) { return const MaterialApp( title: 'Flutter FormBuilder Demo', debugShowCheckedModeBanner: false, localizationsDelegates: [ FormBuilderLocalizations.delegate, ...GlobalMaterialLocalizations.delegates, GlobalWidgetsLocalizations.delegate, ], supportedLocales: FormBuilderLocalizations.supportedLocales, home: Example(), ); } } class Example extends StatefulWidget { const Example({Key? key}) : super(key: key); @override State createState() => _ExampleState(); } class _ExampleState extends State { int? selectedLineId; final GlobalKey _formKey = GlobalKey(); late List lines; @override void initState() { lines = _initLines(); super.initState(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Example')), body: FormBuilder( key: _formKey, child: Column( children: [ FormBuilderDropdown( name: 'lineId', valueTransformer: (int? val) => val?.toInt(), decoration: const InputDecoration( labelText: 'lines', icon: Icon(Icons.ad_units)), validator: FormBuilderValidators.compose( [FormBuilderValidators.required()]), items: lines .map((Line item) => DropdownMenuItem( value: item.id, child: Text(item.name), )) .toList(), onChanged: (int? value) { setState(() { selectedLineId = value; }); }, ) ], ), ), ); } List _initLines() { //This is just for test purpose return [ Line( id: 1, name: 'Test', ), Line( id: 2, name: 'Test 2', ) ]; } } class Line { final int id; final String name; Line({required this.id, required this.name}); } ```

Current Behavior

If I add setState inside of onChanged the FormBuilderDropdown is not working correctly:

If I select an option:

01

Then nothing is selected:

02

On previous version it was working once I update to 9.0.0 it stop to works.

Expected Behavior

If I select an option

For example:

01

Then the option is selected:

03

Steps To Reproduce

  1. Add setState to onChanged

Aditional information

No response

bull-xu commented 1 year ago

The setState triggers the rebuild. and a break point to the didUpdateWidget showing the initialValue has been set to value:

image

To fix the issue, a walkaround is to set the items at initState.

deandreamatias commented 1 year ago

I test the example code on web with Flutter 3.10 and works fine. Please edit minimal example code or add more details about the bug

eaedev commented 1 year ago

Same problem here. If you use a setState in onChanged, then the widget get rebuilds and it jumps to the initial value

tetious commented 1 year ago

@deandreamatias I think the key here is that the form needs to rebuild to trigger. It probably won't happen as easily on the web, but on mobile the widget rebuilds when the input/keyboard changes, and that is pretty common on a form.

If you pick something in the dropdown (keyboard goes away) and then move focus to a widget that reopens the keyboard, the selected item dissapears.

GiuseppeSperanza commented 1 year ago

Yes, same problem. Any update?

mngphm commented 1 year ago

same problem, will this be fixed? or any workaround?

tetious commented 1 year ago

My workaround (which may not be the best, but seems to work) is to set the model prop value manually in onChanged, kinda like this:

onChanged: (value) => YOUR_MODEL.whateverProp = value,

ncuillery commented 1 year ago

More generally, rebuilding the widget where the FormBuilderDropdown is declared causes the following error if the items prop is reassigned:

The following assertion was thrown building MyForm:
setState() or markNeedsBuild() called during build.

This Form widget cannot be marked as needing to build because the framework is already in the process of building widgets. A widget can be marked as needing to be built during the build phase only if one of its ancestors is currently building. This exception is allowed because the framework builds parent widgets before children, which means a dirty descendant will always be built. Otherwise, the framework might not visit this widget during this build phase.

The workaround consists in keeping the items array (the same reference) by declaring the array of DropdownMenuItem in the initState, like @bull-xu mentioned for instance

Dubemtopsite commented 1 year ago

Facing same issue while using setState to update some variables from onChanged

Dubemtopsite commented 1 year ago

My workaround (which may not be the best, but seems to work) is to set the model prop value manually in onChanged, kinda like this:

onChanged: (value) => YOUR_MODEL.whateverProp = value,

Had to create a model for my form and update the model from the onChanged function

hallmayer commented 1 year ago

Same here. Any update?

godofsleepy commented 1 year ago

https://github.com/flutter-form-builder-ecosystem/flutter_form_builder/assets/45101689/375ff586-6b57-484b-8ff2-a20ae3b788c1

Version 9.0.0 Got same error when on change trigger GetX builder(form key.did change -> getx). then when i downgrade to version 8.0.0 its fixed, any suggest ?

ashburger commented 1 year ago

Version 9.0.0

(On Android) I get a similar issue, where if I use a dynamic list for items, when setState is called in the form onChanged, the dropdown with the dynamic list is treated as invalid. It should be noted, that if you check the form's formKey.currentstate.fields['dynamicDropdown'].value, it does show the correct value, and the list itself displays correctly. Another issue is that if in the form, the first entry you change is the dynamic dropdown, and setstate is called, the dropdown resets itself, but only once, if you select an item in it again, it displays the selection correctly, but is still considered invalid.

EDIT: with this block WidgetsBinding.instance.addPostFrameCallback((_) { if (_formKey.currentState?.isValid ?? false) { setState(() { //todo }); } } the dynamic dropdown is always reset These issues don't affect dropdowns with const item lists

ShashankSMayya commented 1 year ago

Any updates on this? in Form builder 9.0 this issue is persistent. The form builder values reset to initial values on reset. For now my solution is to use Form Builder 8.0.

Is it something to do with this changelog point?

latenite-ironman commented 1 year ago

I hope this will be fixed soon. because of this issue, we cannot update to the latest flutter version 3.10.

ShashankSMayya commented 1 year ago

I hope this will be fixed soon. because of this issue, we cannot update to the latest flutter version 3.10.

You can override dependency to upgrade to latest version of flutter, I am using 3.10 with other package with overridden intl package.

Anyways I hope the team solves this issue soon.

danvick commented 1 year ago

The setState triggers the rebuild. and a break point to the didUpdateWidget showing the initialValue has been set to value:

image

To fix the issue, a walkaround is to set the items at initState.

@deandreamatias, I believe the bug originates from the didUpdateWidget() function as highlighted by @bull-xu.

I understand the reason for adding the functionality was to avoid the failed assertion where if the items List is dynamically changed and the new list doesn't contain the already selected value.

I think the method should be removed because of the following reasons:

  1. We are not performing a deep equality comparison, therefore, widget.items != oldWidget.items will almost always return true. Remember that in Dart [1,2,3] != [1,2,3] is true!
  2. Even if we did deep comparison of the list and the lists are of non-primitive types which don't override the equality operator, the comparison would still fail.
  3. If the initialValue of the field is non-null but it's not part of the new updated items List, the assertion will still fail.
  4. Since what we are trying to avoid is just an assertion, we can let the users of the library handle that.
Rileyjrjohns commented 1 year ago

Hello, any updates on the subject? It's quite disturbing. For example, I wanted to create a pseudonym selector with an "Other" field that would display a text field conditionally. Set State doesn't work, but neither does riverpod (which works on SetState) for this type of task. It's a pity, because there's no way out for this kind of functionality...

deandreamatias commented 1 year ago

Hi people! Sorry for can't take a look on this issue before. I had a lot of things happening and also had a bug with my Android install and emulator, that I solved today (3 AM). This issue is my first priority and I expect work on bug ASAP.

All the comments that you have added and can add, will be useful for debug. Thanks for patience

deandreamatias commented 1 year ago

@deandreamatias I think the key here is that the form needs to rebuild to trigger. It probably won't happen as easily on the web, but on mobile the widget rebuilds when the input/keyboard changes, and that is pretty common on a form.

If you pick something in the dropdown (keyboard goes away) and then move focus to a widget that reopens the keyboard, the selected item dissapears.

I think that the problem is related when use autovalidateMode: AutovalidateMode.onUserInteraction

deandreamatias commented 1 year ago

I opened a PR. Please if all of you can review this PR and considerations on it, I will really appreciate it.