It seems like bug in bloc v0.11.2
I have the following Event/State:
class DeleteReceipt extends ReceiptEvent {
final Receipt receipt;
DeleteReceipt(this.receipt) : super([receipt]);
}
class ReceiptDeleted extends ReceiptState {
final Receipt receipt;
ReceiptDeleted(this.receipt) : super();
}
and the following code in bloc:
if (event is DeleteReceipt) {
var delReceipt = event.receipt;
await _receiptDao.delete(delReceipt);
print("deleting: " + delReceipt.snapshot.documentID);
yield ReceiptDeleted(delReceipt);
}
and my widget I have:
if (state is ReceiptDeleted) {
print("delete: "+state.receipt.snapshot.documentID);
receipts.delete(state.receipt);
}
and when I do: _receiptBloc.dispatch(DeleteReceipt(receipt));
the first time I get:
I/flutter (28196): deleting: AzgAzcn5wRNFVd7NyZqQ
I/flutter (28196): delete: AzgAzcn5wRNFVd7NyZqQ
which is correct, but the second time I do _receiptBloc.dispatch(DeleteReceipt(receipt)); on a different receipt, I get:
I/flutter (28196): deleting: d4oUjrGwHX1TvIDr9L2M
I/flutter (28196): delete: AzgAzcn5wRNFVd7NyZqQ
You can see that in the second time the DeleteReceipt event was received with the correct value, but the ReceiptDeleted State was received with the wrong value, and then it just get stuck like this, it never fires ReceiptDeleted State with the correct value, only with the first value.
My app is not trivial, and I have set many events and state in the past, and it worked with no issue (except this one, that probably is related flutter bloc state not received)
Basically I let the user create photos of receipt, that are persistent (using bloc/firestore), and I want to let the user delete them, so when the user click on a receipt, it opens in a new screen:
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) {
return ReceiptDetailPage(widget.receipt);
},
),
and when the user click on delete, I show a dialog, and delete the receipt if is OK
var result = await showDialog(
context: context,
builder: (BuildContext dialogCtxt) {
// return object of type Dialog
return AlertDialog(
title: new Text(AppLocalizations.of(context).deleteReceiptQuestion),
actions: <Widget>[
// usually buttons at the bottom of the dialog
new FlatButton(
child: new Text(AppLocalizations.of(context).cancel),
onPressed: () {
Navigator.of(dialogCtxt).pop("cancel");
},
),
new FlatButton(
child: new Text(AppLocalizations.of(context).ok),
onPressed: () {
Navigator.of(dialogCtxt).pop("OK");
},
),
],
);
},
);
if (result == 'OK') {
Navigator.of(context).pop();
_receiptBloc.dispatch(DeleteReceipt(receipt));
}
Solution:
add state/event:
after receiving the delete state do:
and add this to your bloc
This will cause an empty event and state to be fired and will clear the problem
Explain: I noticed that once I fire a State, the block provider will send that state every time I change a screen, which is strange since the app is receiving a
DeleteState many time. this is not a problem in my case, since the code will try to delete an element that is already delete and will fail quietly:NOTE: this seems to happen with all State that the app is firing, not only the
DeletestateSo I guest that if I will fire an empty event, it will clear the old
Deletestate, and will somehow fix the issue, and WALLA...Note that I didn't had to actually listen to the
EmptyStateany where in my codeMORE INFO:
I realize that although the
blocseems to loose state, also my design is wrong, because the Data Structure should be updated in thebloc, once the event is received and not in the widget, when the state is received (or not received in this case, which cause the bug)Initially I used
blocwithsembast, but then I wanted the data to be sync with the remote DB, so I replacedsembastwithfirestore.but that causes the load time to go from nothing, to more than 2 seconds, and that is a problem since in the original design I load all the data from the DB on every update.
So I tried to update the
storeand the UI seperatly, ie. instead of reading all the data, I keep a List in my widget and update the widget when the state changes - per update/delete state.That was a problem, since many state were lost (especially when the user click fast - which cause many events/states to fire)
So I guess a
correctsolution would be to manage the in-memory Data in a separate Service, and update the Data when the Event is received, and then read all data from the Service instead of the store (when possible)