简体   繁体   中英

Transition not triggered when adding element to list in BLoC state with flutter_bloc and Equatable

I'm developing a web application using flutter_bloc library for state management. In my app I have to keep track of the state of a form ( AreaForm widget). In this form I have to manage a list of objects: I must be able to add or remove an object to the list.

This is the code for the AreaFormState :

part of 'area_form_bloc.dart';

class AreaFormState extends Equatable {
  final Provincia provincia;
  final Comune comune;
  final String denominazione;
  final List<TipoUdo> listaTipoUdo;
  final List<Specialita> listaSpecialita;
  final bool isDisciplineChecked;
  final bool isBrancheChecked;
  final String indirizzo;

  const AreaFormState({
    @required this.provincia,
    @required this.comune,
    @required this.denominazione,
    @required this.listaTipoUdo,
    @required this.listaSpecialita,
    @required this.isDisciplineChecked,
    @required this.isBrancheChecked,
    @required this.indirizzo,
  });

  factory AreaFormState.empty() {
    return AreaFormState(
      provincia: null,
      comune: null,
      denominazione: null,
      listaTipoUdo: <TipoUdo>[],
      listaSpecialita: <Specialita>[],
      isDisciplineChecked: false,
      isBrancheChecked: false,
      indirizzo: null,
    );
  }

  AreaFormState update({
    Provincia provincia,
    Comune comune,
    String denominazione,
    List<TipoUdo> listaTipoUdo,
    List<Specialita> listaSpecialita,
    bool isDisciplineChecked,
    bool isBrancheChecked,
    String indirizzo,
  }) {
    return copyWith(
      provincia: provincia,
      comune: comune,
      denominazione: denominazione,
      listaTipoUdo: listaTipoUdo,
      listaSpecialita: listaSpecialita,
      isDisciplineChecked: isDisciplineChecked,
      isBrancheChecked: isBrancheChecked,
      indirizzo: indirizzo,
    );
  }

  AreaFormState copyWith({
    Provincia provincia,
    Comune comune,
    String denominazione,
    List<TipoUdo> listaTipoUdo,
    List<Specialita> listaSpecialita,
    bool isDisciplineChecked,
    bool isBrancheChecked,
    String indirizzo,
  }) {
    return AreaFormState(
      provincia: provincia ?? this.provincia,
      comune: comune ?? this.comune,
      denominazione: denominazione ?? this.denominazione,
      listaTipoUdo: listaTipoUdo ?? this.listaTipoUdo,
      listaSpecialita: listaSpecialita ?? this.listaSpecialita,
      isDisciplineChecked: isDisciplineChecked ?? this.isDisciplineChecked,
      isBrancheChecked: isBrancheChecked ?? this.isBrancheChecked,
      indirizzo: indirizzo ?? this.indirizzo,
    );
  }

  @override
  List<Object> get props => [
        provincia,
        comune,
        denominazione,
        listaTipoUdo,
        listaSpecialita,
        isDisciplineChecked,
        isBrancheChecked,
        indirizzo,
      ];

  @override
  String toString() {
    return '''
    AreaFormState {
      provincia: $provincia,
      comune: $comune,
      denominazione: $denominazione,
      listaTipoUdo: $listaTipoUdo,
      listaSpecialita: $listaSpecialita,
      isDisciplineChecked: $isDisciplineChecked,
      isBrancheChecked: $isBrancheChecked,
      indirizzo: $indirizzo,
    }''';
  }
}

This is the code for the AreaFormEvent (I have reported only the two events of interest):

part of 'area_form_bloc.dart';

abstract class AreaFormEvent extends Equatable {
  const AreaFormEvent();

  @override
  List<Object> get props => [];
}

...

class SpecialitaAdded extends AreaFormEvent {
  final Specialita specialita;

  const SpecialitaAdded({@required this.specialita});

  @override
  List<Object> get props => [specialita];

  @override
  String toString() => 'SpecialitaAdded { specialita: $specialita }';
}

class SpecialitaRemoved extends AreaFormEvent {
  final Specialita specialita;

  const SpecialitaRemoved({@required this.specialita});

  @override
  List<Object> get props => [specialita];

  @override
  String toString() => 'SpecialitaRemoved { specialita: $specialita }';
}

...

And finally this is the code for AreaFormBloc :

import 'dart:async';

import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:meta/meta.dart';

