I am experiencing this weird behavior when loading assets from external storage, sometimes the path gets added to the list and most of the time the path is not added.
Here is my function, Am I missing something?
Future<List<String>> loadAssets() async {
List<String> loadedAssets = [];
loadedAssets.add('test');
try {
final Directory dir = await syspath.getExternalStorageDirectory();
dummyData.forEach((path) async {
final extPath =
path.substring(('assets/products_dummy_data/'.length));
final localPath='${dir.path}/$extPath}';
final file = File(localPath);
if (await file.exists()) {
await file.delete();
}
final data = await rootBundle.load(path);
var asUint8List =
data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);
await file.create(
recursive: true,
);
await file.writeAsBytes(asUint8List);
if (await file.exists()) {
loadedAssets.add(localPath);
}
});
} catch (e, s) {
AppHelper.appLogger.e('Error while loading assets', e, s);
}
AppHelper.appLogger.i('loadedAssets.length ${loadedAssets.length}');
return loadedAssets;
}
But I always get the length as one, for the test element added
Problem Fixed after using await Future.forEach
Related
I list all pdf files from storage and now I want to delete multi-files in my flutter list . as well as from the device file manager. I am using this function but when I delete and restart the app the file comes again
This is the function I'm using to delete the list:
void deleteItems() {
var list = myMultiSelectController.selectedIndexes;
list.sort((b, a) => a.compareTo(b));
list.forEach((element) {
files.removeAt(element);
});
setState(() {
myMultiSelectController.set(files.length);
});
}
files.removeAt(element); Just removes file from the list. You need to actually delete file from device.
eg
Future<String> get _localPath async {
final directory = await getApplicationDocumentsDirectory();
return directory.path;
}
Future<File> get _localFile async {
final path = await _localPath;
print('path ${path}');
return File('$path/counter.txt');
}
Future<int> deleteFile() async {
try {
final file = await _localFile;
await file.delete();
} catch (e) {
return 0;
}
}
See more here from SO answer
I have a List<File> imageFiles made up of files that a user selects and when they press submit, each of the files is uploaded to the collection in Firebase Storage. For each one, the downloadUrl is added to another list uploadedImages. I cannot upload the document to Firestore until all the downloadUrls are added to the uploadedImages list so I used an if statement if (imageFiles.length == uploadedImages.length) {<upload document>}. The problem is, in those lists are not the same length until the forEach block completes, and even then it doesn't return true. I confirmed that all the files are uploaded to Firebase Storage and printed the uploadedImages so I know they are eventually the same length but don't know how to make the if statement recognize that. Here is my current code:
List<String> uploadedUrls = [];
/// Uploads each file and adds its URL
imageFiles.forEach((file) async {
String fileName = '${UniqueKey()}_post.png';
TaskSnapshot uploadTask = await FirebaseStorage.instance
.ref()
.child('/${user.uid}/posts/$postId/$fileName')
.putFile(file);
String url = await uploadTask.ref.getDownloadURL();
setState(() {
uploadedUrls.add(url);
});
print(uploadedUrls);
});
/// Uploads post if all files and URLs are added *NOT WORKING*
if (imageFiles.length == uploadedUrls.length) {
Post post =
Post(postId, uploadedUrls, user.uid, profile, location, Timestamp.now());
try {
FirebaseFirestore.instance
.collection('users')
.doc(user.uid)
.collection('posts')
.doc(postId)
.set(post.toJson()).then((value) {
setState(() {
_isLoading = false;
});
});
} on FirebaseAuthException {
setState(() {
_isLoading = false;
});
}
}
Because you are running an asynchronous function in forEach,it won’t wait until the task is completed. So you can either move the if condition inside the forEach’s callback below the setState or use a parent function as wrapper for both them, and it should await the first call
imageFiles.forEach((file) async {
String fileName = '${UniqueKey()}_post.png';
TaskSnapshot uploadTask = await FirebaseStorage.instance
.ref()
.child('/${user.uid}/posts/$postId/$fileName')
.putFile(file);
String url = await uploadTask.ref.getDownloadURL();
setState(() {
uploadedUrls.add(url);
});
if (imageFiles.length == uploadedUrls.length) {
....
});
or extract the forEach loop into a async function
void addDownloafUrls() asyn {
imageFiles.forEach((file) async { ... });
}
// And you can await this function
addDownloafUrls().then(() {
if (imageFiles.length == uploadedUrls.length) {...}
})
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 " );
}
}
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;
}
}
I am trying to upload multiple images to firebase storage at once. I can upload successfully but cannot retrieve the download URLs;
Future<void> saveImages() async {
int index = images.length;
setState(() {
isLoading = true;
});
try {
final ref = FirebaseStorage.instance
.ref()
.child('images/${FirebaseAuth.instance.currentUser!.uid}');
images.forEach((element) async {
final file = await element.file;
final refPut = ref.child('$element$index');
await refPut.putFile(
File(file!.path),
);
});
final ref2 = ref.child('${imagePciker.path.split('/')}');
await ref2.putFile(File(imagePciker.path));
final ListResult listData = await ref.listAll();
final data =await ref2.getDownloadURL();
print('Im HERE!=Line 95');
Future.forEach(listData.items, (Reference element) async {
await element.getDownloadURL().then((value) => listUrls.add(value));
print('line 101');
print(listUrls.length);
});
print('Line 104');
await FirebaseFirestore.instance.collection('test').add({
'titleDownloadUrl': data,
'ListOfDownloadUrl': listUrls,
});
print('Line 108');
setState(() {
isLoading = false;
});
} catch (e) {
/* setState(() {
isLoading = false;
}); */
print(e);
}
}
The print statements are to debug. This whole function returns and no errors are thrown.
However in the firebase collection 'test' the URLs are not stored correctly.
On the first/fresh run after restart of the app.
the array of ListOfDownloadUrl is empty
On a hot restart(with the no images saved in firebase storage)
the array of ListOfDownloadUrl has one URL
then on multiple restarts, the amount of URLs saved becomes huge(ie. 4 images uploaded-10 URLs saved).
Please comment if I need to add anything else;
Thanks for any Help
.forEach() is NOT ASYNCHRONOUS - it WILL NOT wait. Use await Promise.all(images.map()) (thus creating an array of promises, which must all resolve).
Solved!
Following the thinking of #LeadDreamer
The forEach isn't ASYNCHRONOUS, the problem with his answer is that flutter does not have
Promise.all()
What it does have if Future.wait(), which acts(in my case) the same way.
Future<void> saveImages() async {
int index = images.length;
setState(() {
isLoading = true;
});
try {
final refMain = FirebaseStorage.instance
.ref()
.child('images${FirebaseAuth.instance.currentUser!.uid}');
AssetEntity eleId =
new AssetEntity(id: '', typeInt: 5, width: 5, height: 5);
await Future.wait(images.map((element) async { <-- here instead of the for each
setState(() {
eleId = element;
});
final file = await element.file;
final refPut = refMain.child('suportImage$_counter/$element$index');
await refPut.putFile(
File(file!.path),
);
}));
final ref2 =
refMain.child('headingImage$_counter/${imagePciker.path.split('/')}');
await ref2.putFile(File(imagePciker.path));
final ListResult listData =
await refMain.child('suportImage$_counter').listAll();
final data = await ref2.getDownloadURL();
await Future.wait(listData.items.map((e) async { <-- here instead of for each
await e.getDownloadURL().then((value) => listUrls.add(value));
}));