For loop only executes once when calling function in flutter - firebase

This is my first experience with Flutter, just trying to create a super simple app that uploads a bunch of photos to firebase-storage to assist our media & marketing teams internally.
I have successfully implemented multi_image_picker, I can select images and build out the grid view, the images are stored in a list.
I have an upload button, when pressed it calls a uploadImages Function which should loop over the list of images created by multi_image_picker and call the saveImage function to save the images to Firebase.
While it triggers, it only ever uploads a single image and never the entire list as such, the for looop never seems to keep running.
Button to Call uploadImages function:
RaisedButton(
child: Text("Upload Images"),
onPressed: () {
uploadImages(images);
},),
uploadImages function with for loop:
void uploadImages(List<Asset> images) async {
for (var i = 0; i < images.length; i++) {
await saveImage(images[i]);
}
}
Finally the saveImage function that works, but only ever uploads a single image:
Future saveImage(Asset i) async {
ByteData byteData = await i.getByteData();
List<int> imageData = byteData.buffer.asUint8List();
StorageReference ref =
FirebaseStorage.instance.ref().child("$now " + "$displayName.jpg");
StorageUploadTask uploadTask = ref.putData(imageData);
StorageTaskSnapshot storageSnapshot = await uploadTask.onComplete;
var downloadURL = await storageSnapshot.ref.getDownloadURL();
if (uploadTask.isComplete) {
final String url = downloadURL.toString();
return url;
} else {
print('Error from image repo ${storageSnapshot.error.toString()}');
}}
I've tried to many different things, found a few errors with debugger and resolved, but after days of trying I am just unable to get it to keep uploading the images per the for loop. Can someone see what I have done wrong here?

Related

Flutter Web: Images uploaded to firebase storage not showing preview

I am migrating one of my apps to the web with flutter for the first time. My app uses firebase packages to post images to firebase storage. We know "file path" doesn't work on the web so I had to change to byte. But on the firebase storage, I discovered that the images posted through bytes cannot be previewed. If you click on the token, rather than a preview, it will just download straight away.
Here is a pictorial illustration of what I mean:
Images, when clicked are supposed to be able to show a preview like this
But this is what I get
I see they are uploaded as documents instead of file.
Please how do I rectify this.
This is my code below
Future selectFile() async {
final result = await FilePicker.platform.pickFiles(allowMultiple: false, type: FileType.image);
if (result == null) return;
final path = result.files.single;
setState(() => imageFile = path);
}
//upload
Future upload() async {
if (imageFile == null) return;
final fileName = imageFile!.name;
final destination = 'FTV/Thumbnails/$fileName';
// task = FirebaseApi.uploadFile(destination, file!);
task = FirebaseApi.uploadBytes(destination, fileBytes!);
setState(() {});
if (task == null) return;
final snapshot = await task!.whenComplete(() {});
var urlDownload = await snapshot.ref.getDownloadURL();
// print('Download-Link: $urlDownload');
setState(() {
imageURL = urlDownload;
});
}

Correct order to upload images on firebase

To upload images for an object created by a user I store the images (selected by the user) in an array 'imagesList' as a File. When the user clicked upload (whole object) the following method saves the data on firebase:
TextButton(
onPressed: () async {
await uploadImage();
await jobService.createJob(Job(
titleTextEditorController.text.trim(),
category,
false,
false,
finalImageList));
},
child: Text('upload')),
The List finalImageList is filled in the first method 'uploadImage()'. I sourced it out in another method to get the await statement. The Code:
uploadImage() async {
for (int i = 0; i < imageList.length; i++) {
_imageFile = imageList[i];
String fileName = Path.basename(_imageFile!.path);
Reference reference =
FirebaseStorage.instance.ref().child('uploads/$fileName');
firebase_storage.SettableMetadata(
contentType: 'image/jpeg',
customMetadata: {'picked-file-path': fileName});
UploadTask uploadTask = reference.putFile(_imageFile!);
uploadTask.whenComplete(() async {
try {
imageUrl = await reference.getDownloadURL();
print('imageUrl' + imageUrl);
finalImageList.add(imageUrl);
} catch (onError) {
print("Upload Error");
}
});
await Future.value(uploadTask)
.then((value) => {print('Upload file path ${value.ref.fullPath}')})
.onError((error, stackTrace) =>
{print('Upload file path error ${error.toString()}')});
}
}
But the method is not fast enough to store the imageUrl's in finalImageList, so the Images is online but its not connected to the object in firebase. Is there a possibility to upload it immediately or the save the imageUrl correctly? Or is my code just in the wrong order?
The FlutterFire UploadTask class extends Future, which means that you can use await on it to wait until the upload is done.
That means you can write your code much simpler as:
await reference.putFile(_imageFile!);
imageUrl = await reference.getDownloadURL();
finalImageList.add(imageUrl);
print('Upload file path ${value.ref.fullPath}')
With this change, your uploadImage will only complete after the download URL was added to finalImageList.

How to get list of documents and not listen for changes in flutter cloud firestore?