import '../../../data/comune.dart';
import '../../../data/provincia.dart';
import '../../../data/specialita.dart';
import '../../../data/tipo_udo.dart';
import '../../repositories/area/area_repository.dart';

part 'area_form_event.dart';
part 'area_form_state.dart';

class AreaFormBloc extends Bloc<AreaFormEvent, AreaFormState> {
  final AreaRepository areaRepository;

  AreaFormBloc({@required this.areaRepository})
      : assert(areaRepository != null);

  @override
  AreaFormState get initialState => AreaFormState.empty();

  @override
  Stream<AreaFormState> mapEventToState(
    AreaFormEvent event,
  ) async* {
    if (event is ProvinciaSelected) {
      yield* _mapProvinciaSelectedToState(event.provincia);
    } else if (event is ComuneSelected) {
      yield* _mapComuneSelectedToState(event.comune);
    } else if (event is DenominazioneChanged) {
      yield* _mapDenominazioneChangedToState(event.denominazione);
    } else if (event is TipoUdoAdded) {
      yield* _mapTipoUdoAddedToState(event.tipoUdo);
    } else if (event is TipoUdoRemoved) {
      yield* _mapTipoUdoRemovedToState(event.tipoUdo);
    } else if (event is SpecialitaAdded) {
      yield* _mapSpecialitaAddedToState(event.specialita);
    } else if (event is SpecialitaRemoved) {
      yield* _mapSpecialitaRemovedToState(event.specialita);
    } else if (event is DisciplineChanged) {
      yield* _mapDisciplineChangedToState();
    } else if (event is BrancheChanged) {
      yield* _mapBrancheChangedToState();
    } else if (event is IndirizzoChanged) {
      yield* _mapIndirizzoChangedToState(event.indirizzo);
    } else if (event is NearestUdoIconPressed) {
      yield* _mapNearestUdoIconPressedToState();
    } else if (event is PulisciPressed) {
      yield* _mapPulisciPressedToState();
    } else if (event is CercaPressed) {
      yield* _mapCercaPressedToState();
    }
  }

  ...

  Stream<AreaFormState> _mapSpecialitaAddedToState(
    Specialita specialita,
  ) async* {

    yield state.update(listaSpecialita: state.listaSpecialita..add(specialita));
  }

  Stream<AreaFormState> _mapSpecialitaRemovedToState(
    Specialita specialita,
  ) async* {
    yield state.update(
        listaSpecialita: state.listaSpecialita..remove(specialita));
  }

  ...

}

In this way, when the event SpecialitaAdded is added to the AreaFormBloc , the bloc should produce a Transition to a new AreaFormState which should have a listaSpecialita equal to the one before with the addition of the new Specialita object just added.

Unfortunately no transition at all is triggered: But the really strange thing is that if I add the element like this instead:

  Stream<AreaFormState> _mapSpecialitaAddedToState(
    Specialita specialita,
  ) async* {
    yield state.update(listaSpecialita: state.listaSpecialita + [specialita]);
  }

then the transition is triggered.

Unfortunately I can't keep this solution because I don't know how to manage the removal of an element.

I think the problem is that I use the Equatable package for equality comparison but I really don't understand where I'm wrong.

With Equatable you have to have immutable fields (your list is modifiable).

Something like this happened I think:

final someList = <int>[1, 2, 3]; // this is done when creating state
final anotherList = someList; // this is done with update, you just pass the reference
someList.add(4);
print(anotherList == someList); // true

So equatable, what is does it checks if the passed props are equal or not, in this case are equal.

The solution:

Stream<AreaFormState> _mapSpecialitaAddedToState(
  Specialita specialita,
) async* {
  // creating a new copy of the list
  yield state.update(
    listaSpecialita: List<Specialita>.of(
      state.listaSpecialita..add(specialita),
    ),
  );
}

This gives the List a new reference with the same values. Which will make the internal == check false.

Bloc doesn't emit a new state unless they are not equal.

Would advise you to use freezed package instead, it will save you the hussle of copyWith.

I solved the problem using Dart's spread operator :

  Stream<AreaFormState> _mapSpecialitaAddedToState(
    Specialita specialita,
  ) async* {
    yield state.update(listaSpecialita: [...state.listaSpecialita]..add(specialita));
  }

  Stream<AreaFormState> _mapSpecialitaRemovedToState(
    Specialita specialita,
  ) async* {
    yield state.update(
        listaSpecialita: [...state.listaSpecialita]..remove(specialita));
  }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM