Correct order to upload images on firebase - 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.

Related

Flutter Firestore Storage get downloadUrl

I need to get the downloadURL from uplaoding a photo to a Firebase Storage so I can store it inside of a Firestore document. The issue with my code is that the URL that is saved isnt a https//: so I need to get the downloadURL. I was wondering where I need to call it to get the downloadUrl and save it inside of my Firestore Database.
Here is my code:
Future<void> _uploadProfilePhoto(String inputSource) async {
final picker = ImagePicker();
PickedFile? pickedImage;
try {
pickedImage = await picker.getImage(
source: inputSource == 'camera'
? ImageSource.camera
: ImageSource.gallery,
maxWidth: 1920);
final String fileName = path.basename(pickedImage!.path);
File imageFile = File(pickedImage.path);
try {
await storage.ref("avatars/$fileName").putFile(
imageFile,
SettableMetadata(customMetadata: {
'uploaded_by': '$uid',
}));
// Create/Update firesotre document
users.doc(uid).update({
"profilePhoto": fileName,
});
setState(() {});
} on FirebaseException catch (error) {
print(error);
}
} catch (err) {
print(err);
}
}
You can call getDownloadURL() on the reference at any time after the file upload has completed. So this would be a good spot:
await storage.ref("avatars/$fileName").putFile(
imageFile,
SettableMetadata(customMetadata: {
'uploaded_by': '$uid',
}));
var downloadURL = await storage.ref("avatars/$fileName").getDownloadURL();
// Create/Update firesotre document
users.doc(uid).update({
"profilePhoto": downloadURL,
});

Is there any way to save flutter_tts file to firebase storage?

I am working on a flutter project in which user is supposed to create some scripts and by typing them in text and then flutter_tts library is supposed to convert them to audio file which works fine for that time being but I want to save that file into firebase storage for later user. I have tried the following code but it just saves blank audio file in the firebase storage. Any kind of help will be appreciated.
The code I have tried is:
final FlutterTts _flutterTts = FlutterTts();
late var fileName;
/// creation of audio script
Future createAudioScript(
String name,
String script,
String firebasepath,
) async {
await _flutterTts.setLanguage("en-US");
await _flutterTts.setSpeechRate(1.0);
await _flutterTts.setVolume(1.0);
await _flutterTts.setPitch(1.0);
await _flutterTts.setVoice(
{"name": "en-us-x-tpf-local", "locale": "en-US"},
);
await _flutterTts.speak(script);
fileName = GetPlatform.isAndroid ? '$name.wav' : '$name.caf';
print('FileName: $fileName');
var directoryPath =
"${(await getApplicationDocumentsDirectory()).path}/audio/";
var directory = Directory(directoryPath);
if (!await directory.exists()) {
await directory.create();
print('[INFO] Created the directory');
}
var path =
"${(await getApplicationDocumentsDirectory()).path}/audio/$fileName";
print('[INFO] path: $path');
var file = File(path);
if (!await file.exists()) {
await file.create();
print('[INFO] Created the file');
}
await _flutterTts.synthesizeToFile(script, fileName).then((value) async {
if (value == 1) {
print('generated');
var file = File(
'/storage/emulated/0/Android/data/com.solution.thriving/files/$fileName',
);
print(file);
moveFile(file, path, '$firebasepath/$fileName').then((value) {
print('move file: $value');
_app.link.value = value;
print('link: ${_app.link.value}');
});
}
});
}
/// move file from temporary to local storage and save to firebase
Future<String> moveFile(
File sourceFile,
String newPath,
String firebasePath,
) async {
String audioLink = '';
print('moved');
await sourceFile.copy(newPath).then((value) async {
print('value: $value');
await appStorage.uploadAudio(value, fileName, firebasePath).then((audio) {
print(audio);
audioLink = audio;
return audioLink;
});
}).whenComplete(() async {
customToast(message: 'Audio has been generated successfully.');
});
return audioLink;
}
After spending whole day and with the help of a friend, I finally managed to figure out the issue which was being caused because I was using synthesizeToFile() and speak() functions at the same time, which I managed to resolved the issue by changing my code to the following code snippet.
final FlutterTts _flutterTts = FlutterTts();
late var fileName;
/// converting text to speech
Future createAudioScript(
String name,
String script,
String firebasepath,
) async {
await _flutterTts.setLanguage("en-US");
await _flutterTts.setSpeechRate(1.0);
await _flutterTts.setVolume(1.0);
await _flutterTts.setPitch(1.0);
await _flutterTts.setVoice(
{"name": "en-us-x-tpf-local", "locale": "en-US"},
);
if (GetPlatform.isIOS) _flutterTts.setSharedInstance(true);
// await _flutterTts.speak(script);
fileName = GetPlatform.isAndroid ? '$name.wav' : '$name.caf';
log('FileName: $fileName');
await _flutterTts.synthesizeToFile(script, fileName).then((value) async {
if (value == 1) {
log('Value $value');
log('generated');
}
});
final externalDirectory = await getExternalStorageDirectory();
var path = '${externalDirectory!.path}/$fileName';
log(path);
saveToFirebase(path, fileName, firebasPath: '$firebasepath/$name')
.then((value) => {log('Received Audio Link: $value')});
}
/// saving converted audio file to firebase
Future<String> saveToFirebase(String path, String name,
{required String firebasPath}) async {
final firebaseStorage = FirebaseStorage.instance;
SettableMetadata metadata = SettableMetadata(
contentType: 'audio/mpeg',
customMetadata: <String, String>{
'userid': _app.userid.value,
'name': _app.name.value,
'filename': name,
},
);
var snapshot = await firebaseStorage
.ref()
.child(firebasPath)
.putFile(File(path), metadata);
var downloadUrl = await snapshot.ref.getDownloadURL();
print(downloadUrl + " saved url");
return downloadUrl;
}

