How do I cache images from firebase in flutter? - flutter

I have been trying to do a cache management system in my flutter app. Ideally I want to retrieve images from firebase storage and display them along with other details. I retrieve the snapshots from firestore and have used cachednetworkimage to display the images. But the amount of images I display is a lot and is causing my app to crash. I believe if I was caching the image locally, that problem would be solved. And besides that, I also want to cache json files so that in offline mode, my app will display both the cached images and the other details available in the cache memory.
I want to display posts, which contain username, user profile picture, the image post itself, caption and comments. So the way I retrieve the posts is according to the following...
void fetchFeed() async {
auth.User currentUser = await _repository.getCurrentUser();
User user = await _repository.fetchUserDetailsById(currentUser.uid);
setState(() {
this.currentUser = user;
});
setState(() {
loadingPosts = true;
});
Query query = _firestore.collection("users").doc(user.uid).collection("following").orderBy("uid").limit(perPage);
QuerySnapshot querySnapshot = await query.get();
for (var i = 0; i < querySnapshot.docs.length; i++) {
followingUIDs.add(querySnapshot.docs[i].id);
}
for (var i = 0; i < followingUIDs.length; i++) {
Query posts = _firestore.collection("users").doc(followingUIDs[i]).collection("posts").orderBy("time").limit(perUser);
QuerySnapshot postSnapshot = await posts.get();
lastPost = postSnapshot.docs[postSnapshot.docs.length -1];
for (var i = 0; i < postSnapshot.docs.length; i++) {
feedlist.add(postSnapshot.docs[i]);
}
}
setState(() {
loadingPosts = false;
});
}
And after I retrieved the posts, I put them in a listview and show them sequentially. The problem I am getting is that since the images are not cached locally, I use cached network image widget to display them. And whenever I navigate to another page and return, all the cached network images get reloaded and that puts a big load on the app, which causes it to crash.
CachedNetworkImage(
imageUrl: list[index].data()['imgUrl'],
placeholder: ((context, url) => Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.width,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/Black.png'),
fit: BoxFit.cover),
))),
fit: BoxFit.fitWidth,
),
Alternatively, I tried to download the images and save them locally using the following function. And I call the function for every image item I retrieve from firebase. But that just distorts the images for some reason.
Future <Null> downloadFile(String httpPath) async{
final StorageReference ref = await FirebaseStorage.instance.getReferenceFromUrl(httpPath);
final StorageFileDownloadTask downloadTask = ref.writeToFile(file);
final int byteNumber = (await downloadTask.future).totalByteCount;
print(byteNumber);
setState(() => _cachedFile = file);
}
The http path is a download url I got for each image. But I am not sure if this is the best way to download images. Since I don't have a way to know the image file names as they appear in firebase storage, this was my only option.
Can someone tell me an alternative way to download and cache images, and also json files (which contain username, comments, caption) in my case, so that I can show them offline?

For this purpose use cached_network_image package. which also support placeholders and fading images in as they’re loaded.
CachedNetworkImage( imageUrl: 'https://picsum.photos/250?image=9');
Complete Example
`
return MaterialApp(
title: title,
home: Scaffold(
appBar: AppBar(
title: Text(title),
),
body: Center(
child: CachedNetworkImage(
placeholder: (context, url) => CircularProgressIndicator(),
imageUrl:
'https://picsum.photos/250?image=9',
),
),
),
);
`

Related

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,
},
)

Upload race condition - Google Firebase Storage

I am attempted to update an avatar on my app and then load and display it once done. However, I am seeing the following errors which seem to indicate a false positive or race condition when the image has actually finished uploading.
I'm using a CicleAvatar widget, but also attempted with NetworkImage and am experiencing the same issues. I have also attempted .then/onComplete and various others outside of a delayed or wrapping it in a completer.
What is the best way to handle Firebase storage upload and immediate download without error§
Example Error n attempting to retrieve the image from the DownloadURLL:
════════ Exception caught by image resource service
════════════════════════════ HTTP request failed, statusCode: 503,
!isImageProcessing
? GestureDetector(
onTap: () => _uploadAvatarImage(),
child: CircleAvatar(
minRadius: 40,
backgroundColor: Colors.grey,
backgroundImage: NetworkImage(user.imageURL),
),
)
: Center(
child: CircularProgressIndicator(),
),
The actual upload of the file is being managed in this function/class
class StorageController {
static Future<String> storeAvatarImage(File file) async {
// Get user UUID to reference avatar;
String uuid = await identityBloc.retrieveActiveUUID();
String downloadURL;
TaskSnapshot ts;
ts = await firebase_storage.FirebaseStorage.instance
.ref('avatars/$uuid-avatar.png')
.putFile(file);
downloadURL = await ts.ref.getDownloadURL();
User user = await ProfileDataController.retrieveUserProfile();
user.imageURL = downloadURL;
await ProfileDataController.createUserProfile(user);
downloadURL = downloadURL;
return downloadURL;
}
}
I think you are not properly awaiting for the file upload. Can you change this line to read:
ts = await firebase_storage.FirebaseStorage.instance
.ref('avatars/$uuid-avatar.png')
.putFile(file);
// removed the below part
// .snapshot;
The image would update if there are listeners to listen to changes in the changed user avatar.
What I would advise as a workaround is store the avatarUrl to firestore or rtdb, there you can set a listener that updates the UI on the frontend when a change is written there.
Initially, the avatarUrl field would be null then when a user uploads a new picture the field is then a string and you can supply it to your UI

