Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FormBuilderField & FormBuilder]: Unexpected retention of initialValue when coerced widget recreation #1388

Open
2 of 7 tasks
nvshah opened this issue May 3, 2024 · 0 comments
Labels
bug Something isn't working

Comments

@nvshah
Copy link

nvshah commented May 3, 2024

Is there an existing issue for this?

  • I have searched the existing issues

Package/Plugin version

9.2.1

Platforms

  • Android
  • iOS
  • Linux
  • MacOS
  • Web
  • Windows

Flutter doctor

Flutter doctor
[√] Flutter (Channel stable, 3.19.6, on Microsoft Windows [Version 10.0.22631.3447], locale en-IN)
    • Flutter version 3.19.6 on channel stable at C:\src\flutter
    • Upstream repository https://github.com/flutter/flutter.git
    • Framework revision 54e66469a9 (2 weeks ago), 2024-04-17 13:08:03 -0700
    • Engine revision c4cd48e186
    • Dart version 3.3.4
    • DevTools version 2.31.1

[√] Windows Version (Installed version of Windows is version 10 or higher)

[√] Android toolchain - develop for Android devices (Android SDK version 33.0.1)
    • Android SDK at C:\Users\Nipun Shah\AppData\Local\Android\sdk
    • Platform android-33-ext4, build-tools 33.0.1
    • Java binary at: C:\Program Files\Android\Android Studio\jbr\bin\java
    • Java version OpenJDK Runtime Environment (build 17.0.6+0-b2043.56-10027231)
    • All Android licenses accepted.

[√] Chrome - develop for the web
    • Chrome at C:\Program Files\Google\Chrome\Application\chrome.exe

[X] Visual Studio - develop Windows apps
    X Visual Studio not installed; this is necessary to develop Windows apps.
      Download at https://visualstudio.microsoft.com/downloads/.
      Please install the "Desktop development with C++" workload, including all of its default components

[√] Android Studio (version 2022.3)
    • 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-10027231)

[√] IntelliJ IDEA Community Edition (version 2022.3)
    • IntelliJ at C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2022.3.1
    • 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

[√] VS Code (version 1.87.2)
    • VS Code at C:\Users\Nipun Shah\AppData\Local\Programs\Microsoft VS Code
    • Flutter extension can be installed from:
       https://marketplace.visualstudio.com/items?itemName=Dart-Code.flutter

[√] Connected device (3 available)
    • Windows (desktop) • windows • windows-x64    • Microsoft Windows [Version 10.0.22631.3447]
    • Chrome (web)      • chrome  • web-javascript • Google Chrome 115.0.5790.171
    • Edge (web)        • edge    • web-javascript • Microsoft Edge 124.0.2478.67

[√] Network resources
    • All expected network resources are available.

! Doctor found issues in 1 category.

Minimal code example

Code sample
import 'package:flutter/material.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart';

void main() {
  runApp(const MyApp());
}

const kCountryItems = ['India', 'US', 'Japan'];
const kStateItems = [
  'Gujarat',
  'Rajasthan',
  'Punjab',
  'Florida',
  'California',
  'Texas',
  'Kinki',
  'Shikoku',
  'Tohoku'
];

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

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

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

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  String? _country = kCountryItems.first;
  String? _state = kStateItems.first;

  // Can be used later to manipulate fields
  final _formKey = GlobalKey<FormBuilderState>();

  @override
  Widget build(BuildContext context) {
    const gap = SizedBox(height: 12);
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: Container(
          width: 300,
          color: Colors.blue.shade100,
          padding: const EdgeInsets.all(12),
          child: FormBuilder(
            key: _formKey,
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.stretch,
              mainAxisAlignment: MainAxisAlignment.center,
              mainAxisSize: MainAxisSize.min,
              children: <Widget>[
                const Text('Form'),
                gap,
                CountryInput(
                  initialValue: _country,
                  onChanged: _onCountryChanged,
                ),
                gap,
                StateInput(
                  // ? Enforcing state input to reset/recreate asa `_country` changes
                  key: ValueKey(_country),
                  initialValue: _state,
                  onChanged: _onStateChanged,
                ),
                gap,
              ],
            ),
          ),
        ),
      ),
    );
  }

  void _onCountryChanged(String? country) {
    setState(() {
      _country = country;
      // ?? Eventhough state is made null & we used ValueKey() for StateInput to force recreation, it will retain old value !!
      _state = null;
      print('(: Country Changed > C:- $_country S:- $_state');
    });
  }

  void _onStateChanged(String? state) {
    _state = state;
    print('(: State Changed > $_state');
  }
}

