I've been trying to convert the document retrieved from the Firebase's Cloud Firestore to a custom object in Swift 5. I'm following the documentation:
https://firebase.google.com/docs/firestore/query-data/get-data#custom_objects
However, Xcode shows me the error Value of type 'NSObject' has no member 'data' for the line try $0.data(as: JStoreUser.self). I've defined the struct as Codable.
The code:
func getJStoreUserFromDB() {
db = Firestore.firestore()
let user = Auth.auth().currentUser
db.collection("users").document((user?.email)!).getDocument() {
(document, error) in
let result = Result {
try document.flatMap {
try $0.data(as: JStoreUser.self)
}
}
}
}
The user struct:
public struct JStoreUser: Codable {
let fullName: String
let whatsApp: Bool
let phoneNumber: String
let email: String
let creationDate: Date?
}
The screenshot:
Does anyone know how to resolve this?
After contacting the firebase team, I found the solution I was looking for. It turns out I have to do import FirebaseFirestoreSwift explicitly instead of just doing import Firebase. The error will disappear after this. (And of course you'll need to add the pod to your podfile first:D)
You can do it as shown below:-
First create model class:-
import FirebaseFirestore
import Firebase
//#Mark:- Users model
struct CommentResponseModel {
var createdAt : Date?
var commentDescription : String?
var documentId : String?
var dictionary : [String:Any] {
return [
"createdAt": createdAt ?? "",
"commentDescription": commentDescription ?? ""
]
}
init(snapshot: QueryDocumentSnapshot) {
documentId = snapshot.documentID
var snapshotValue = snapshot.data()
createdAt = snapshotValue["createdAt"] as? Date
commentDescription = snapshotValue["commentDescription"] as? String
}
}
Then you can convert firestore document into custom object as shown below:-
func getJStoreUserFromDB() {
db = Firestore.firestore()
let user = Auth.auth().currentUser
db.collection("users").document((user?.email)!).getDocument() { (document, error) in
// Convert firestore document your custom object
let commentItem = CommentResponseModel(snapshot: document)
}
}
You need to initialize your struct and then you can extend the QueryDocumentSnapshot and QuerySnapshot like:
extension QueryDocumentSnapshot {
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
}
}
extension QuerySnapshot {
func toObject<T: Decodable>() throws -> [T] {
let objects: [T] = try documents.map({ try $0.toObject() })
return objects
}
}
Then, try to call the Firestore db by:
db.collection("users").document((user?.email)!).getDocument() { (document, error) in
guard error == nil else { return }
guard let commentItem: [CommentResponseModel] = try? document.toObject() else { return }
// then continue with your code
}
In the past, I had some issues though importing FirebaseFirestore with the package manager in my project.
So I explain about the access to FirebaseFirestore in swift.
SnapshotListener
import Foundation
import FirebaseFirestore
class BooksViewModel: ObservableObject {
#Published var books = [Book]()
private var db = Firestore.firestore()
func fetchData() {
db.collection("books").addSnapshotListener { (querySnapshot, error) in
guard let documents = querySnapshot?.documents else {
print("No documents")
return
}
self.books = documents.map { queryDocumentSnapshot -> Book in
let data = queryDocumentSnapshot.data()
let title = data["title"] as? String ?? ""
let author = data["author"] as? String ?? ""
let numberOfPages = data["pages"] as? Int ?? 0
return Book(id: .init(), title: title, author: author, numberOfPages: numberOfPages)
}
}
}
}
using uid and getDocument function
Firestore.firestore().collection("users").document(uid).getDocument { snapshot, error in
if let error = error {
self.errorMessage = "Failed to fetch current user: \(error)"
print("Failed to fetch current user:", error)
return
}
guard let data = snapshot?.data() else {
self.errorMessage = "No data found"
return
}
let uid = data["uid"] as? String ?? ""
let email = data["email"] as? String ?? ""
let profileImageUrl = data["profileImageUrl"] as? String ?? ""
self.chatUser = ChatUser(uid: uid, email: email, profileImageUrl: profileImageUrl)
}
Related
I would like to display data from the Firestore database into my mobile app using the Swift TableView however I am stuck with the coding.
import UIKit
import Firebase
import FirebaseAuth
import FirebaseFirestore
class BookListViewController: UIViewController {
var bookCollectionRef: CollectionReference!
var books = [Books]()
var id: String
var bookAuthor: String
var bookSummary: String
var bookTitle: String
override func viewDidLoad() {
super.viewDidLoad()
bookCollectionRef = Firestore.firestore().collection("bookData")
bookCollectionRef.getDocuments { [weak self] (snapshot, error ) in
if let err = error {
debugPrint("Error fetching docs: \(err)")
} else {
guard let snap = snapshot else {return}
for document in snap.documents {
let data = document.data()
_ = data[self?.bookAuthor ?? <#default value#>] as? String ?? "Anonymous"
_ = data[self?.bookSummary] as? String ?? ""
_ = data[self?.bookTitle] as? String ?? ""
The collection that I wanted to display is the bookData:
I think you forget to append your books array with fetched data and don't forget to reload tableview.
Try this code:
else {
if let snapshot = snapshot {
for document in snapshot.documents {
let data = document.data()
let author = data["bookAuthor"] as? String ?? ""
let title = data["bookTitle"] as? String ?? ""
let summary = data["bookSummary"] as? String ?? ""
let newBook = (bookAuthor:author,booktitle:title,bookSummary:summary)
self.books.append(newBook)
}
self.tableview.reloadData()
In my page named service, xcode points to the user and gives an error. but it doesn't work. What do you think should I change?
my user is already optional. I think it is an index problem but I don't know how to solve it I would appreciate it if you could help.where do you think the problem
message.swift
import Firebase
struct Message {
let text: String
let toId: String
let fromId: String
var timestamp: Timestamp!
var user: User?
let isFromCurrentUser :Bool
init(dictionary: [String: Any]) {
self.text = dictionary["text"] as? String ?? ""
self.toId = dictionary["toId"] as? String ?? ""
self.fromId = dictionary["fromId"] as? String ?? ""
self.timestamp = dictionary["timestamp"] as? Timestamp ?? Timestamp(date: Date())
self.isFromCurrentUser = fromId == Auth.auth().currentUser?.uid
}
}
struct Conversation {
let user: User
let message : Message
}
Service.Swift
import Firebase
struct Service {
static func fetchUsers (completion: #escaping([User]) -> Void) {
var users = [User] ()
COLLECTION_USERS.getDocuments { (snapshot, error) in
snapshot?.documents.forEach({ (document) in
let dictionary = document.data()
let user = User(dictionary: dictionary)
users.append(user)
completion(users)
})
}
}
static func fetchUser(widhtUid uid: String, completion:#escaping([User]) ->Void) {
COLLECTION_USERS.document(uid).getDocument { (snapshot, error) in
guard let dictionary = snapshot?.data() else {return}
let user = User(dictionary: dictionary)
completion(user)
}
}
static func fetchConversations (completion: #escaping([Conversation]) ->Void) {
var conversations = [Conversation]()
guard let uid = Auth.auth().currentUser?.uid else {return}
let query = COLLECTION_MESSAGES.document(uid).collection("recent-messages").order(by: "timestamp")
query.addSnapshotListener { (snapshot, error) in
snapshot?.documentChanges.forEach({ change in
let dictionary = change.document.data()
let message = Message(dictionary: dictionary)
self.fetchUser(widhtUid: message.toId) { user in
let conversation = Conversation(user:user, message: message)
conversations.append(conversation)
completion(conversations)
}
})
}
}
static func fetchMessages (forUser user: User, completion: #escaping([Message])-> Void) {
var messages = [Message]()
guard let currentUid = Auth.auth().currentUser?.uid else {return}
let query = COLLECTION_MESSAGES.document(currentUid).collection(user.uid).order(by: "timestamp")
query.addSnapshotListener{(snapshot,error) in
snapshot?.documentChanges.forEach({ change in
if change.type == .added {
let dictionary = change.document.data ()
messages.append(Message(dictionary: dictionary))
completion(messages)
}
})
}
}
static func uploadMessage(message: String, to user: User, completion: ((Error?)->Void)?) {
guard let currentUid = Auth.auth().currentUser?.uid else {return}
let data = ["text": message,
"fromId": currentUid,
"toId": user.uid,
"timestamp" : Timestamp(date: Date())] as [String : Any]
COLLECTION_MESSAGES.document(currentUid).collection(user.uid).addDocument(data:data) { _ in
COLLECTION_MESSAGES.document(user.uid).collection(currentUid).addDocument(data:data,completion:completion)
COLLECTION_MESSAGES.document(currentUid).collection("recent- messages").document(user.uid).setData(data)
COLLECTION_MESSAGES.document(user.uid).collection("recent- messages").document(currentUid).setData(data)
}
}
}
In this method:
static func fetchUser(widhtUid uid: String, completion:#escaping ([User]) -> Void)
The completion closure's parameter should be a User, not an array of users - [User].
Xcode should point you to the line where this error happens...
Anyway, here
static func fetchUser(widhtUid uid: String, completion:#escaping([User]) ->Void) {
COLLECTION_USERS.document(uid).getDocument { (snapshot, error) in
guard let dictionary = snapshot?.data() else {return}
let user = User(dictionary: dictionary)
completion(user)
}
}
Your completion:#escaping([User]) ->Void) expects an array [User] , but you invoke it with just one User object here completion(user)
I have a users collection in firebase and a user struct. I need to write a function that takes in the user's id and returns the corresponding user object:
struct AppUser: Codable {
var id: String
var displayName: String
var photoURL: String
var points: Int?
var knownLanguageCodes: Set<String>?
}
This is my function that I have so far.
func getUser(id: String) -> AppUser? {
let db = Firestore.firestore()
let userRef = db.collection("users").document(id)
userRef.getDocument { (document, error) in
if let document = document, document.exists {
let userID = document.data()?["id"] as! String
let userDisplayName = document.data()?["displayName"] as! String
let userPhotoURL = document.data()?["photoURL"] as! String
let userPoints = document.data()?["points"] as! Int?
let userKnownLanguageCodes = document.data()?["knownLanguageCode"] as! Set<String>?
let user = AppUser(id: userID,
displayName: userDisplayName,
photoURL: userPhotoURL,
points: userPoints,
knownLanguageCodes: userKnownLanguageCodes)
return user
} else {
print("Error getting user")
return nil
}
}
}
Both of the return statements above give the error: Unexpected non-void return value in void function
I have looked at the code here https://cloud.google.com/firestore/docs/query-data/get-data under the heading 'Custom objects' and it doesn't seem to work for me. I get the error: Value of type 'NSObject' has no member 'data'. This is produced on line 6 of the code in the link.
You can't return inside a closure use a completion like
func getUser(id: String,completion:#escaping((AppUser?) -> ())) {
let db = Firestore.firestore()
let userRef = db.collection("users").document(id)
userRef.getDocument { (document, error) in
if let document = document, document.exists {
let userID = document.data()?["id"] as! String
let userDisplayName = document.data()?["displayName"] as! String
let userPhotoURL = document.data()?["photoURL"] as! String
let userPoints = document.data()?["points"] as! Int?
let userKnownLanguageCodes = document.data()?["knownLanguageCode"] as! Set<String>?
let user = AppUser(id: userID,
displayName: userDisplayName,
photoURL: userPhotoURL,
points: userPoints,
knownLanguageCodes: userKnownLanguageCodes)
completion(user)
} else {
print("Error getting user")
completion(nil)
}
}
}
Call
getUser(id:<#str#>) { user in
print(user)
}
im new to swift and Firebase. And im trying to understand it.
I have a Cloud Firestore DB, where i store some user data like username and email. Now i want the output of the given User assign to my UserData Model for easy use. i really dont know how to achiev this.
These are my two Files:
User.swift
import SwiftUI
import Firebase
import FirebaseFirestore
struct User {
var username : String
var email : String
}
GetData.swift
let docRef = db.collection("users").document(Auth.auth().currentUser?.uid)
docRef.getDocument { (document, error) in
if let document = document, document.exists {
let dataDescription = document.data().map(String.init(describing:)) ?? "nil"
print("Document data: \(dataDescription)")
} else {
print("Document does not exist")
}
}
The Variable dataDescription is givin me a dictonaryArray, with all the needed values. And now i need to assign that Dictonary to my User.swift struct.
best regards!
fYI: Firebase Printscreen
I'd recommend using Codable where you can. I'm using the CodableFirebase Cocoapod to help with Firebase parsing. Below is an example how to use this:
let docRef = db.collection("users").document(Auth.auth().currentUser?.uid)
docRef.getDocument { (document, error) in
guard let value = document.value, value as? NSNull == nil else {
return
}
do {
let newValue = try self.decoder.decode(User.self, from: value)
///New value created here
} catch let error {
print(error)
}
}
You can make this more generic by passing through a class type and having an optional instance returned:
static func item<T: Codable>(_ item: T.Type, docRef: DatabaseReference, completion: #escaping ((Result<T?, Error>)->Void)) {
docRef.getDocument { (document, error) in
guard let value = snapshot.value, value as? NSNull == nil else {
completion(.success(nil))
return
}
do {
let newValue = try self.decoder.decode(T.self, from: value)
completion(.success(newValue))
} catch let error {
completion(.failure(error))
}
}
}
try
GetData.swift
var user = User()
let docRef = db.collection("users").document(Auth.auth().currentUser?.uid)
docRef.getDocument { (document, error) in
if let document = document, document.exists {
let dataDescription = document.data().map(String.init(describing:)) ?? "nil"
print("Document data: \(dataDescription)")
user.username = document.data(["username"]) as! String
user.email= document.data(["email"]) as! String
} else {
print("Document does not exist")
}
}
I'm querying some data from my firestore,and I put it in my Usersdata,
but I dont know how to get my values from Usersdata.
Please help me to query my data!
This is my struct base on Firestroe example
struct Usersdata {
let uid:String?
let facebook:String?
let google:String?
let name:String?
let age:Int?
let birthday:String?
let smokeage:Int?
let smokeaddiction:Int?
let smokebrand:String?
let gold:Int?
let score:Int?
let fish:Int?
let shit:Int?
let userimage:String?
init?(dictionary: [String: Any]) {
guard let uid = dictionary["uid"] as? String else { return nil }
self.uid = uid
self.facebook = dictionary["facebook"] as? String
self.google = dictionary["google"] as? String
self.name = dictionary["name"] as? String
self.age = dictionary["age"] as? Int
self.birthday = dictionary["birthday"] as? String
self.smokeage = dictionary["smokeage"] as? Int
self.smokeaddiction = dictionary["smokeaddiction"] as? Int
self.smokebrand = dictionary["smokebrand"] as? String
self.gold = dictionary["gold"] as? Int
self.score = dictionary["score"] as? Int
self.fish = dictionary["fish"] as? Int
self.shit = dictionary["shit"] as? Int
self.userimage = dictionary["userimage"] as? String
}
}
this is my function to query data from firebase
func test(schema:String , collection:String , document : String){
let queryRef = db.collection("Users").document(userID).collection(collection).document(document)
queryRef.getDocument { (document, error) in
if let user = document.flatMap({
$0.data().flatMap({ (data) in
return Usersdata(dictionary: data)
})
}) {
print("Success \(user)")
} else {
print("Document does not exist")
}
}
}
I think you are asking how to work with a structure with Firebase data. Here's a solution that will read in a known user, populate a structure with that data and then print the uid and name.
Assume a stucture
Users
uid_0
name: "Henry"
and then a structure to hold that data
struct Usersdata {
let uid:String?
let user_name:String?
init(aDoc: DocumentSnapshot) {
self.uid = aDoc.documentID
self.user_name = aDoc.get("name") as? String ?? ""
}
}
and a function to read that user, populate the struct and print out data from the struct
func readAUser() {
let docRef = self.db.collection("Users").document("uid_0")
docRef.getDocument { (document, error) in
if let document = document, document.exists {
let aUser = Usersdata(aDoc: document)
print(aUser.uid, aUser.user_name)
} else {
print("Document does not exist")
}
}
}
and the output
uid_0 Henry