Dart/Flutter: Firebase and Firestore wait for async for loop to complete before continuing - flutter

I am trying to submit images to Firebase storage, and then also submit the links to the images to the new record that I am "linking" the images to.
My issue is that if I add image(s) to the data, it seems that the upload makes the rest of the firebase activity not happen (it doesn't make sense to me).
So essentially when I click the FAB then it's supposed to submit the data.
floatingActionButton: FloatingActionButton(
child: Icon(Icons.send),
// SUBMIT THE DATA
onPressed: () async {
setState(() {
// Show the modal spinner until submit is complete
showSpinner = true;
});
// upload images
List<StorageReference> fileRefs = [];
for (var image in imageFiles) {
fileRefs.add(await uploadPic(context, image));
}
// When there are images in the imageFiles array then the below part doesn't run
// but if no images was selected it runs fine, if images are selected they get uploaded
// to firebase storage, but no record gets added. :(
_firestore.collection('InspectionPoints').add({
'project': selectedProject,
// some other fields
'user': loggedInUser.email,
'photoIds': fileRefs.length == 0 ? [] : fileRefs,
'timestamp': DateTime.now(),
//'photoIds' : imageFiles;
});
setState(() {
// <--------------- this still runs
clearForm();
showSpinner = false;
});
} // onPressed
),
I also now tried to put the getting the file refs into an async formula, but it also doesn't work:
// upload images
List<StorageReference> fileRefs = await getFileRefs(context);
And the new function:
Future<List<StorageReference>> getFileRefs(BuildContext context) async {
List<StorageReference> fileRefs = [];
for (var image in imageFiles) {
fileRefs.add(await uploadPic(context, image));
}
return fileRefs;
}
Edit: My Actual uploading code:
Future<StorageReference> uploadPic(BuildContext context, File image) async {
StorageReference firebaseStorageRef = FirebaseStorage.instance.ref().child(basename(image.path));
StorageUploadTask uploadTask = firebaseStorageRef.putFile(image);
StorageTaskSnapshot taskSnapshot = await uploadTask.onComplete;
setState(() {
print('File: ${image.path} uploaded to the cloud');
showInSnackBar('File: ${image.path} uploaded to the cloud');
});
return taskSnapshot.ref;
}

To upload an image to Firebase Storage, you can use the code below.
Future upLoadImage(_providedFile, String folderName, String imageName) async{
//_providedFile -> FILE THAT CONTAINS IMAGE
//folderName -> FOLDER NAME IN FIREBASE STORAGE
//imageName -> NAME OF THE IMAGE THAT WILL BE SAVED ON FIREBASE STORAGE
//FILE UPLOAD LOCATION
//IF YOU DON'T WANT TO ADD IMAGE IN A FOLDER JUST REMOVE
//".child(folderName)" from the line
StorageReference reference = firebasestorage.ref().child(folderName).child('$imageName.jpg');
StorageUploadTask uploadTask = reference.putFile(_providedFile); await
uploadTask.onComplete;
}
I hope this will help you.

You cannot save the type StorageReference to firebase cloud, and due to that type failing to submit the whole submit fails, but because the images are uploaded separately from the data entry, they are already there by the time the data entry fails to submit.
The fix was to convert the StorageReference to string via the .path property.

Related

flutter firebase image upload takes time to get file url

i'm trying to upload an image to the cloud firestore and the firebase storage. I'm saving the image url in a variable called imgUrl, this variable is later on passed inside a function called addIntervention(). The problem is that the upload task takes few time so if I upload and click the save button directly, imgUrl will be having null value cus the image is still getting uploaded.
Here is my code:
IconButton(
icon: Icon(
Icons.image,
color: Palette.primaryColor,
),
onPressed: () async {
ImagePicker imagePicker = ImagePicker();
XFile? file = await imagePicker.pickImage(
source: ImageSource.gallery);
if (file == null) return;
Reference referenceRoot =
FirebaseStorage.instance.ref();
Reference dirImages =
referenceRoot.child("iv_images");
Reference imgToUpload = dirImages.child(file.name);
try {
await imgToUpload.putFile(File(file.path));
var x = imgUrl = await imgToUpload.getDownloadURL();
imgUrl = x;
} catch (e) {}
},
),
And for the button I took this snippet:
if (imgUrl.isEmpty) {
QuickAlert.show(
context: context,
type: QuickAlertType.error,
title: 'Error',
text:
'Please upload an image');
} else {
await addIntervention(
imgUrl,
etatLabel,
numIntv,
intervention,
myPrice!,
note,
dateTime);
Noting that i'm using async/await for the save button as well, is there any way I can solve this? thanks in advance.
You can try these tips:
First thing to make your upload time a lot less is to compress a picture, you don't have to compress the image till it gets blurry but a small amount of compression will significantly reduce your upload time. Also if a use selects an image then he/she may want to crop it too. So it will be better if you add that functionality too.
Luckily there's a package called image_cropper(link), which you can use to crop as well as for compressing your image.
If you don't want to show any loading indicator then you can directly pass the image to the next screen and run your processes in the background(which is called optimistic updating), but if you want to show a loading indicator then you can use this package called flutter_spinkit. It has a very large variety of loading indicators which you will love.
When a user clicks on a button, you can show a progress indicator on the button itself to indicate how much percent has been uploaded, has to be uploaded before the user can click on the button.
In the firebase, you can get percentage like this:
Future getImage(BuildContext context) async {
final picker = ImagePicker();
final pickedFile = await picker.getImage(source: ImageSource.gallery);
setState(() {
_image = File(pickedFile.path);
});
StorageReference firebaseStorageRef = FirebaseStorage.instance.ref().child('profile/${Path.basename(_image.path)}}');
StorageUploadTask uploadTask = firebaseStorageRef.putFile(_image);
var dowurl = await (await uploadTask.onComplete).ref.getDownloadURL();
setState(() {
_imageURL = dowurl.toString();
});
print(_imageURL);
}
uploadTask.events.listen((event) {
setState(() {
_progress = event.snapshot.bytesTransferred.toDouble() /
event.snapshot.totalByteCount.toDouble();
});
}).onError((error) {
// do something to handle error
});
Then you can display progress like this:
Text('Uploading ${(_progress * 100).toStringAsFixed(2)} %')

Saving image permenantly after user upload it in flutter

In my flutter app, if the user is signing in for the first time, he will be directed to profile page where he gets to key in his personal details and upload his profile pic. now my issue is with the profile pic. First of all, Im using Image picker package.
Future pickImage(ImageSource source) async {
try {
final image = await ImagePicker().pickImage(
source: source);
if (image == null) return;
final UserImage = File(image.path);
setState(() => this.image = UserImage );
}on PlatformException catch (e){
Utils.showSnackBar(e.message);
}
}
But with this code alone, everytime the app gets restarted the image will be null. So I tried to upload the image to the Firebase Storage when the user picks an image and generate a url:
Future uploadImage () async {
FirebaseStorage storage = FirebaseStorage.instance;
Reference ref = storage.ref().child(userID.toString());
UploadTask uploadTask = ref.putFile(image!);
uploadTask.whenComplete(() async {
url = await ref.getDownloadURL(); }
).catchError((onError){
print(onError);
});
return url;
}
But again every time I restart the app, the url will be null.
What is the best way to save the image permenantly when the user signs in for the first time.
edit: I want to store the image locally so that the user doesnt need an internet connection to load the image everytime he open the app.
Your answers and responses are highly appreciated.
you need to get folder directory first
Future<String> getStorageDirectory() async {
if (Platform.isAndroid) {
return (await getExternalStorageDirectory()).path;
} else {
return (await getApplicationDocumentsDirectory()).path;
}
}
Add image in path
uploadImage() async{
String dir= getStorageDirectory();
File directory = new File("$dir");
if (directory.exists() != true) {
directory.create();
}
final image = await ImagePicker().pickImage(
source: source);
if (image == null) return;
final userImage = File(image.path);
var newFile = await userImage.writeAsBytes(/* image bytes*/);
await newFile.create();
}

Firebase Storage URL is FutureString

in my flutter app, the user picture is loaded by Cached Network image command, which gets its url by stream builder from firestore.
I am trying to add the functionality to the user of changing his pic by pressing on the pic as following:
Selecting his pic with image picker.
upload it to firebase storage.
updating firestore usercollection document with new image url.
I created the below code.
The problem is getDownloadURL() is not returning actual string, but "Instance of 'Future'".
so the new link stored in firestore is not correct to be used by Cached Network Image.
how can I get the actual URl String?
My Future Function Code:
Future ChangeProfilePic() async {
String newimageurl = "";
FirebaseStorage storage = FirebaseStorage.instance;
Reference ref =
storage.ref().child("ProfileImages/$globaluserid".toString());
CollectionReference userscollectionref =
FirebaseFirestore.instance.collection('UsersCollection');
final ImagePicker _picker = ImagePicker();
final XFile? image = await _picker.pickImage(source: ImageSource.gallery);
File imagefile = File(image!.path);
UploadTask uploadTask = ref.putFile(imagefile);
uploadTask.whenComplete(() {
newimageurl = ref.getDownloadURL().toString();
print("Image Uploaded");
userscollectionref
.doc(globaluserid)
.update({'User_image_link': newimageurl});
print("Link is Updated");
}).catchError((onError) {
print("Error");
print(onError);
});
}
Like many calls in your code `` is an asynchronous call, whose result won't be available immediately, so it returns a Future that will at some point contain the value. You can use await to wait for such a Future to complete and get its value, similar to what you already do in await _picker.pickImage.
await ref.getDownloadURL().toString();
Another change to consider is that putFile returns a Task, but that is actually also a Future, which means that you can await that too.
Combining these two fact, you can simplify your code to:
final XFile? image = await _picker.pickImage(source: ImageSource.gallery);
File imagefile = File(image!.path);
await ref.putFile(imagefile);
newimageurl = (await ref.getDownloadURL()).toString();
print("Image Uploaded");
userscollectionref
.doc(globaluserid)
.update({'User_image_link': newimageurl});
print("Link is Updated");

Await putfile never finishes flutter firebase storage

I'm currently getting an audio file's path on my devices using flutter file picker but when I try to upload it to storage the await never completes for the upload task. However, if I don't await for the uploadtask to finish I can see it in my bucket. What am I doing wrong
This is the upload to storage code
final firebase_storage.Reference storageRef = firebase_storage
.FirebaseStorage.instance
.ref('audio.m4a');
Future<void> uploadFile(String filePath) async {
File file = File(filePath);
print(file.path);
firebase_storage.UploadTask task = storageRef.putFile(file);
print(task);
String url = await (await task).ref.getDownloadURL();
print(url);
}
This is the code for my button which calls the upload
ElevatedButton(
child: Text('Run Future',
style: TextStyle(fontSize: 20)),
onPressed: () {
uploadFile(path)
.then((value) => print("in builder"));
},
)))
Edit: So my file uploads fine but it never finishes the await. The download URL is never retrieved. I tried to listen to snapshots of the task but nothing ever gets printed
I don't know if the other answer is correct. However, this is how I do such a task.
task = storageRef.putFile(_uniImage);
task.whenComplete(() {
print("image uploaded");
});
url = await profReference.getDownloadURL();
Now, you can return this url, if you wish to. It is up to you.

