I have a Google Firebase Cloud Function that returns an array of items. In my iOS app, I need to fetch the returned array and append it into another array in my Swift code.
Here's what I've got so far in Swift:
struct Item: Identifiable {
let id: UUID
let user: String
let name: String
let icon: String
let latitude: Double
let longitude: Double
init(id: UUID = UUID(), user: String, name: String, icon: String, latitude: Double, longitude: Double) {
self.id = id
self.user = user
self.name = name
self.icon = icon
self.latitude = latitude
self.longitude = longitude
}
}
@State var items = [Item]() // This is the array that I need to put the returned data into.
Functions.functions().httpsCallable("getItems").call() { result, error in
// This is where I'm stuck. I need to put the items from the result into the items array.
}
This is what the result?.data equals:
Optional({
items = (
{
icon = snow;
latitude = "39.13113";
longitude = "-84.518387";
name = Clothing;
user = tS7T8ATGCLTZOXi3ZGr0iaWWJAf1;
},
{
icon = eyeglasses;
latitude = "37.785834";
longitude = "-122.406417";
name = Glasses;
user = tS7T8ATGCLTZOXi3ZGr0iaWWJAf1;
},
{
icon = "wallet.pass";
latitude = "37.785834";
longitude = "-122.406417";
name = Wallet;
user = tS7T8ATGCLTZOXi3ZGr0iaWWJAf1;
},
{
icon = key;
latitude = "37.785834";
longitude = "-122.406417";
name = Keys;
user = tS7T8ATGCLTZOXi3ZGr0iaWWJAf1;
},
{
icon = laptopcomputer;
latitude = "37.785834";
longitude = "-122.406417";
name = "Laptop/Tablet";
user = tS7T8ATGCLTZOXi3ZGr0iaWWJAf1;
},
{
icon = iphone;
latitude = "37.785834";
longitude = "-122.406417";
name = Phone;
user = tS7T8ATGCLTZOXi3ZGr0iaWWJAf1;
}
);
})
Firebase Function:
exports.getItems = functions.https.onCall(async (data, context) => {
let resultMessage;
let items = [];
if (context.auth.uid) {
const itemsRef = db.collection('items');
const itemsRes = await itemsRef.orderBy('timestamp', 'desc').limit(255).get();
if (!itemsRes.empty) {
itemsRes.forEach(itemDoc => {
const item = {
user: itemDoc.data().user,
name: itemDoc.data().name,
icon: itemDoc.data().icon,
latitude: itemDoc.data().latitude,
longitude: itemDoc.data().longitude
}
items.push(item);
});
resultMessage = "Successfully got the items.";
} else {
resultMessage = "Failed to get the items because there are no items to get.";
}
} else {
resultMessage = "Failed to get the items because the user does not have an ID.";
}
functions.logger.log(resultMessage);
return {
items: items
};
});
I feel like this should be super easy but am still very new to Swift. I read Google Firebase's Documentation and figured out how to get a single variable but not an array. I have also been searching the internet and have had no luck finding a solution. Any help would be greatly appreciated!!
CodePudding user response:
To answer your question directly, your cloud function is returning a native JavaScript object which you need to properly cast to a native Swift object. Your cloud function actually returns a "dictionary" (Object
in JavaScript) that only contains an array so I would ditch this pattern and just return the array (otherwise you'll have to just unpack it again on the client and that's redundant). Therefore, in your cloud function, just return the array:
return items;
This array will be packaged as an NSArray
on the Swift client in the result's data
property. Therefore, simply cast it as such. Each item in that array is then packaged as an NSDictionary
which you can treat as a Swift [String: Any]
dictionary.
Functions.functions().httpsCallable("getItems").call() { result, error in
// Cast your data as an array of Any
guard let items = result?.data as? [Any] else {
if let error = error {
print(error)
}
return // forgot to add this!
}
// Iterate through your array and cast each iteration as a [String: Any] dictionary
for item in items {
if let item = item as? [String: Any],
let name = item["name"] as? String,
let user = item["user"] as? String,
let icon = item["icon"] as? String,
let latitude = item["latitude"] as? Double,
let longitude = item["longitude"] as? Double {
print(name)
let item = Item(name: name...) // instantiate
items.append(item) // append
}
}
}
There are much more attractive ways of parsing this data. You can map the array, integrate the type casting in the loop, use Firestore's SwiftUI API which is just a couple lines of code, etc.
If, however, you insist on returning a "dictionary" from the cloud function then just modify the guard statement on the client and cast it appropriately:
guard let items = result?.data as? [String: Any] else {
return
}
And then get the items
value from the dictionary which would be of type [Any]
and you're off to the races.
One last thing, on the server side, I would consider using a try-catch
block to handle errors since you are already using the async-await
pattern, which I think is a great idea. Remember that these cloud functions must be terminated by either returning a Promise
or throwing a Firebase-specific HTTPS error. You don't do any error handling in your cloud function which I would consider problematic.