SwiftUI: upload multiple images to Firebase - firebase

Goal: upload 3 images from SwiftUI app to Firebase, with different URL for each.
Problem: I only managed to upload 1.
What I have tried (but didn't work)....
:
storagePostRef.putData(image1, image2, image3, metadata: metadata) { (storageMetadata, error) in
Full function bellow:
static func savePostPhoto(
//id
userId: String,
image1: Data,
image2: Data,
image3: Data,
// imagesArray : [Data],
metadata: StorageMetadata,
storagePostRef: StorageReference,
onSuccess: #escaping() -> Void,
onError: #escaping(_ errorMessage: String) -> Void)
{
let arrayOfImages : [Data] = [image1, image2, image3]
//image storage
storagePostRef.putData(image1, metadata: metadata) { (storageMetadata, error) in
if error != nil {
onError(error!.localizedDescription)
return
}
//image URL
storagePostRef.downloadURL { (url, error) in
let image1 = url?.absoluteString
let image2 = url?.absoluteString
let image3 = url?.absoluteString
}
}
}

Each call to putData stores a single image, in the location that you call putData on.
So if you want to store three separate images, you'll have to call putData on three difference StorageReference objects. To then get the three download URLs, you call downloadURL on each of the three StorageReference objects too.
storagePostRef1.putData(image1, metadata: metadata) { (storageMetadata, error) in
storagePostRef1.downloadURL { (url1, error) in
let image1 = url?.absoluteString storagePostRef2.putData(image2, metadata: metadata) { (storageMetadata, error) in
storagePostRef2.downloadURL { (url2, error) in
storagePostRef3.putData(image3, metadata: metadata) { (storageMetadata, error) in
storagePostRef3.downloadURL { (url3, error) in
You can probably clean this up a bit, by creating your own helper function that handles the calls to putData and downloadUrl with a single closure/callback.

This works pretty well.
var photoArrayModel = PhotoArrayModel(photoArray: [])
let userPhotosFirstoreRef = self.ref.document(uid)
imagesData.enumerated().forEach { index, imageData in
let userPhotosStorageRef = self.storageRoot.child("user_photos").child(uid).child("image_\(index)")
userPhotosStorageRef.putData(imageData, metadata: nil) { metaData, error in
if error != nil {
promise(.failure(.uploadingPhoto))
}
userPhotosStorageRef.downloadURL { url, error in
if error != nil {
promise(.failure(.uploadingPhoto))
}
guard let urlString = url?.absoluteString else { return promise(.failure(.uploadingPhoto))}
let photo = PhotoModel(ownerID: uid, imageURL: urlString, timeStamp: Date().millisecondsSince1970)
photoArrayModel.photoArray.append(photo)
if photoArrayModel.photoArray.count == imagesData.count {
do {
try userPhotosFirstoreRef.setData(from: photoArrayModel.self)
promise(.success(()))
} catch {
promise(.failure(.uploadingPhoto))
}
}
}
}
}

Related

How to store videos in Firebase using Swift

I currently have a function that allows me to upload an image to Firebase Storage. But how in the world can I achieve the same, with a video instead of an image?
I suspect I have to change UIImage and .jpg to something else. But what is the type of a video?
#State var pickedImages: [UIImage] = []
#State var retrievedImages = [UIImage]()
This is my function:
func uploadImage() {
// Create storage reference
let storageRef = Storage.storage().reference()
// Turn our image into data
let selectedImage = pickedImages[0]
let imageData = selectedImage.jpegData(compressionQuality: 0.8)
guard imageData != nil else {
let er = "THIS: Error while converting to data"
return print(er)
}
// Specifie filepath and name
let path = "images/\(UUID().uuidString).jpg"
let fileRef = storageRef.child(path)
// Upload that data
let uploadTask = fileRef.putData(imageData!, metadata: nil) {metaData, error in
print("THIS: from uploadTAsk")
// Check for errors
if error == nil && metaData != nil {
// Save reference in firestore DB
let db = Firestore.firestore()
db.collection("images").document("user1").setData(["url": path]) { error in
print("THIS: inside closure")
// If there was no errors, display the image
if error == nil {
DispatchQueue.main.async {
self.retrievedImages.append(selectedImage)
}
}
}
}
}
}

Firebase SwiftUI and Firebase Auth - not reading user ID?

Below is the code for my signup page. I want to make it so that when someone creates an account on the sign up page, I create a document in the users collection and include uuid in the document. However, session.session?.uid ends up being nil. Does anyone know why this is?
struct SignUpView: View {
#State var email = ""
#State var password = ""
#State var name = ""
#State var error = ""
#EnvironmentObject var session: SessionStore
func signUp() {
let db = Firestore.firestore()
let user = db.collection("users").document()
let test = db.collection("users").document(user.documentID).collection("routines").document()
session.signUp(email: email, password: password) { (result, error) in
if let error = error {
self.error = error.localizedDescription
print("This is the error \(error)")
return
} else {
self.email = ""
self.password = ""
}
}
user.setData(["id": user.documentID, "email": email]) { (err) in
if err != nil {
print((err?.localizedDescription)!)
return
}
}
print(session.session?.uid)
test.setData(["id:": test.documentID, "msg": "samwell Tarly", "uuid": session.session?.uid]) { (err) in
print("ummmmm test data?")
if err != nil {
print((err?.localizedDescription)!)
return
}
}
}
The Firebase APIs are asynchronous, simply because they access a remote system, across the internet, which takes a little time. The same applies for accessing the local disk, by the way. This blog post explains this in more detail.
Consequently, session.signUp is an asynchronous process. I.e. the call to print(session.session?.uid) is executed before session.signUp returns. Thus, session.session?.uid is still nil.
To work around this, you can nest your calls like this:
session.signUp(email: email, password: password) { (result, error) in
if let error = error {
self.error = error.localizedDescription
print("This is the error \(error)")
return
}
else {
self.email = ""
self.password = ""
user.setData(["id": user.documentID, "email": email]) { (err) in
if err != nil {
print((err?.localizedDescription)!)
return
}
}
}
}
Generally speaking, I would strongly recommend to not perform so much logic in your views, but instead keep your views as anaemic as possible - meaning: put all your logic into view models, and bind the view to the view models by using Combine. This will make your code much cleaner, easier to test, and maintainable.
See https://peterfriese.dev/replicating-reminder-swiftui-firebase-part2/ for how to do this.

ClientException, and i can't print the returned value (the request body)

Alright i'm losing my mind here,
in my flutter app, i'm using this function to perform post requests :
Future<Map> postRequest(String serviceName, Map<String, dynamic> data) async {
var responseBody = json.decode('{"data": "", "status": "NOK"}');
try {
http.Response response = await http.post(
_urlBase + '$_serverApi$serviceName',
body: jsonEncode(data),
);
if (response.statusCode == 200) {
responseBody = jsonDecode(response.body);
//
// If we receive a new token, let's save it
//
if (responseBody["status"] == "TOKEN") {
await _setMobileToken(responseBody["data"]);
// TODO: rerun the Post request
}
}
} catch (e) {
// An error was received
throw new Exception("POST ERROR");
}
return responseBody;
}
The problems are :
I get a ClientException (Not every time)
In another class, I stored the result of this function in a variable, it's supposed to return a Future<Map<dynamic, dynamic>>, when i printed it it shows :
I/flutter ( 9001): Instance of 'Future<Map<dynamic, dynamic>>'
But when i run the same post request directly (without using a function) it worked, and it shows the message that i was waiting for.
note: in both cases (function or not), in the server side it was the same thing.
this is the function where i used the post request:
void _confirm() {
if (_formKey.currentState.saveAndValidate()) {
print(_formKey.currentState.value);
var v = auth.postRequest("se_connecter", _formKey.currentState.value);
print(v);
} else {
print(_formKey.currentState.value);
print("validation failed");
}
}
Well for the second problem, i just did these changes:
void _confirm() async {
and
var v = await auth.postRequest('se_connecter', _formKey.currentState.value);
and yes it is stupid.
For the exception, it was the ssl encryption that caused it, so i removed it from my backend.

Thread 1: EXC_BAD_ACCESS Trying to upload images to Firebase

I am trying to upload images to Firebase like this:
let storageRef = Storage().reference()
if let uploadData = self.profileImageView.image!.pngData() {
storageRef.putData(uploadData, metadata: nil, completion: { (metadata, error) in
if error != nil {
print(error as Any)
return
}
print(metadata as Any)
})
}
and it's redirecting me to this code here:
- (void)dispatchAsync:(void (^)(void))block {
dispatch_async(self.dispatchQueue, block);
}
It does not get past the if let statement. (if let uploadData = self.profileImageView.image!.pngData())
I have no idea why. It does not give me any additional error messages in the console.
The answer by king_T did not work for me. The issue is related to this line
Storage().reference()
As noted in this post you should use
Storage.storage().reference()
It's very unintuitive.
I just had similar issues, and I solved it by compressing my image.
let scaledimage = self.profileImageView.image!.jpegData(compressionQuality: 0.5)
let storageRef = Storage().reference()
if let uploadData = scaledimage {
storageRef.putData(uploadData, metadata: nil, completion: { (metadata, error) in
if error != nil {
print(error as Any)
return
}
print(metadata as Any)
})
}
that solved it for me.

Wait for 2 callbacks before instantiating an object

I would like to download from firebase:
data issued from a group profile (Firebase realtime DB)
including...
data issued from the group admin profile (Firebase realtime DB)
a group profile image (Firebase Storage)
Then I can instantiate a group object with its data and its image
First approach, I used 3 nested closures that allowed me to get data, and then to get the image.
It did work, but it was quite long to get sequentially all that stuffs from firebase.
So I tried to use GCD in order to push my 2 latest Firebase queries (user data + group image) at the same time (rather than one after the other), and to wait for the last callback to start instantiating my group.
Is it a correct approach ?
If yes, I find some difficulties to implement it...
My issue : returnedUser and returnedGroupImage are always nil
Here is my bunch of code :
static func getGroup(_ groupID:String, completionBlock: #escaping (_ group: Group?) -> ()) {
dataRef.child("data").child("groups").child(groupID).observe(.value, with: { (snapshot) in
if let snapshotValue = snapshot.value {
guard let name = (snapshotValue as AnyObject).object(forKey: "name") as? String else
{
completionBlock(nil)
return
}
guard let adminID = (snapshotValue as AnyObject).object(forKey: "adminID") as? String else
{
completionBlock(nil)
return
}
let queue = DispatchQueue(label: "asyncQueue", attributes: .concurrent, target: .main)
let dispatch_group = DispatchGroup()
var returnedUser: User?
var returnedGroupImage: UIImage?
queue.async (group: dispatch_group) {
FireBaseHelper.getUser(adminID, completionBlock: { (user) in
if user != nil {
returnedUser = user
}
})
}
queue.async (group: dispatch_group) {
FireBaseHelper.getGroupImage(groupID, completionBlock: { (image) in
if image != nil {
returnedGroupImage = image
}
})
}
dispatch_group.notify(queue: DispatchQueue.main) {
// Single callback that is supposed to be executed after all tasks are complete.
if (returnedUser == nil) || (returnedGroupImage == nil) {
// always true !
return
}
let returnedGroup = Group(knownID: (snapshotValue as AnyObject).key, named: name, createdByUser: currentUser!)
returnedGroup.groupImage = returnedGroupImage
completionBlock(returnedGroup)
}
}
})
}
Thanks for your help !
I believe that the way you are using DispatchGroups are not correct.
let dispatch_group = DispatchGroup()
var returnedUser: User?
var returnedGroupImage: UIImage?
dispatch_group.enter()
FireBaseHelper.getUser(adminID, completionBlock: { (user) in
if user != nil {
returnedUser = user
}
dispatch_group.leave()
})
dispatch_group.enter()
FireBaseHelper.getGroupImage(groupID, completionBlock: { (image) in
if image != nil {
returnedGroupImage = image
}
dispatch_group.leave()
})
dispatch_group.notify(queue: DispatchQueue.main) {
// Single callback that is supposed to be executed after all tasks are complete.
if (returnedUser == nil) || (returnedGroupImage == nil) {
// always true !
return
}
let returnedGroup = Group(knownID: (snapshotValue as AnyObject).key, named: name, createdByUser: currentUser!)
returnedGroup.groupImage = returnedGroupImage
completionBlock(returnedGroup)
}

Resources