使用 flutter_bloc 和 Equatable 将元素添加到 BLoC 状态列表时不会触发转换

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

我正在开发一个使用 flutter_bloc 库进行状态管理的网络应用程序。在我的应用程序中,我必须跟踪表单的状态(AreaForm 小部件)。在这种形式下,我必须管理对象列表:我必须能够向列表中添加或删除对象。

这是 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,
    }''';
  }
}

这是 AreaFormEvent 的代码(我只报告了两个感兴趣的事件):

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 }';
}

...

最后这是 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));
  }

  ...

}

这样,当事件 SpecialitaAdded 被添加到 AreaFormBloc 时,该集团应该产生一个到新 AreaFormState 的过渡,它应该有一个 listaSpecialita等于之前加上刚刚添加的新 Specialita 对象。

不幸的是,根本没有触发任何转换!但真正奇怪的是,如果我改为这样添加元素:

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

然后触发转换。

不幸的是,我无法保留此解决方案,因为我不知道如何管理元素的删除。

我觉得问题是我用了Equatable包来进行相等比较,但是我真的不明白我哪里错了。

对于 Equatable,您必须具有不可变字段(您的列表是可修改的)。

我认为发生过这样的事情:

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

如此平等,它检查传递的 props 是否相等,在本例中是相等的。

解决方法:

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

这为列表提供了具有相同值的新引用。 这将使内部 == 检查为假。

除非它们不相等,否则 Bloc 不会发出新状态。

建议您改用 freezed 包,这样可以节省您 copyWith 的麻烦。

我使用 Dart 的 展开运算符解决了这个问题:

  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));
  }