Flutter async/await is not working between 2 Firebase functions - flutter

I'm trying to call a cloud firestore function that has to persist an object only when another function returns the url of the file in Firebase Storage but the async await is not working and the second function is called anyway whereas the first function is not yet completed!!!
await schoolProfileProvider.uploadSchoolProfileAvatar(data).then( (data) {
schoolProfileProvider.addSchoolProfile(data);
});
print('PROFILE ADDED');
Future<SchoolProfileData> uploadSchoolProfileAvatar(SchoolProfileData data) async {
List<File> avatars = [];
data.childrenDetails.forEach((child) {
avatars.add(File(child.childImage));
});
try {
await _api.uploadFilesToStorage(avatars, 'image', 'png').then((urls) {
for (var i = 0; i < urls.length; i++) {
data.childrenDetails[i].childImage = urls[i];
print('ADD ' + data.childrenDetails[i].childImage);
}
});
} on Exception catch (e) {
print(e.toString());
}
return data;
}
T cast<T>(x) => x is T ? x : null;
Future<List<String>> uploadFilesToStorage(List<File> files, String type, String extension) async {
final urls = <Future<String>>[];
files.forEach((file) async {
StorageReference storageRef = storage.ref().child(file.path);
final StorageTaskSnapshot downloadUrl =
(await storageRef
.putFile(file, StorageMetadata(contentType: type + '/' + extension))
.onComplete);
await downloadUrl.ref.getDownloadURL().then((url) {
urls.add(cast<Future<String>>(url));
print('URL for file ${file.path} = ${url.toString()}');
});
});
print ('urls returned');
return Future.wait(urls);
}
Future addSchoolProfile(SchoolProfileData data) async{
var result;
try {
result = await _api.addDocument(data.toJson());
} on Exception catch(e) {
print (e.toString());
}
return result;
}

I've managed to make the things work and execute addSchoolProfile only after uploadFilesToStorage is completed by reducing the nested functions and making the await downloadUrl.ref.getDownloadURL() as the last instruction returned in the callee function.
Please find the code for who is interested in :
The caller :
schoolProfileProvider.addSchoolProfileAndUploadAvatar(data);
Future addSchoolProfileAndUploadAvatar(SchoolProfileData data) async {
List<File> avatars = [];
data.childrenDetails.forEach((child) {
avatars.add(File(child.childImage));
});
try {
for (int i=0;i<avatars.length;i++){
await _api.uploadFileToStorageAndGetUrl(avatars[i], 'image', 'png').then((url) {
print('URL for file ${avatars[i].path} = ${url.toString()}');
data.childrenDetails[i].childImage = url;
print('ADD ' + data.childrenDetails[i].childImage);
});
}
_api.addDocument(data.toJson()) ; // Add document in Google Firebase after setting the avatar url
} on Exception catch (e) {
print(e.toString());
}
}
The callee :
Future <String> uploadFileToStorageAndGetUrl(File file,String type, String extension) async{
StorageReference storageRef = storage.ref().child(file.path);
final StorageTaskSnapshot downloadUrl =
(await storageRef
.putFile(file, StorageMetadata(contentType: type + '/' + extension))
.onComplete);
return await downloadUrl.ref.getDownloadURL(); // return the url of the file in Google Storage
}

Related

Cannot get the download link after uploading files to firebase storage Flutter

so this is the my file picking and file upload code
class Storage with ChangeNotifier {
PlatformFile? pickedFile;
UploadTask? uploadTask;
Future uploadFile() async {
final path = 'files/${pickedFile!.name}.png';
final file = File(pickedFile!.path!);
final ref = FirebaseStorage.instance.ref().child(path);
ref.putFile(file);
try {
final snapshot = await uploadTask!.whenComplete(() {});
final urlDownload = await snapshot.ref.getDownloadURL();
print(urlDownload);
} catch (e) {
print("this is the error $e " );
}
}
void pickFile() async {
FilePickerResult? result = await FilePicker.platform.pickFiles();
if (result != null) {
File file = File(result.files.single.path!);
pickedFile = result.files.first;
} else {
print("no image picked");
}}}
the code works for upload the image but after that i didnt get any download link, the error is "Null check operator used on a null value" i dont know how to fix it, im still new in this topic, help please
i got the answer, need to change the uploadFile method to this
Future uploadFile() async {
final path = 'files/${pickedFile!.name}.png';
final file = File(pickedFile!.path!);
FirebaseStorage storage = FirebaseStorage.instance;
Reference ref = storage.ref().child(path);
UploadTask uploadTask = ref.putFile(file);
uploadTask.then((res) {
res.ref.getDownloadURL();
});
try {
final snapshot = await uploadTask.whenComplete(() {});
final urlDownload = await snapshot.ref.getDownloadURL();
print(urlDownload);
} catch (e) {
print("this is the error $e " );
}
}

uploading multiple xfiles to firebase storage

