Upload race condition - Google Firebase Storage - flutter

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

Related

Uploading image into Floating Action Button, set state not working

I have a floating action button that I want a user to click to take a picture with their camera and then have that image replace the camera icon on the floating action bar button.
Here is the code for my FAB, and including uploading the image to firestore storage.
floatingActionButton: FloatingActionButton.large(
heroTag: "add image",
backgroundColor: const Color(0xFF93C3B9),
child: (imageURL == ' ')
? const Icon(Icons.add_a_photo_outlined)
: Image.network(imageURL),
//open add gear page
onPressed: () async {
// todo: upload an image to Firebase Storage
//Take picture
ImagePicker imagePicker = ImagePicker();
XFile? file = await imagePicker.pickImage(source: ImageSource.camera);
if (file == null) return;
String uniqueFileName =
DateTime.now().millisecondsSinceEpoch.toString();
//Get reference to storage root
Reference referenceRoot = FirebaseStorage.instance.ref();
Reference referenceDirImages = referenceRoot.child('images/$userID');
Reference referenceImageToUpload =
referenceDirImages.child(uniqueFileName);
try {
//upload image
await referenceImageToUpload.putFile(File(file.path));
//get download URL
setState(() async {
imageURL = await referenceImageToUpload.getDownloadURL();
print(imageURL);
});
//upload path to fireStore database
} catch (error) {}
},
),
After the image uploads it's like the set state is not working to replace the icon with the image. The odd part is is I crtl-s and save in Visual Studio Code then the widgets seem to rebuild and then the image is visible there...
So after playing around with my code a bit I decided to edit the above code and take tha await function out of the setState() and make setState() not async anymore:
//get download URL
String tempUrl = await referenceImageToUpload.getDownloadURL();
setState(() {
print("--------- Set State -----------");
imageURL = tempUrl;
print("--------- Set State end -----------");
});
print("New image url $imageURL ------------");
not sure why it works, but this solves my issue.
By your description of the issue, I think you might be using StatelessWidget instead of StatefulWidget.
You see the button change when performing a hotreload because the value of imageURL is correctly changing internally, but you need a StatefulWidget to update the UI also.
Hope it helps!

How to retrieve an image from Firestore database - Flutter

i was wondering how to retrieve an image from Firestore, i was trying and reading and watching tutorials so i have come with this solution:
1- Upload images to Firebase storage.
2- Go to Firestore create a collection and a document with field name and value of image path. ( collection "majorChallenges", doc "challenge-1", field "source", value "image path url")
3- Initiate a Firestore Instance: var _fireStoreInstance = FirebaseFirestore.instance;
4- Access the Database collection using QuerySnapshot: QuerySnapshot qn = await _fireStoreInstance.collection("majorChallenges").get();
5- now after that am not sure how to setState, initState, return and display the image inside Scaffold or Container.
some tutorials are using the image link or name which is not efficient for me what if i have to change the image in the future so the best solution i believe must not use the image name it should access the collection and the doc and retrieve the image path.
i already set up the read and write permissions and i have accessed the database before to retrieve text so no authorisation problems, I would be very thankful if someone could complete the steps for us
I can now answer my own question, first of all it is wrong to specify this operation in steps because there are a lot of approaches to achieve this goal, and the approach i am using is pretty nice and simple using QuerySnapshot. so to retrieve picture we need simply do these steps (for my approach):
1- we need to declare a string variable then create asynchronous method that will fetch the image URL from Firestore and update the variable state using set state method, inside this method we can initiate a Firestore instance and a QuerySnapshot:
String img = "";
fetchMajorChallengeImage() async {
var _fireStoreInstance = FirebaseFirestore.instance;
QuerySnapshot qn =
await _fireStoreInstance.collection("majorChallenges").get();
setState(() {
img = qn.docs[0]["source"];
});
return qn.docs;
}
2- we will initiate the state using initState method and call our function inside:
void initState() {
fetchMajorChallengeImage();
super.initState();
}
3- now the image is retrieved and the path stored to "img" variable we declared, so we need to display the image, here its your choice where and how to display the image but for me this is how i displayed it:
Widget build(BuildContext context) {
return InkWell(
onTap: () {},
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Image.network(img, height: 150, fit: BoxFit.fill),
],
),
);
}
note that it must be displayed inside Image.network() widget because the variable holds a URL value fetched from the firestore collection > doc > value and now the image is retrieved and displayed successfully.

