GiancarloCode/form_bloc

FormBlocState isn't emitted on field value change

julek-kal opened this issue · 5 comments

Hi, I found potential issue on newest git version

On older version(0.20.4) i had this code

 BlocBuilder<FormBlocT, FormBlocState<String, String>>(
        builder: (context, state) {
         return Opacity(
                  key: saveButtonKey,
                  opacity: hasChangedFields(state) ? 1 : 0,
                  child: CustomButton(
                    enabled: hasChangedFields(state),
                    text: 'Save',
                    onPressed: () => widget.onSaveButtonPresed(state),
                  ),
                ),
         }

after migrate to bloc 8 I had to checkout to git version of form bloc, because on pub version removeFieldBlocs wasn't work. Additionally I changed above code to this

 BlocBuilder<FormBlocT, FormBlocState<String, String>>(
        builder: (context, state) {
         return Opacity(
                  key: saveButtonKey,
                  opacity: !bloc.hasInitialValues() ? 1 : 0,
                  child: CustomButton(
                    enabled: !bloc.hasInitialValues(),
                    text: 'Zapisz zmiany',
                    onPressed: () => widget.onSaveButtonPresed(state),
                  ),
                ),
         }

Unfortunately after this change, state from FormBloc stopped being emitted. So I changed _setupStepValidationSubs to this

  void _setupStepValidationSubs(
   Map<int, Iterable<FieldBloc>> allFieldBlocs,
 ) {
   for (final sub in _stepValidationSubs.values) {
     sub.cancel();
   }
   final singleFieldBlocsMap = allFieldBlocs.map(
     (key, fieldBlocOfStep) => MapEntry(
       key,
       FormBlocUtils.getAllSingleFieldBlocs(fieldBlocOfStep),
     ),
   );

   singleFieldBlocsMap.forEach(
     (key, singleFieldBlocs) {
       _stepValidationSubs[key] = Rx.combineLatest<FieldBlocState, List<FieldBlocState>>(
         singleFieldBlocs.map((fieldBloc) => Rx.merge([
               Stream.value(fieldBloc.state),
               fieldBloc.stream,
             ])),
         (fieldStates) {
           // if any value change, then can submit again
           _canSubmit = true;
           return fieldStates;
         },
       ).listen((fieldStates) {
         _updateValidStep(isValid: MultiFieldBloc.areFieldBlocsValid(singleFieldBlocs), step: key);
       });
     },
   );
 }

and replaced _fieldBloc with toJson() in props of all FormBlocState implementations

All started working as before, after changes mentioned above.

I don't know if this behavior change is intentional especially given that is version not published on pub.dev yet. But I'm adding this issue in info purposes

@julek-kal thank you for reporting this issue.
Could you please send me an example of how to reproduce this bug so that I can track it down?
can you open a pull request with your fix so we can easily discuss it?

@WahdanZ Yes sure.
I created gists with example below

Wanted behavior is to update text on top of screen, after type text into textfield

It's work as expected on 0.20.4(on 0.29.1 too I guess): https://gist.github.com/julek-kal/bdad61d945f93530627596fbfab28199

But it's stopped work on newest version from git https://gist.github.com/julek-kal/01ab3036342324f596c58ed6691695ba

I didn't created PR because it turned out that my change isn't perfect(tests start to failing and some things stopped work too).

@julek-kal @WahdanZ I am sorry to report that it was quite a deliberate change.
It was not clear to me how some FormBloc implementations work. Of course, one thing I wanted to avoid is just this, rendering the widgets listen to the FormBloc every time its FieldBloc changes. He is honored in terms of performance

Now the formBloc updates from FieldBloc only when they are added or removed or when a step becomes valid / invalid

Probably the correct solution is to pass this information on between fathers. By adding to the MultiFieldBlocState hasInitialValues and hasValuesChanged. From here to the FormBloc
I would be happy to see a PR if anyone wants to implement it

You can solve quickly using FormBlocUtils.getAllSingleFieldBlocs and FormBlocUtils.(isValuesChanged/hasInitialValues/hasUpdatedValues) to create a stream from the state of the FormBloc

@julek-kal @WahdanZ How could this problem be solved for easy use?

We should expose this set of information in the state or in one or more streams, (for FormBloc and MultiFieldBloc)

  • hasInitialValues
  • isValuesChanged
  • hasUpdatedValues
  • errors
  • values
  • and more???...

Only two solutions come to mind:

  • Put all the information on the various states. It seems excessive to me and would cause a lot of rendering of widgets, mostly useless
  • Expose stream (one ore more) on the FormBloc and MultiFieldBloc to get this information. Ex: onDataFields or similar name

@SimoneBressan
I think we could go with the first solution and create and create helper function for to be used on buildWhen or listenWhen to avoid rendering the widgets listen to the FormBloc