How to add multiple images in flutter (firebase)

how I can add multiple images and store them in an array in firebase ?
every container has a + button so the user can add single image per container then I WANT TO STORE them in my firebase
any help would be greatly appreciated ^^
what you are showing in your image is fbCloudStore which is use to store information in json structure.
To store the images you may use fbStorge.
here's an snippet I use on my project:
Future<String?> uploadFile({
required FilePickerResult file,
required String fileName,
required FirebaseReferenceType referenceType,
}) async {
try {
final ref = _getReference(referenceType);
final extension = path.extension(file.files.first.name);
final _ref = ref.child(fileName + extension);
late UploadTask uploadTask;
if (kIsWeb) {
uploadTask = _ref.putData(file.files.first.bytes!);
} else {
uploadTask = _ref.putFile(File(file.files.single.path!));
}
var url;
await uploadTask.whenComplete(
() async => url = await uploadTask.snapshot.ref.getDownloadURL());
print(url);
return url;
} on FirebaseException catch (_) {
print(_);
}
}
Note that I'm returning the URL of fbStorage in order to associate it with fbCloudStorage
final url =
await FirebaseStorageSer.to.uploadFile(
file: result,
fileName: STORAGE_USER_PROFILE_PICTURE,
referenceType: FirebaseReferenceType.user,
);
if (url != null) {
await FirebaseAuthSer.to
.updateUserProfile(photoUrl: url);
}
FirebaseReferenceType.user is just a simple enum to simplify ref targets.

Saving to Firebase storage not possible in an isolate