class CountryInput extends StatelessWidget {
  const CountryInput({super.key, this.initialValue, required this.onChanged});

  static String id = 'country';

  final String? initialValue;
  final ValueChanged<String?> onChanged;

  @override
  Widget build(BuildContext context) {
    return FormBuilderField<String>(
      name: CountryInput.id,
      initialValue: initialValue,
      builder: (field) => _buildField(field),
      onChanged: onChanged,
    );
  }

  Widget _buildField(FormFieldState<String> field) {
    return DropdownMenu(
        width: 250,
        initialSelection: field.value,
        onSelected: (value) {
          field.didChange(value);
        },
        dropdownMenuEntries: kCountryItems
            .map((e) => DropdownMenuEntry<String>(value: e, label: e))
            .toList());
  }
}

class StateInput extends StatelessWidget {
  const StateInput({super.key, this.initialValue, required this.onChanged});

  static String id = 'state';

  final String? initialValue;
  final ValueChanged<String?> onChanged;

  @override
  Widget build(BuildContext context) {
    return FormBuilderField<String>(
      name: StateInput.id,
      initialValue: initialValue,
      builder: (field) => _buildField(field),
      onChanged: onChanged,
    );
  }

  Widget _buildField(FormFieldState<String> field) {
    return DropdownMenu(
        width: 250,
        initialSelection: field.value,
        onSelected: (value) {
          field.didChange(value);
        },
        dropdownMenuEntries: kStateItems
            .map((e) => DropdownMenuEntry<String>(value: e, label: e))
            .toList());
  }
}

Current Behavior

FormBuilderField : widget whenever coerced/forced to be recreated (ie maybe via ValueKey or UniqueKey), it's retaining the previous field's value.

Given that name parameter remains unchanged before & after forced recreation

In above code example

  1. The initial value for CountryInput = India & StateInput = Gujarat
  2. StateInput retains value = Gujarat even when CountryInput is changed

NOTE: here StateInput holds key = ValueKey(_country), hence it will be recreated.

Expected Behavior

FormBuilderField : widget whenever coerced/forced to be recreated (ie maybe via ValueKey or UniqueKey), it should respect & attain the latest field's initial value rather than prioritizing the previous (stale) field's value,

Given that latest field's name = old field's name .

In above code example

  1. The initial value for CountryInput = India & StateInput = Gujarat
  2. Whenever CountryInput is changed, StateInput must reset & hence no selected value (ie _state = null)
    i.e CountryInput = 'Japan' & StateInput = ''

NOTE: here StateInput holds key = ValueKey(_country), hence it will be recreated; thus current value of _state (i.e null) should be considered as an initial value for the latest StateInput field.

Steps To Reproduce

  1. Provide some initial value to your FormBuilderField (let say initialValue = 'Gujarat')
  2. Try to change value of initialVal variable holder to null
  3. Try to forcefully recreate your FormBuilderField widget or any of it's ancestor. (for an instance, you can use UniqueKey)

[With above sample code]

  1. create flutter project
  2. add dependency -> flutter_form_builder: ^9.2.1

don't forget to do flutter pub get

  1. copy paste aforementioned sample code into main.dart
  2. run the project

Initially selected value => Country = India & State = Gujarat

  1. Try to change the country by tapping CountryInput DropDownMenu

You will observe that StateInput still holds Gujarat.

Aditional information

Below is my console snippet when I followed above steps to reproduce

(: Country Changed > C:- US S:- null
Warning! Replacing duplicate Field for state -- this is OK to ignore as long as the field was intentionally replaced
Warning! Ignoring Field unregistration for state -- this is OK to ignore as long as the field was intentionally replaced
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

1 participant