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.