Flutter form builder package image picker firestore flutter

i am using FormBuilderImagePicker from package Flutter form builder
I want to use the img path but i am not able to do so
sending() async {
var storageimage =
FirebaseStorage.instance.ref().child('/google/google');
var task = storageimage.putFile();
imgurl = await (await task.onComplete).ref.getDownloadURL();
// await Firestore.instance.collection('twst').add(
// {
// 'img': imgurl.toString(),
// },
// );
}
i want to use that function with the imagepicker
but the problem is i am not able to find path to use putfile
To get the path of the FormBuilderImagePicker, the toString() method of the class prints the path.
Here is an example of how you can print in a container the Text field including FormBuilderImagePicker which have the path.
Then you will need to pass the image or file to the putFile method.
You can also use the ImagePicker pickImage class method to get the file.
sending() async {
File image;
try {
//Get the file from the image picker and store it
image = await ImagePicker.pickImage(source: ImageSource.gallery);
// Throws error when you don't select any image or when you don't have permissions
} on PlatformException catch (e) {
return;
}
//Create a reference to the location you want to upload to in firebase
StorageReference reference = FirebaseStorage.instance.ref().child("/google/google");
//Upload the file to Firebase
StorageUploadTask uploadTask = reference.putFile(image);
StorageTaskSnapshot taskSnapshot = await uploadTask.onComplete;
// Waits till the file is uploaded then stores the download URL
String url = await taskSnapshot.ref.getDownloadURL();
}