Flutter remove image after upload using image_picker package

I have managed to upload an image using the image picker package as shown below:
final picker = ImagePicker();
Future selectPhoto() async {
final pickedFile = await picker.getImage(source: ImageSource.gallery);
setState(() {
_image = File(pickedFile.path);
});
}
The image has been displayed on the device where I have a remove button which I can use to remove the image and upload another before saving it.
child: Container(
height: height * 0.2,
width: width * 0.2,
child: Image.file(
_image,
fit: BoxFit.fill,
),
),
My question is how can I remove the image from cache as this piece is not working
onTap: () {
setState(() {
imageCache.clear();
print('removes $_image');
});
},
You have a misunderstanding of what the cache is compared to explicitly storing a file in memory with your code. Flutter may store images that you use in widgets in a cache so that they may load faster in the future. imageCache.clear(); does clear the cache and is likely doing it's work in your code. However, your method of checking if the image is still cached is flawed.
imageCache.clear(); only clears the cache, it does not delete the file that you're passing to the Image.file widget. This means that _image(the file variable you have stored in memory) will persist even over the cache clearing. To truly delete this image you could use the delete method or if you just want to stop showing the image, deference the file by setting the file reference to null:
_image = null;
Solved it by calling a method within the button which had set state method where the path was returned back to null
clearimage() {
setState(() {
_image = null;
});
}

How can I save data to permanent storage and retrieve it?

I want to save data on device which it should be visible to user when he/she went to the desired path from file manager.
now I have two problem:
1-
like social networks which save images in storage ( not cache images ) , I want to save my images into desired path storage and the images should be accessable like cacheNetworkImage.
so what should I do ?
because I loads many images , caching them is not a good solution because it occupy a high amount of RAM.
2-
this scenario is like Previous but I download some media into my storage with some IDs.
but I a want to read only IDs and if user click on that file , execute it.
so How can I access name of existing files in that file of storage path ?
thanks to community. any help is appreciated.
You can chose between basic path_provider library and more advanced network_to_file_image library.
In the first one you can list your files and read them this way:
void checkDirs() async{
Directory tempDir = await getApplicationDocumentsDirectory();
List<FileSystemEntity> directory = tempDir.listSync();
directory.forEach((x) => debugPrint(x.path));
}
Future<File> readImage(String path) async{
return File(path);
}
And render it this way:
Container(child: FutureBuilder<File>(
future: readImage("some_path"),
builder: (BuildContext context,
AsyncSnapshot<File> snapshot) {
if (snapshot.connectionState ==
ConnectionState.done &&
snapshot.data != null) {
return Image.file(
snapshot.data,
);
} else if (snapshot.error != null) {
return const Text(
'Error Picking Image',
textAlign: TextAlign.center,
);
} else {
return const Text(
"No photo"
);
}
},
))
The second one is well documented on the link.

Flutter : fetch image from picture Directory

I'm working with saved image from Url with Gallery Saver. Everything it's oke , i can insert image from URL, but i don't know how to fetch image from picture Directory .
It's different about getApplicationDocumentsDirectory() or getExternalStorageDirectory() ?
I want to display the image that was just saved.
Any solution ?
Save image Code :
void _testSaveImage() async {
String path = "${Urls.BASE_API_IMAGE}/berita/${widget.gambarBerita}";
GallerySaver.saveImage(path).then((bool success) {
print('Success add image $path');
}).catchError((onError) {
print('Error add image $path');
});
}
Location Image saved
Then just return the path you have and use a Image.file() widget to display it, lets say you have a Column():
Column(children: <Widget>[
[...]
Text('Here is my image:'),
if (path.isNotEmpty && path != null)
SizedBox(
height: 200,
width: 200,
child: Image.file(File(path),
),
Text('Caption of the image'),
[...]
),
Docs for Image class here
Load local saved images using path and use it
var file = new File('path');
Image.file(file )