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
Delete
State 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
Delete
stateSo I guest that if I will fire an empty event, it will clear the old
Delete
state, and will somehow fix the issue, and WALLA...Note that I didn't had to actually listen to the
EmptyState
any where in my codeMORE INFO:
I realize that although the
bloc
seems 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
bloc
withsembast
, but then I wanted the data to be sync with the remote DB, so I replacedsembast
withfirestore
.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
store
and 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
correct
solution 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)