I am working on an ordering application. where I have an object called "Comanda" that contains a list of objects called "CommandLine". I want to handle with the BLOC pattern the whole issue of adding lines to the command, deleting and/or editing.
I show the implemented code.
CLASSES:
class Comanda extends Equatable {
Comanda({this.lineasComanda = const <LineaComanda>[], this.mesa, this.zona});
List<LineaComanda> lineasComanda;
Mesa? mesa;
Zona? zona;
factory Comanda.fromJson(Map<String, dynamic> json) => Comanda(
lineasComanda: json["lineasComanda"] ?? [],
mesa: Mesa.fromJson(json["mesa"]),
zona: Zona.fromJson(json["mesa"]));
Map<String, dynamic> toJson() => {
"lineasComanda": lineasComanda,
"mesa": mesa != null ? mesa!.toJson() : null,
"zona": zona != null ? zona!.toJson() : null,
};
Comanda copyWith({
List<LineaComanda>? lineasComanda,
Mesa? mesa,
Zona? zona,
}) {
return Comanda(
lineasComanda: lineasComanda!,
mesa: mesa ?? this.mesa,
zona: zona ?? this.zona,
);
}
@override
List<Object?> get props => [lineasComanda, mesa, zona];
}
class LineaComanda extends Equatable {
LineaComanda({
required this.articulo,
required this.unidades,
this.unidadesPendientes = 0,
this.anotaciones,
});
Articulo? articulo;
int unidades;
int unidadesPendientes;
List<String>? anotaciones = [];
//UI VARIALBLES
bool isExpanded = false;
LineaComanda copyWith(
{Articulo? articulo,
int unidades = 0,
int unidadesPendientes = 0,
List<String>? anotaciones = const []}) {
return LineaComanda(
articulo: articulo ?? this.articulo,
unidades: unidades,
unidadesPendientes: unidadesPendientes,
anotaciones: anotaciones ?? this.anotaciones,
);
}
@override
List<Object?> get props => [articulo, unidades, unidadesPendientes, anotaciones];
}
BLOC:
part of 'Comanda_bloc.dart';
abstract class ComandaState extends Equatable {
const ComandaState();
}
class ComandaInitial extends ComandaState {
@override
List<Object> get props => [];
}
class ComandaLoading extends ComandaState {
@override
List<Object> get props => [];
}
class ComandaLoaded extends ComandaState {
final Comanda comanda;
const ComandaLoaded({required this.comanda});
@override
List<Object> get props => [comanda];
}
class ComandaError extends ComandaState {
final String error;
const ComandaError(this.error);
@override
List<Object?> get props => [error];
}
part of 'Comanda_bloc.dart';
abstract class ComandaEvent extends Equatable {
const ComandaEvent();
}
class StartComanda extends ComandaEvent {
@override
List<Object?> get props => [];
}
class AddLineaComanda extends ComandaEvent {
final LineaComanda lineaComanda;
const AddLineaComanda(this.lineaComanda);
@override
List<Object> get props => [lineaComanda];
}
class RemoveLineaComanda extends ComandaEvent {
final LineaComanda lineaComanda;
RemoveLineaComanda(this.lineaComanda);
@override
List<Object> get props => [lineaComanda];
}
class AddUnidades extends ComandaEvent {
final LineaComanda lineaComanda;
AddUnidades(this.lineaComanda);
@override
List<Object> get props => [lineaComanda];
}
class RemoveUnidades extends ComandaEvent {
final LineaComanda lineaComanda;
RemoveUnidades(this.lineaComanda);
@override
List<Object> get props => [lineaComanda];
}
class RemoveAllLineaComanda extends ComandaEvent {
final LineaComanda lineaComanda;
RemoveAllLineaComanda(this.lineaComanda);
@override
List<Object> get props => [lineaComanda];
}
class SelectMesa extends ComandaEvent {
final Mesa mesa;
SelectMesa(this.mesa);
@override
List<Object> get props => [mesa];
}
class LoadComanda extends ComandaEvent {
final List<LineaComanda> lineasComanda;
LoadComanda(this.lineasComanda);
@override
List<Object?> get props => [lineasComanda];
}
part 'Comanda_event.dart';
part 'Comanda_state.dart';
class ComandaBloc extends Bloc<ComandaEvent, ComandaState> {
final Comanda_Repository repository;
ComandaBloc(this.repository) : super(ComandaInitial()) {
//EVENTOS
on<LoadComanda>((event, emit) async {
emit(ComandaLoading());
try {
// final comanda = await repository.getone(1);
emit(ComandaLoaded(comanda: Comanda()));
} catch (error) {
emit(ComandaError(error.toString()));
}
});
on<StartComanda>((event, emit) async {
emit(ComandaLoading());
try {
// final comanda = await repository.getone(1);
emit(ComandaLoaded(comanda: Comanda()));
} catch (error) {
emit(ComandaError(error.toString()));
}
});
on<AddLineaComanda>((event, emit) async {
final state = this.state;
if (state is ComandaLoaded) {
try {
emit(
ComandaLoaded(
comanda: state.comanda.copyWith(
lineasComanda: List.from(state.comanda.lineasComanda)
..add(event.lineaComanda))),
);
} catch (_) {}
}
});
on<RemoveLineaComanda>((event, emit) async {
final state = this.state;
if (state is ComandaLoaded) {
try {
emit(
ComandaLoaded(
comanda: state.comanda.copyWith(
lineasComanda: List.from(state.comanda.lineasComanda)
..remove(event.lineaComanda))),
);
} catch (_) {}
}
});
on<AddUnidades>((event, emit) async {
emit(ComandaLoading());
final state = this.state;
if (state is ComandaLoaded) {
try {
if (state.comanda.lineasComanda.contains(event.lineaComanda)) {
List<LineaComanda> lineasComanda = state.comanda.lineasComanda;
lineasComanda.firstWhere((item) => item == event.lineaComanda).unidades = 1;
emit(
ComandaLoaded(comanda: state.comanda.copyWith(lineasComanda: lineasComanda)),
);
}
} catch (_) {}
}
});
on<RemoveUnidades>((event, emit) async {
final state = this.state;
if (state is ComandaLoaded) {
try {
if (state.comanda.lineasComanda.contains(event.lineaComanda)) {
List<LineaComanda> lineasComanda = state.comanda.lineasComanda;
lineasComanda.firstWhere((item) => item == event.lineaComanda).unidades -= 1;
emit(
ComandaLoaded(comanda: state.comanda.copyWith(lineasComanda: lineasComanda)),
);
}
// LineaComanda lineaComanda =
// state.comanda.lineasComanda.firstWhere((element) => element == event.lineaComanda);
} catch (_) {}
}
});
}
}
UI:
class Comanda extends StatefulWidget {
const Comanda({Key? key}) : super(key: key);
@override
State<Comanda> createState() => _ComandaState();
}
class _ComandaState extends State<Comanda> {
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return Expanded(
child: Padding(
padding: const EdgeInsets.all(10.0),
child: BlocBuilder<ComandaBloc, ComandaState>(
builder: (context, state) {
if (state is ComandaLoading) {
return const Center(
child: CircularProgressIndicator(),
);
}
if (state is ComandaLoaded) {
return state.comanda.lineasComanda.isEmpty
? const EmptyList(
text: 'No existen artículos en la comanda',
)
: ListView(children: [
ExpansionPanelList(
elevation: 3,
// dividerColor: Colors.blue,
expandedHeaderPadding: const EdgeInsets.all(0),
expansionCallback: (index, isExpanded) {
setState(() {
state.comanda.lineasComanda[index].isExpanded = !isExpanded;
});
},
animationDuration: const Duration(milliseconds: 200),
children: state.comanda.lineasComanda
.map(
(item) => LineaComandaCard(item),
)
.toList(),
// Card_lineaComanda(flatButtonStyle),
),
]);
}
if (state is ComandaError) {
return Center(
child: Text(state.error.toString()),
);
}
return Container();
},
),
),
);
}
ExpansionPanel LineaComandaCard(LineaComanda lineaComanda) {
//Conusltamos el color de la famila
Color color = Colors.grey;
;
// do stuff here based on BlocA's state
return ExpansionPanel(
canTapOnHeader: true,
// backgroundColor: item['isExpanded'] == true ? Colors.cyan[100] : Colors.white,
headerBuilder: (context, isExpanded) {
return Container(
decoration: BoxDecoration(
// borderRadius: BorderRadius.circular(3),
border: Border(left: BorderSide(color: color, width: 3))),
padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(lineaComanda.articulo!.nombre, style: AppTheme.tituloCard),
Row(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
AnimatedSwitcher(
duration: const Duration(milliseconds: 500),
child: Row(
children: [
Text(
'Unidades:',
style: AppTheme.textTagsCard,
),
BlocBuilder<ComandaBloc, ComandaState>(
builder: (context, state) {
if (state is ComandaLoaded)
return Text(
' ${state.comanda.lineasComanda.firstWhere((item) => item == lineaComanda).unidades}',
style: AppTheme.textTagsCard,
key: ValueKey(lineaComanda.unidades),
);
return Container();
},
),
],
),
transitionBuilder: (Widget child, Animation<double> animation) {
return ScaleTransition(scale: animation, child: child);
},
),
const Text(" | "),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 5.0, vertical: 2),
child: Text(
'Pendientes: ${lineaComanda.unidades}',
style: AppTheme.textTagsCard,
),
),
(lineaComanda.unidadesPendientes >= lineaComanda.unidades)
? const Icon(
Icons.check_box_rounded,
color: AppTheme.greenOscuro,
size: 15,
)
: (lineaComanda.unidades == lineaComanda.unidadesPendientes)
? const Icon(Icons.disabled_by_default_rounded,
color: AppTheme.redOscuro, size: 15)
: const Icon(Icons.indeterminate_check_box_rounded,
color: AppTheme.yellow, size: 15),
const SizedBox(
width: 10,
),
// const SizedBox(
// width: 20,
// ),
],
),
],
));
},
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Padding(
padding: EdgeInsets.only(top: 8, bottom: 3, left: 10, right: 10),
child: Text(
"Anotaciones:",
style: TextStyle(fontSize: 12),
),
),
const Padding(
padding: EdgeInsets.only(top: 0, left: 30),
child: Text(
"· Sin pepinillo.",
style: TextStyle(fontSize: 12),
),
),
const Divider(),
Padding(
padding: const EdgeInsets.all(4.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
// alignment: MainAxisAlignment.spaceAround,
// buttonHeight: 12.0,
// buttonMinWidth: 10.0,
children: <Widget>[
TextButton(
// style: flatButtonStyle,
onPressed: () {
context.read<ComandaBloc>()..add(AddUnidades(lineaComanda));
// setState(() {
// lineaComanda.unidades = 1;
// });
},
child: Column(
children: const <Widget>[
Icon(
Icons.add,
color: AppTheme.grismedio,
),
Padding(
padding: EdgeInsets.symmetric(vertical: 2.0),
),
Text(
'Más',
style: TextStyle(fontSize: 9, color: AppTheme.secondaryTextColor),
),
],
),
),
TextButton(
// style: flatButtonStyle,
onPressed: () {
context.read<ComandaBloc>()..add(RemoveUnidades(lineaComanda));
// setState(() {
// // lineaComanda.unidades -= 1;
// });
},
child: Column(
children: const <Widget>[
Icon(
Icons.remove,
color: AppTheme.grismedio,
),
Padding(
padding: EdgeInsets.symmetric(vertical: 2.0),
),
Text(
'Menos',
style: TextStyle(fontSize: 9, color: AppTheme.secondaryTextColor),
),
],
),
),
TextButton(
// style: flatButtonStyle,
onPressed: () {},
child: Column(
children: const <Widget>[
Icon(
Icons.edit_note_outlined,
color: AppTheme.grismedio,
),
Padding(
padding: EdgeInsets.symmetric(vertical: 2.0),
),
Text(
'Anotaciones',
style: TextStyle(fontSize: 9, color: AppTheme.secondaryTextColor),
),
],
),
),
TextButton(
// style: flatButtonStyle,
onPressed: () {
context.read<ComandaBloc>()..add(RemoveLineaComanda(lineaComanda));
},
child: Column(
children: const <Widget>[
Icon(
Icons.delete_outline_outlined,
color: AppTheme.grismedio,
),
Padding(
padding: EdgeInsets.symmetric(vertical: 2.0),
),
Text(
'Eliminar',
style: TextStyle(fontSize: 9, color: AppTheme.secondaryTextColor),
),
],
),
),
TextButton(
// style: flatButtonStyle,
onPressed: () {},
child: Column(
children: const <Widget>[
Icon(
Icons.local_offer_outlined,
color: AppTheme.grismedio,
),
Padding(
padding: EdgeInsets.symmetric(vertical: 2.0),
),
Text(
'Invitar',
style: TextStyle(fontSize: 9, color: AppTheme.secondaryTextColor),
),
],
),
)
],
),
),
],
),
isExpanded: lineaComanda.isExpanded,
);
}
}
The add and remove command line events do work correctly and the UI is redrawed, but the add and remove unit events do not redraw the UI, although the event is executed.
I show console logs:
ADD/REMOVE Command Line:
I/flutter (13260): ComandaBloc AddLineaComanda(LineaComanda(Article(389, , CHIVITO BOCADILLO, 1, 0.0, 0.0, 0.0, 0.0, 10, YesNO.No, YesNO.No, YesNO.No, -1, 0, - 1, YesNO.No, YesNO.No), 2, 0, [])) I/flutter (13260): Transition { currentState: CommandLoaded(Command([], null, null)), event: AddCommandLine(CommandLine(Item (389, , CHIVITO SNACK, 1, 0.0, 0.0, 0.0, 0.0, 10, YesNO.No, YesNO.No, YesNO.No, -1, 0, -1, YesNO.No, YesNO.No), 2, 0, [])), nextState: CommandLoaded(Command([CommandLine(Article(389, , CHIVITO BOCADILLO, 1, 0.0, 0.0, 0.0, 0.0, 10, SioNO.No, SioNO.No, SioNO.No, -1 , 0, -1, YesNO.No, YesNO.No), 2, 0, [])], null, null)) }
ADD / REMOVE UNITS:
I/flutter (13260): CommandBloc AddUnits(CommandLine(Article(389, , CHIVITO BOCADILLO, 1, 0.0, 0.0, 0.0, 0.0, 10, YesNO.No, YesNO.No, YesNO.No, -1, 0, - 1, YesNO.No, YesNO.No), 4, 0, []))
Any solution to redraw the UI correctly???
Thank you very much.
CodePudding user response:
If the user interface isn't getting redrawn it's because the emmited state is the same as before.
With "Same as before" i mean that the ==
operator returns true.
I see that you are using equatable.
I personally prefer to override the ==
so that I have more control when deciding wether or not the two objects are the same.
Anyway, in your case, carefully check that you have correctly overridden the propery props
of all the types you are using (IE, I can't see the implementation for the class Articulo
).
You could also write some unit tests to make sure that all the types you create correctly implement Equatable
.
CodePudding user response:
When you handle RemoveLineaComanda
and AddLineaComanda
events, you create a new instance of a List
with List.from()
.
In the case of the RemoveUnidades
and AddUnidades
you emit a state with the same instance of List
from the previous state. If you create a new List in for these events as well it should work properly.