Search code examples
iosswiftfirebasegoogle-cloud-firestoreswift4

Swift iOS - Add data as custom model containing custom model to Firestore


So I have two custom model classes. One is a Story and the other is Page. The Story contains multiple properties including an array of pages.

struct Story {

var name: String
var pages: [Page]
var tags: [String]
var likes: [String]
var isPrivate: Bool

var dictionary: [String: Any] {
    return [
        "name": name,
        "pages": pages,
        "tags": tags,
        "likes": likes,
        "isPrivate": isPrivate,
    ]
}

struct Page {

let thumbnail: String
let image: String?
let video: String?

var dictionary: [String: Any] {
    return [
        "thumbnail": thumbnail,
        "image": image as Any,
        "video": video as Any
    ]
}

When I upload to Firebase I basically I want the pages object to be a subcollection within the Story object. But I am confused on how I would correctly do this in such a way that I can have the models properly map and decode from the Firebase Response.

The only way I can think of doing this is to create a Story on the database, get a callback with the ID and then create the page within that. The problem is that my Story model wouldn't then include the custom Page model


Solution

  • First, you don't need to write to Firestore to get the auto-generated Id of that document. Firestore auto generates Ids on the client (it's a combination of timestamp and random).

    Second, you may want to consider adding a property to Story that carries the array of pages in dictionary format:

    struct Page {
    
        let thumbnail: String
        let image: String?
        let video: String?
    
        var dictionary: [String: Any] {
            return [
                "thumbnail": thumbnail,
                "image": image as Any,
                "video": video as Any
            ]
        }
    
    }
    
    struct Story {
    
        var name: String
        var pages: [Page]
        var pagesData: [[String: Any]] // I added this for convenience
        var tags: [String]
        var likes: [String]
        var isPrivate: Bool
    
        var dictionary: [String: Any] {
            return [
                "name": name,
                "pages": pages,
                "tags": tags,
                "likes": likes,
                "isPrivate": isPrivate,
            ]
        }
    
    }
    

    Then create your pages as normal:

    let page1 = Page(thumbnail: "abc", image: nil, video: nil)
    let page2 = Page(thumbnail: "xyz", image: nil, video: nil)
    

    Create a story and inject the pages into it, including the datafied version:

    let story = Story(name: "Story", pages: [page1, page2], pagesData: [page1.dictionary, page2.dictionary], tags: ["kiwi", "mango"], likes: ["x", "y"], isPrivate: false)
    

    Use Firestore to generate a random identifier:

    let docRef = Firestore.firestore().collection("someCollection").document()
    let docId = docRef.documentID
    

    You can now inject this identifier into every object before writing to Firestore if you need them to share it.

    Create the data object that will be written to Firestore:

    let data: [String: Any] = [
    
        "name": story.name,
        "pages": story.pagesData
        // etc
    
    ]
    

    In the backend, the pages field will be an array of maps.

    Then just write the data to Firestore:

    docRef.setData(data) { (error) in
    
        if let error = error {
            print("🤬 \(error)")
        } else {
            print("👌")
        }
    
    }