Home > Software design >  Swift 5 - Fetch The Returned Data From A Google Firebase Cloud Function And Put It Into An Array
Swift 5 - Fetch The Returned Data From A Google Firebase Cloud Function And Put It Into An Array

Time:03-14

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.

  • Related