I have an app feature where the user picks images from his phone and then uploads them to Firebase Storage.
I thought that the upload process should be done in a separate isolate.
I keep getting an exception which I think is related to the Multi Image Picker package.
The exception is:
E/flutter (12961): [ERROR:flutter/lib/ui/ui_dart_state.cc(177)]
Unhandled Exception: Exception: NoSuchMethodError: The getter
'defaultBinaryMessenger' was called on null.
When the user presses on the upload button, this method is called:
Future<void> _initIsolate() async {
ReceivePort receivePort = ReceivePort();
receivePort.listen(
(message) {
print(message.toString());
},
onDone: () => print('Done'),
onError: (error) => print('$error'),
);
await compute(
_function, // This function is called in the separate isolate
{
'sendingPort': receivePort.sendPort,
'images': images,
},
);
}
The _function method is as follows:
static void _function(Map<String, dynamic> parameterMap) async {
SendPort sendingPort = parameterMap['sendingPort'];
List<Asset> images = parameterMap['images'];
List<String> urls = [];
int index = 0;
images.forEach(
(image) async {
String url = await getDownloadUrl(image); // a helper method
urls.add(url);
sendingPort.send('Image number: $index uploaded');
index += 1;
},
);
final CollectionReference collectionRef = FirebaseFirestore.instance.collection('offers');
final user = CurrentUser.getCurrentUser();
await collectionRef.doc(user.uid).set(
{
'time': FieldValue.serverTimestamp(),
'urls': urls,
},
);
}
The helper method _getDownloadUrl is as follows:
Future<String> getDownloadUrl(Asset image) async {
String rannum = Uuid().v1();
final ByteData byteData = await image.getByteData(); // --> This produces a defaultBinaryMessenger
final List<int> imageData = byteData.buffer.asUint8List();
Reference reference = FirebaseStorage.instance.ref().child("offers/$rannum");
UploadTask uploadTask = reference.putData(imageData);
TaskSnapshot downloadUrl = await uploadTask.whenComplete(() => null);
Future<String> futureUrl = downloadUrl.ref.getDownloadURL();
return futureUrl;
}
The getByteData method is part of the multi_image_picker package.
The source code is:
Future<ByteData> getByteData({int quality = 100}) async {
if (quality < 0 || quality > 100) {
throw new ArgumentError.value(
quality, 'quality should be in range 0-100');
}
Completer completer = new Completer<ByteData>();
ServicesBinding.instance.defaultBinaryMessenger // --> Exception here. ServicesBinding.instance is null
.setMessageHandler(_originalChannel, (ByteData message) async {
completer.complete(message);
ServicesBinding.instance.defaultBinaryMessenger
.setMessageHandler(_originalChannel, null);
return message;
});
await MultiImagePicker.requestOriginal(_identifier, quality);
return completer.future;
}
Why is the ServicesBinding.instance null?
Since this method is working fine without using Isolates, does this have something to do with the isolates?

Upload multi images to firestore using flutter and get it's download URLs and save all of URLs to firebase

I have a form that have images to upload , when the user try to press on "Submit" button i'm trying to upload list of images to firestore and get all of its URLs and then submit a form to "x" collection in firebase but the writing on "x" collocation done before upload the images and get it's URLs.
I thinks the problem with (async,await).
Appreciate to help me.
List<File> imagePaths= new List() ;
List<String> imageURL= new List() ;
Future<FirebaseUser> getUser() async {
return await _auth.currentUser();
}
Future<void> uploadPic(File _image) async {
String fileName = basename(_image.path);
StorageReference firebaseStorageRef =
FirebaseStorage.instance.ref().child(Random().nextInt(10000).toString()+fileName);
StorageUploadTask uploadTask = firebaseStorageRef.putFile(_image);
var downloadURL = await(await uploadTask.onComplete).ref.getDownloadURL();
var url =downloadURL.toString();
imageURL.add(url); // imageURL is a global list that suppose to contain images URLs
print("\n ---------------------------\n downloadURL :"+ url);
print("\n ---------------------------\n imageURL :"+ imageURL.toString());
}
Submit(BuildContext context) {
//imagePaths is list of file
imagePaths.add(Front_image);
imagePaths.add(Back_image);
imagePaths.forEach((x) => {
uploadPic(x)
});
getUser().then((user) {
crudObj.addNew({
'uid': user.uid,
'name': name,
'images':imageURL,
}).then((result) {
Navigator.pop(context);
}).catchError((e) {
print(e);
});
});
}
You should call your Submit only once your upload task is complete. I would recommend implementing something like this:
Stream uploadImageToFirebaseStorage(File image, String fullPath) {
final StorageReference firebaseStorageRef =
FirebaseStorage.instance.ref().child(fullPath);
StorageUploadTask task = firebaseStorageRef.putFile(image);
return task.events;
}
And then listen to this Stream and only then submit:
uploadImageToFirebaseStorage(
image, 'path/imagename.jpg'
).listen((data) async {
StorageTaskEvent event = data;
if (data.type == StorageTaskEventType.success) {
String downloadUrl = await event.snapshot.ref.getDownloadURL();
await Submit(title, imageUrl: downloadUrl);
return true;
}
if (data.type == StorageTaskEventType.failure) return false;
});
Please take note that I did not re-write your code, I am sharing a possible implementation.

Resources