I am trying to upload multiple (max 5) xfiles images (image_picker library) to firebase storage. currently, it uploads only one with the below method:
Future<void> addProduct({
BuildContext? ctx,
String? proName,
String? productDescription,
double? count,
double? price,
required List<XFile> images
}) async {
try {
var imageUrls = await Future.wait(images.map((_image) =>
uploadFile(_image)));
await firestore
.collection(outletsCollection)
.doc(_currentUserOutletId)
.set({
products: FieldValue.arrayUnion([
{
productId: Uuid().v4(),
productName: proName,
prodDesc: productDescription,
countInStock: count,
productPrice: price,
productImg: FieldValue.arrayUnion([imageUrls]),
}
]),
});
} catch (err) {
String errMsg = "error blahblah";
errorDialog(ctx!, errMsg);
}
notifyListeners();
}
for the uploadFile method i tried using images.forEach() instead of .map(), but it gave me void type error. uploadFile method:
Future<String> uploadFile(XFile _image) async {
var storageReference = storage
.ref()
.child('product_images')
.child(_currentUserOutletId!)
.child(Uuid().v4());
final metadata = SettableMetadata(
contentType: 'image/jpeg',
customMetadata: {'picked-file-path': _image.path},
);
if (kIsWeb) {
await storageReference.putData(await _image.readAsBytes(), metadata);
} else {
await storageReference.putFile(io.File(_image.path), metadata);
}
return await storageReference.getDownloadURL();
}
so, at the end only one image is uploaded and addProduct() does not add product to firestore database. log in android studio returns nothing. I couldn't find a method that would work with the xfile. help appreciated very much!
List<XFile> _imageFileList;
try{
final pickedFile = await _picker.pickMultiImage();
setState(() {
_imageFileList = pickedFile;
_imageFileList.forEach((element) async {
firebase_storage.FirebaseStorage storage = firebase_storage.FirebaseStorage.instance;
var date = DateTime.now().millisecondsSinceEpoch;
try {
setState(() {
_loading = true;
});
firebase_storage.UploadTask task = firebase_storage.FirebaseStorage.instance.ref('uploads/photos/'+date.toString()+'_image.png').putData(await element.readAsBytes(), SettableMetadata(contentType: 'image/jpeg'));
await storage.ref('uploads/file-to-upload.png').putData(await element.readAsBytes(), SettableMetadata(contentType: 'image/jpeg'));
firebase_storage.TaskSnapshot downloadUrl = (await task);
String url = (await downloadUrl.ref.getDownloadURL());
// print(url.toString());
} on FirebaseException catch (e) {
// e.g, e.code == 'canceled'
setState(() {
_loading = false;
});
}
});
try this way using image_picker plugin. You can get url after image upload and you have yo it in your data.

PlatformException error, Invalid document reference, When attempting to work with Firebase Storage

I have created a function to work on my app. This function add's the photo from my camera or gallery into the Firebase storage, and into the user collection. Althought I'm receiving a strange error when trying to add the data. I have attempted to pass throught this Exception but the data wasn't added neither.
The erro:
This is the function:
class Product {
final Firestore firestore = Firestore.instance;
final FirebaseStorage storage = FirebaseStorage.instance;
DocumentReference get firestoreRef => firestore.document('products/$id');
StorageReference get storageRef => storage.ref().child('products').child(id);
Future<void> save() async {
loading = true;
final Map<String, dynamic> data = {
'name': name,
'description': description,
'sizes': exportSizeList()
};
if (id == null) {
final doc = await firestore.collection('products').add(data);
id = doc.documentID;
} else {
await firestoreRef.updateData(data);
}
final List<String> updateImages = [];
for (final newImage in newImages!) {
if (images.contains(newImage)) {
updateImages.add(newImage as String);
} else {
final StorageUploadTask task =
storageRef.child(Uuid().v1()).putFile(newImage as File);
final StorageTaskSnapshot snapshot = await task.onComplete;
final String url = await snapshot.ref.getDownloadURL() as String;
updateImages.add(url);
}
}
for (final image in images) {
if (!newImages!.contains(image)) {
try {
final ref = await storage.getReferenceFromUrl(image);
await ref.delete();
} catch (e) {
debugPrint('Falha ao deletar $image');
}
}
}
await firestoreRef.updateData({'images': updateImages});
images = updateImages;
loading = false;
}
}
From the error message is looks like id doesn't have a value in this call:
firestore.document('products/$id');
When id has no value, that leads to a document reference with a path /products/, which explains the error message.
So you'll want to run the code in a debugger, set a breakpoint on that line, and figure out why id doesn't have a value at that point.

How to await a Map.forEach() in dart

I have a Future which returns a map. I then need to use the values of that map to await another future and then return the entire result at the end. The problem is that dart can't await async Map.forEach() methods (see this: https://stackoverflow.com/a/42467822/15782390).
Here is my code:
the debug console shows that the items printed are in the following order:
flutter: getting journal entries
flutter: about to loop through pictures
flutter: getting picture
flutter: returning entries
flutter: [[....]] (Uint8List)
Future<List<JournalEntryData>> getJournalEntries() async {
List<JournalEntryData> entries = [];
print('getting journal entries');
EncryptService encryptService = EncryptService(uid);
await journal.get().then((document) {
Map data = (document.data() as Map);
print('about to loop through pictures');
data.forEach((key, value) async {
print('getting picture');
dynamic pictures = await StorageService(uid).getPictures(key);
print('done getting image');
entries.add(JournalEntryData(
date: key,
entryText: encryptService.decrypt(value['entryText']),
feeling: value['feeling'],
pictures: pictures,
));
});
});
print('returning entries');
return entries;
}
Future getPictures(String entryID) async {
try {
final ref = storage.ref(uid).child(entryID);
List<Uint8List> pictures = [];
await ref.listAll().then((result) async {
for (var picReference in result.items) {
Uint8List? pic = await ref.child(picReference.name).getData();
if (pic == null) {
// TODO make no picture found picture
var url = Uri.parse(
'https://www.salonlfc.com/wp-content/uploads/2018/01/image-not-found-scaled-1150x647.png');
var response = await http.get(url);
pic = response.bodyBytes;
}
pictures.add(pic);
}
});
return pictures;
} catch (e) {
print(e.toString());
return e;
}
}
It's quite annoying to have to use for-loops when you need async behaviour, specially on Maps, because as the other answer shows, that requires you to iterate over entries and then take the key and value out of it like this:
for (final mapEntry in data.entries) {
final key = mapEntry.key;
final value = mapEntry.value;
...
}
Instead of that, you can write a utility extension that does the work for you:
extension AsyncMap<K, V> on Map<K, V> {
Future<void> forEachAsync(FutureOr<void> Function(K, V) fun) async {
for (var value in entries) {
final k = value.key;
final v = value.value;
await fun(k, v);
}
}
}
Then, you can use that like this:
await data.forEachAsync((key, value) async {
...
});
Much better.
Don't mix the use of then and await since it get rather confusing and things are no longer being executed as you think.
Also, the use of forEach method should really not be used for complicated logic like what you are doing. Instead, use the for-each loop. I have tried rewrite getJournalEntries here:
Future<List<JournalEntryData>> getJournalEntries() async {
List<JournalEntryData> entries = [];
print('getting journal entries');
EncryptService encryptService = EncryptService(uid);
final document = await journal.get();
Map data = (document.data() as Map);
print('about to loop through pictures');
for (final mapEntry in data.entries) {
final key = mapEntry.key;
final value = mapEntry.value;
print('getting picture');
dynamic pictures = await StorageService(uid).getPictures(key);
print('done getting image');
entries.add(JournalEntryData(
date: key,
entryText: encryptService.decrypt(value['entryText']),
feeling: value['feeling'],
pictures: pictures,
));
}
print('returning entries');
return entries;
}
And getPictures here. I have only removed the use of then here.
Future getPictures(String entryID) async {
try {
final ref = storage.ref(uid).child(entryID);
List<Uint8List> pictures = [];
final result = await ref.listAll();
for (var picReference in result.items) {
Uint8List? pic = await ref.child(picReference.name).getData();
if (pic == null) {
// TODO make no picture found picture
var url = Uri.parse(
'https://www.salonlfc.com/wp-content/uploads/2018/01/image-not-found-scaled-1150x647.png');
var response = await http.get(url);
pic = response.bodyBytes;
}
pictures.add(pic);
}
return pictures;
} catch (e) {
print(e.toString());
return e;
}
}

How to track the uploading process on firebase with flutter?

I have this future function that will upload a video to firebase, I want to track this uploading process in percentage, so after the uploading process is completed, I will get the url.
Code
Future storageupload() async {
try {
if (controller = null) {
dialog('Error', 'Please Provide A Video Name', () => {});
} else {
StorageReference ref = FirebaseStorage.instance
.ref()
.child("Khatma 1")
.child("Videos")
.child(controller.text != null ? controller.text : "");
StorageUploadTask uploadTask = ref.putFile(
File(Variables.lastVideoPath),
StorageMetadata(contentType: 'video/mp4'));
}
} catch (e) {
print(e);
}
}
Future uploadToStorage() async {
try {
await storageupload();
final downloadUrl = await FirebaseStorage.instance
.ref()
.child("Khatma 1")
.child('Videos')
.child(controller.text)
.getDownloadURL();
final String url = downloadUrl.toString();
print(url);
} catch (error) {
print(error);
}
}
You can get by listening to TaskSnapshot Stream.
uploadTask.snapshotEvents.listen((TaskSnapshot snapshot) {
double _progress = snapshot.bytesTransferred.toDouble() / snapshot.totalBytes.toDouble();
});
For more info: https://pub.dev/documentation/firebase_storage/latest/firebase_storage/StorageTaskSnapshot-class.html
If you are using an older version of Firebase storage i.e snapshotEvents is not available.
This should work for you.
uploadTask.events.listen((event) {
double _progess = event.snapshot.bytesTransferred.toDouble() / event.snapshot.totalByteCount.toDouble();
});