skip to Main Content

I have a function that fetches a user from FireStore. At the moment the function manually deals with the data. I want to change this using Codable. I have tried many functions but I cannot seem to get it to work. I think the code that is required is quite simple and short & I’m just not understanding something.

Here is the fetchUser function that needs to change to one that doesn’t manually map the data:

let ref = Firestore.firestore()

func fetchUser(uid: String,completion: @escaping (UserModel) -> ()){

    let db = Firestore.firestore()
    @EnvironmentObject var sharedData: SharedDataModel

    ref.collection("Users").document(uid).getDocument { (doc, err) in
        guard let user = doc else{return}

        let username = user.data()?["username"] as? String ?? "No Username"
        let pic = user.data()?["imageurl"] as? String ?? "No image URL"
        let bio = user.data()?["bio"] as? String ?? "No bio"
        let uid = user.data()?["uid"] as? String ?? ""
        let isVerified = user.data()?["isVerified"] as? Bool ?? false

        DispatchQueue.main.async {
            completion(UserModel(username: username, pic: pic, bio: bio, uid: uid, isVerified: isVerified))
        }
    }
}

How do I change this function to manually map the data using Codable?

I want to do this because I want to implement another variable to the user model. This variable is favouriteItems.

Here is my UserModel

import SwiftUI
import FirebaseFirestoreSwift

struct UserModel: Identifiable, Codable{
    @DocumentID var id: String?
    var username : String
    var pic : String
    var bio: String
    var uid : String
    var isVerified: Bool
    var favouriteItems: [FavouriteItems]


Code for FavouriteItem:

import SwiftUI
import FirebaseFirestoreSwift

struct FavouriteItems: Identifiable, Codable {
    
    @DocumentID var id: String?
    var item: Item
}

Code for Item:

import SwiftUI
import FirebaseFirestoreSwift
import Firebase


struct Item: Identifiable, Codable {
    
    @DocumentID var id: String?
    var item_name: String
    var item_type: String
    var item_image: String
    var item_details: String
    var item_uid : String
    var didFavourite: Bool? = false
    var isFavourite: Bool = false  
}

2

Answers


  1. try this approach, is this what you have tried already?
    Since I do not have your database, I cannot test this.

    let ref = Firestore.firestore()
    
    func fetchUser(uid: String, completion: @escaping (UserModel) -> ()){
        let db = Firestore.firestore()
        
        let docRef = db.collection("Users").document(uid).getDocument(as: UserModel.self) { result in
    
            switch result {
            case .success(let user):
                // A `UserModel` value was successfully initialized from the DocumentSnapshot.
                print("UserModel: (user)")
                DispatchQueue.main.async {
                    completion(user)
                }
                
            case .failure(let error):
                // A `UserModel` value could not be initialized from the DocumentSnapshot.
                print("Error decoding UserModel: (error)")
                // handle errors todo
            }
        }
    }
    

    EDIT-1: given your new info.

    I see what is going on here, you have UserModel and nested inside is an array of FavouriteItems, etc…
    But you did not tell us that your FavouriteItems (which shoud be singular) is in another part of the database.
    That is why all answers tried to point you to mapping the simple model UserModel that you showed us at first.

    To obtain your complete UserModel, you need to re-structure your database and your structs, so that you have
    a handle on the different DocumentReference. So UserModel has a field of DocumentReference type, that points to the set of FavouriteItems, and in FavouriteItems a field with DocumentReference that points to the associated Item.

    Have a look at my attempt to deal with nested documents, here: Nested Struct with Document Reference in Swift Firestore

    Note that since I do not have any access to the database, I cannot test any answers, I can only guess.

    Login or Signup to reply.
  2. Add extinsion

    extension DocumentSnapshot {
        func toObject<T: Decodable>() throws -> T {
            let jsonData = try JSONSerialization.data(withJSONObject: data()!, options: [])
            let object = try JSONDecoder().decode(T.self, from: jsonData)
            
            return object
        }
    }
    

    confirm to decodable

    struct UserModel:Decodable {
    }
    

    fetch user

    func fetchUser(uid: String,completion: @escaping (UserModel?) -> ()){
            let ref = Firestore.firestore()
            ref.collection("Users").document(uid).getDocument() { (doc, err) in
                guard err == nil else { completion(nil)
                    return
                }
                guard let model: UserModel = try? doc!.toObject() else {
                    completion(nil)
                    return
                }
                completion(model)
            }
        }
    

    Second Way

    user init with constructor as dict

    struct UserModel {
        
        let uid:String
        let username:String
        let pic:String
        let bio:String
        let isVerified:Bool
        
        init?(value:[String:Any]) {
            guard let id = value["uid"] as? String else {
                return nil
            }
            uid = id
            username = value["username"] as? String ?? "No Username"
            pic = value["imageurl"] as? String ?? "No image URL"
            bio = value["bio"] as? String ?? "No bio"
            isVerified = value["isVerified"] as? Bool ?? false
        }
    }
    

    and fetch

    func fetchUser(uid: String,completion: @escaping (UserModel) -> ()){
    
            let db = Firestore.firestore()
            db.collection("Users").document(uid).getDocument { (doc, err) in
                guard let documentSnap = doc,err == nil else {return}
                if let value = documentSnap.data() ,
                   let user = UserModel(value: value) {
                    completion(user)
                }
            }
        }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search