My application is again fetching list of items from firestore whenever I make a sort locally.
Due to which I am losing my sorted list and getting the original list back again.
Essentially, I am looking for a .once() alternative as I used in firebase realtime db with JS.
fetchItemsFromDb().then((itemsFromDb) {
setState(() {
items = itemsFromDb;
isProcessed = true;
});
});
fetchItemsFromDb() async {
List<Item> items = [];
await Firestore.instance.collection('items').getDocuments().then((data) {
items = data.documents.map((DocumentSnapshot item) {
var i = item.data;
return Item(
i['itemName'],
int.parse(i['quantity']),
int.parse(i['price']),
i['veg'],
i['uid'],
i['timestamp'],
LatLng(i['location'].latitude, i['location'].longitude),
);
}).toList();
});
return items;
}
FetchItemsFromDB() should be working how you expect it to, it could be that the code calling the function:
fetchItemsFromDb().then((itemsFromDb) {
setState(() {
items = itemsFromDb;
isProcessed = true;
});
});
is being run again when you do not expect it. Does that code live in a build method? If so it will run anytime the widget it is in rebuilds, which depending on how you are doing your local sort may be happening. If you only need it to run once maybe add it to the initState() function for the widget.

Issue adding element to list

I'm a total newbie to Flutter and I'm trying to add some data from Cloud Firestore to a list in Flutter, but having issues. I try to add the element, but after executing, the element isn't there. It's not throwing an exception or anything either. Maybe someone else has some advice for me!
I have tried changing the type of list (capture the doc from Cloud Firestore instead of data within the doc, same issue), I have also debugPrinted the data I am trying to store to make sure it exists, it does. I have done basic troubleshooting like running flutter clean as well. I am on the latest version of Flutter.
Firestore db = firestore();
List<String> getString() {
var dataList = new List<String>();
db.collection('Users').get().then((querySnapshot) {
querySnapshot.forEach((doc) {
dataList.add(doc.get('First name'));
});
});
debugPrint(dataList.first);
return dataList;
The list is empty, though it should contain the "First name" field on this Cloud Firestore doc. Again, verified the data does exist and prints when calling debugPrint.
The db.collection('Users').get() is a async function, so debugPrint(dataList.first); executes before of the end of your firestores get, because that your array returns empty.
If you try it:
db.collection('Users').get().then((querySnapshot) {
querySnapshot.forEach((doc) {
dataList.add(doc.get('First name'));
});
debugPrint(dataList.first);
});
You will see your data.
You can use await to wait the call finishes, so you must return a Future and use async key word on function declaration. This is a conceipt that you must know of flutter async functions (Async Flutter). So, the code below can solve your problem.
Firestore db = firestore();
Future <List<String>> getString() async {
var dataList = new List<String>();
var result = await db.collection('Users').get();
result.forEach((doc) {
dataList.add(doc.get('First name'));
});
debugPrint(dataList.first);
return dataList;
}

Flutter: Restrict calling build method many times

I am implementing a chat application where users can share images. Each image is a stateful widget and each of them should get uploaded to the Firebase Storage as well.
My problem is, this flow works fine at the beginning of the app but when you upload another image, instead of the a single file, now 2 files are getting uploaded (1 new file and the file from the previous message).
I am pretty sure that this is something to do with keys so I provide an instance of UniqueKey as well but the problem is still there.
Let me explain my implementation and then provide the code:
I have 2 files; one is the chat screen, and the other one is a single message chip.
Chat screen keeps a list of message chips and does the rendering accordingly.
Message chip is stateful because, I want the user to see a progress while it is being uploaded to the server. After picking up a file from the device, an instance of message chip will get pushed to the array in the chat screen.
My code for the attach pic button in chat screen:
IconButton(
icon: Icon(Icons.attach_file),
onPressed: () async {
final File _file = await ImagePicker.pickImage(
source: ImageSource.gallery);
if (_file != null) {
//appending to the messages list
final sss = new MediaMessage(
key: UniqueKey(),
file: _file,
isImage: true,
threadId: widget._threadId,
);
setState(() {
_messages.add(sss);
});
}
},
)
and here is the code in my message chip file (including only the essentials)
void initState() {
super.initState();
//creating a file name eg: img_456985.jpg
final rand = Math.Random().nextInt(10000);
final fileExt = widget.file.path
.substring(widget.file.path.lastIndexOf('.'), widget.file.path.length);
_fileName = 'image_$rand$fileExt';
final StorageReference storeRef = FirebaseStorage.instance
.ref()
.child('threads')
.child(widget.threadId)
.child(_fileName);
final uploadTask = storeRef.putFile(widget.file);
uploadTask.events.listen((event) {
setState(() {
_uploadPercentage = event.snapshot.bytesTransferred.toDouble() /
event.snapshot.totalByteCount.toDouble();
});
print(_uploadPercentage);
});
uploadTask.onComplete.then((snapshot) {
setState(() {
_uploadStatus = UploadProgressStatus.complete;
});
});
}
Here is a demo GIF of this issue:
GIF image demo
Any suggestions/solutions would be appreciated.
Thank you
Null the file value after image uploading task might work.
like
widget.file=null
place it after uploadTask

Resources