Переход не срабатывает при добавлении элемента в список в состоянии BLoC с flutter_bloc и 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 для сравнения на равенство, но я действительно не понимаю, в чем я ошибаюсь.


person Marco Gelli    schedule 01.07.2020    source источник


Ответы (2)


С 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

Итак, equatable, что он проверяет, равны ли переданные 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.

person Ahmed Erabti    schedule 01.07.2020
comment
Привет @ Ахмед Эрабти. Я попробовал ваше решение, но, к сожалению, проблема не устранена. А пока обязательно проверю пакет freezed. - person Marco Gelli; 01.07.2020

Я решил проблему с помощью оператора распространения Дарта:

  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));
  }
person Marco Gelli    schedule 13.07.2020