bdlukaa / fluent_ui

Implements Microsoft's WinUI3 in Flutter.
https://bdlukaa.github.io/fluent_ui/
BSD 3-Clause "New" or "Revised" License
2.79k stars 435 forks source link

NumberFormBox doesn't rebuild properly when setState is called #1064

Closed bcerjan closed 1 week ago

bcerjan commented 1 month ago

If you call for a rebuild on a widget that contains a NumberFormBox from outside the NumberFormField it throws the error:

This NumberFormBox<double> 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 widget on which setState() or markNeedsBuild() was called was: NumberFormBox<double>
    dependencies: [UnmanagedRestorationScope, _FluentTheme]
    state: TextFormBoxState#3989d
The widget which was currently being built when the offending call was made was: Flexible
    flex: 1

To Reproduce I believe all you need to do is try to setState()/rebuild a widget that contains a NumberFormBox, but it could be that something about having two of them in a Column is the real issue. I noticed this originally when I had two number form boxes in a stateful widget that calls setState() to work with the values in the NumberFormBoxs.

Widget build(BuildContext context) {
  return Column(children: [
    NumberFormBox(...), // calls setState() in onChanged
    NumberFormBox(...),
  ]

when the first NumberFormBox is edited the second one throws an error when the rebuild starts to occur. This doesn't happen with the default Material TextFormField so I believe something in fluent_ui is producing the issue. Note that I tested this without my bloc as shown below and the issue persisted.

In case it is useful, my entire build method looks like this:

  @override
  Widget build(BuildContext context) {
    return BlocBuilder<DAQBloc, DAQState>(builder: (context, state) {
      final (:min, :max) = state.getAnalogInputRange(id); // this updates every time a value in one of the NumberBoxes changes
      return ConstrainedBox(
          constraints: const BoxConstraints(maxWidth: 400),
          child: Column(
            children: [
              // Minimum Voltage:
              Row(children: [
                const Text('Minimum Voltage'),
                const SizedBox(
                  width: 5,
                ),
                SizedBox(
                    width: 200,
                    child:  NumberFormBox(
                      value: min,
                      max: max,
                      min: minimumAnalogV,
                      onChanged: (m) => _updateCB(m ?? min, max),
                      validator: (value) {
                        if (value == null || value.isEmpty) {
                          return 'Required';
                        }

                        return null;
                      },
                      autovalidateMode: AutovalidateMode.onUserInteraction,
                    )),
              ]),
              // Maxmimum Voltage:
              Row(children: [
                const Text('Maximum Voltage'),
                const SizedBox(
                  width: 5,
                ),
                SizedBox(
                  width: 200,
                  child: NumberFormBox(
                     value: max,
                     max: maximumAnalogV,
                     min: min,
                     onChanged: (m) => _updateCB(min, m ?? max),
                     validator: (value) {
                       if (value == null) {
                         return 'Required';
                       }

                       return null;
                     },
                     autovalidateMode: AutovalidateMode.onUserInteraction,
                   )
                ),
              ]),
            ],
          ));
    });
  }