Flutter error with nested firebase GET requests

I am working on a fitness project, where the user takes "exercises" and can make some of them favorite. The Exercises and the Favorites by user are stored in different collections in Firebase Firestore. When I load the exercises into the app, as I load them I want to check per exercise, which have been favorited. I want to do this at the same time I load the exercises. So I have created the following code:
Future<void> fetchAndSetExercises() async {
final List<Exercise> loadedExercises = [];
final extracted = await _firestore.collection('exercises').get();
extracted.docs.forEach(
(exerciseData) async {
print("EXERCISE DATA: ${exerciseData['title']}");
// TODO: Get Favorite
final checkFav = await _firestore
.collection('favorites')
.doc('${_firebaseAuth.currentUser.uid}')
.collection('userFavorites')
.doc(exerciseData.id)
.get();
if (checkFav.exists) {
print('FOUND A RECORD');
}
loadedExercises.add(
Exercise(
id: exerciseData.id,
title: exerciseData['title'],
description: exerciseData['description'],
imageUrl: exerciseData['imageUrl'],
itemUrl: exerciseData['itemUrl'],
minutes: exerciseData['minutes'],
),
);
}, // end for each function
); // end for each
_exercises = loadedExercises.toList();
notifyListeners();
}
Here as the extracted exercises are loaded, I make another get request. So there are two different get requests in this code. When I run the code without the second firebase get, it works fine, but with the second get it gives and error:
RangeError (index): Invalid value: Valid value range is empty: 0
I haven't been able to find any solution for this. Maybe it is not possible to nest Firebase get requests to different collections...
If anyone could help clarify this or point to how I can solve this?
extracted.docs.forEach(
(exerciseData) async {
The forEach method waits for your function to finish, but your function is async, which means it finishes immediately and returns a Future<> that needs to be awaited. But you do not await. the forEach method does not await the results either.
So move your code using await out from the anonymous method that is not awaited into your main body of code:
for(var exerciseData in extracted.docs) {
print("EXERCISE DATA: ${exerciseData['title']}");
// TODO: Get Favorite
final checkFav = await _firestore
.collection('favorites')
.doc('${_firebaseAuth.currentUser.uid}')
.collection('userFavorites')
.doc(exerciseData.id)
.get();
if (checkFav.exists) {
print('FOUND A RECORD');
}
loadedExercises.add(
Exercise(
id: exerciseData.id,
title: exerciseData['title'],
description: exerciseData['description'],
imageUrl: exerciseData['imageUrl'],
itemUrl: exerciseData['itemUrl'],
minutes: exerciseData['minutes'],
),
);
}

How do I cache images from firebase in 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',
),
),
),
);
`

getting image from firebase storage not working

i am new to flutter and i am trying to make an app which take data from user and make a widget out of it (Classified ads), so far everything is working. but the images from firebase storage is not loading, i have the image url in database ,
for the name its working
Text(snapshot.data.documents[index]['item Name'],),
but for images it gives error
index == null
? Image.asset('assets/afghan.png')
: Image.network(snapshot.data.documents[index]['image 1 Url'],),
the ERROR
Invalid argument(s): No host specified in URI file:///data/user/0/com.example.thisOne/cache/image_picker1382407617041908118.jpg
Change this:
Image.network(snapshot.data.documents[index]['image 1 Url'],)
into this:
Image.file(snapshot.data.documents[index]['image 1 Url'],)
The image url is not valid, this is how you have to save the image in the database:
StorageTaskSnapshot snapshot = await storage
.ref()
.child("images/$imageName")
.putFile(file)
.onComplete;
if (snapshot.error == null) {
final String downloadUrl =
await snapshot.ref.getDownloadURL();
await Firestore.instance
.collection("images")
.add({"url": downloadUrl, "name": imageName});
setState(() {
isLoading = false;
});
You are not saving it correctly to the database, you need to get the downloadUrl from firebase storage which will contain a valid url (https://...) and add it to Firestore. Then you can use Image.network