How to reload a network image in flutter? - flutter

When using a network image in flutter sometimes I got the error Connection closed before full header was received. The code below allows me to output the error but how can I force the widget to reload the image?
Image.network(p.thumbURL,
errorBuilder: (BuildContext context, Object exception, StackTrace stackTrace) {
Log.e(exception);
return Container();
},
),

give a value key to your image widget, otherwise it won't rebuild when you update your link
key: ValueKey(url),
update your url by adding a new meaningless query string at the end, e.g. adding a time
setState(() {
url = url.split('?r')[0] + '?r=' + DateTime.now().millisecondsSinceEpoch.toString();
});

Related

How to set the object variable permanently in Flutter?

I am trying to build an app with Flutter and Dart. Here, I want to retrieve data from my Firebase, and I was able to do so and put it in the replies variable in each Message object that I have as an array type in the Firebase by using a for loop. However, when I try to access the replies variable again, it becomes empty.
I tried using setState, but that just causes the replies variable to keep resetting. Why this is the case, and how can I fix it?
StreamBuilder<List<Message>> pickMessage(
Stream<List<Message>> list, BuildContext context) {
return StreamBuilder(
stream: list,
builder: (context, snapshot) {
if (snapshot.hasError) {
return const Text('Something went wrong!');
} else if (snapshot.hasData) {
final message = snapshot.data!;
for (var msg in message) {
FirebaseFirestore.instance.collection("messages").doc(msg.msgID).get().then((value){
msg.replies = value.get("replies");
});
}
If You Need Some Variable to Initialize Once Put in Init Function or In Builder Function, outside the Scope of StreamBuilder, hope it helps

Caching images (thumbnails) in Listview

I made a simple application where in listview I display a list of items by ListView.builder. Each item is a widget where by FutureBuilder I build a CircleAvatar with a picture taken in initState () via Api. I'm using the AppWrite API. The method returns the photo in the form of Uint8list. It's working fine.
#override
void initState() {
super.initState();
myFuture = AppWriteService.getImagePreview()
}
FutureBuilder(
future: myFuture ,
builder: (context, snapshot) {
print("build photo for:" +widget.doc.place!);
// print(snapshot.data);
Uint8List? bytes = snapshot.data as Uint8List?;
// print(bytes);
return snapshot.hasData && snapshot.data != null
? CircleAvatar(
radius: 40,
backgroundImage: MemoryImage(bytes!),
)
: CircularProgressIndicator();
},
)
However, I wanted the whole list to not refresh after removing one item, I mean, it can build, but I would not like the fetch method to download photos for previously displayed items to be performed again. Now, when you delete an item, the whole list refreshes and the photos are downloaded again by fetch method for all elements.
I have already made the another solution, but I have doubts if it is good.
After downloading the items, before I build the list, I download a photo for each item and save it as bytes in my object. so each item already "holds" a photo and there is no need to use FutureBuilder.
So first I get all elements by first request fetchAll() and then in loop for every element I run getImagePreview() and then I build a ListView
I would be grateful for your tips which solution is better.
If you really want to use cached_network_image, you can. You'll just have to manually build the URL yourself:
CachedNetworkImage(
imageUrl: '${client.endPoint}/storage/buckets/${bucketId}/files/${fileId}/view',
httpHeaders: {
'X-Appwrite-Project': widget.client.config['project']!,
},
)
If your file isn't public, you'll also need to generate a JWT token via account.getJWT() and then pass in the headers:
CachedNetworkImage(
imageUrl: '${client.endPoint}/storage/buckets/${bucketId}/files/${fileId}/view',
httpHeaders: {
'X-Appwrite-Project': widget.client.config['project']!,
'X-Appwrite-JWT': jwt,
},
)

Box not found. Did you forget to call Hive.openBox()? - does not detect the box even though I have opened it

I am trying to open my box after getting some data on a particular page and moving to another page. However, it keeps saying that I did not open it. Why?
GestureDetector(
onTap: () async{
final data = Hive.openBox('${setTask.getAt(index)}');
setState(() {
Navigator.push(
context,
MaterialPageRoute(builder: (context) =>
Tasks(setTask.getAt(index), data)));
}
);
},
);
The next page
final opendata;
Tasks(#required this.opendata);
Also..I added a line in my Stateful Widget when the widget builds
final openBox = Hive.openBox('${widget.hiveName}');
Putting it in initState(){} and using async and await did not work either.
To fix you issue, you have to add await, i.e,
final data = await Hive.openBox('box');
instead of,
final data = Hive.openBox('box');
The problem here is, Flutter is rebuilding the state without waiting for Hive to actually open the box, and hence the error. Adding an await would tell Flutter to keep track of Hive opening the box and work accordingly (i.e, refresh state or whatever you want to do).

Is there way to get a callback after StreamBuilder completely load?

I'm trying get a callback after my context build, i already tried with "WidgetsBinding.instance!.addPostFrameCallback", but not works because i have a StreamBuilder and the first load of snapshot data it is null.
I expected exists a callback of StreamBuilder after data completely load and build loaded, here is my code:
StreamBuilder(
stream: this.paymentInfoFormPresenter.key,
builder: (context, snapshot) {
final _formKey = snapshot.data;
if (_formKey == null) {
return Container();
}
return Text('example');
}
);
And i don't find anything about on internet... i hope someone can help me.
To resolve this issue, i needed change the "Text('example')" to an external widget, and added "WidgetsBinding.instance!.addPostFrameCallback" inside of this external widget

flutter bloc - wrong state is sent from bloc to widget

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:
class EmptyState extends ReceiptState {}
class EmptyEvent extends ReceiptEvent {}
after receiving the delete state do:
if (state is ReceiptDeleted) {
print("delete: "+state.receipt.snapshot.documentID);
receipts.delete(state.receipt);
_receiptBloc.dispatch(EmptyEvent()); // add this line
}
and add this to your bloc
if (event is EmptyEvent) {
yield EmptyState();
}
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:
void delete(Receipt receipt) {
try {
Receipt oldReceipt = receipts.firstWhere(
(r) => r.snapshot.documentID == receipt.snapshot.documentID);
receipts.remove(oldReceipt);
} catch (e) {
print(e);
}
}
NOTE: this seems to happen with all State that the app is firing, not only the Delete state
So 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 code
MORE INFO:
I realize that although the bloc seems to loose state, also my design is wrong, because the Data Structure should be updated in the bloc, 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 with sembast, but then I wanted the data to be sync with the remote DB, so I replaced sembast with firestore.
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)