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")
}
}
Related
I am using this code to retrieve the first name from my Firebase database and trying to display it in a label but it always returns the "Document does not exist in cache". My firebase security is set to read and write true. Do you know what I am doing wrong?
func nameGreeting() -> String{
let db = Firestore.firestore()
let userId = Auth.auth().currentUser!.uid;(Auth.auth().currentUser!.uid);
let docRef = db.collection("users").document(userId)
docRef.getDocument(source: .cache) { (document, error) in
if let document = document {
let name = document.get("firstname")
print("Cached document data: \(String(describing: name))")
} else {
print("Document does not exist in cache")
}
}
return ""
}
you are using an async function (docRef.getDocument...) inside "nameGreeting", so you are returning "" before the firebase function is finished. Try something like this:
func nameGreeting(completion: #escaping ((String?) -> Void)) {
let db = Firestore.firestore()
let userId = Auth.auth().currentUser!.uid
let docRef = db.collection("users").document(userId)
docRef.getDocument(source: .cache) { (document, error) in
if let document = document {
let name = document.get("firstname")
print("Cached document data: \(String(describing: name))")
completion(name)
} else {
print("Document does not exist in cache")
completion(nil)
}
}
}
and call it like this:
nameGreeting() { userName in
print("----> userName: \(userName)")
}
I am working on my first IOS app using Firestore. I am successfully retrieving all user documents or a specific document with a document name from the "users" collection. But as soon as I want to query the currentUsers documents I have trouble. I need the data for a user profile viewController.
I can query a specific document and print it with this code:
func loadUserData() {
let userDocumentRef = Firestore.firestore().collection("users").document(
"6tIzsXgbOMDIFpdgR18i")
userDocumentRef.getDocument { (document, error) in
if let document = document, document.exists {
let userData = document.data().map(String.init(describing: )) ?? "nil"
print("Document data: \(userData)")
} else {
print("document does not exist")
}
}
}
But as soon as I try to get the currentUsers document with this code, then I get the error that "document does not exist":
func currentUserData() {
let userID : String = (Auth.auth().currentUser?.uid)!
let userRef = Firestore.firestore().collection("users").document(userID)
userRef.getDocument { (document, error) in
if let document = document, document.exists {
let userData = document.data().map(String.init(describing: )) ?? "nil"
print("Document data: \(userData)")
} else {
print("document does not exist")
}
}
}
Here is a screenshot of the users collection in Firestore.
Screenshot of Firestore users collection
I just simply cannot figure out what I am doing wrong, so any help is appreciated.
It seems like there is an issue when mapping the document. Here is how you can map data from Firestore to Swift:
struct UserProfile: Codable, Identifiable {
#DocumentID var id: String?
var userId: String
var firstName: String
// ... other attributes
}
func currentUserData() {
// Note: this is unsafe, please use an auth state change listener instead
// Also, please try to avoid using force unwrap - your app will crash if the attribute is nil
let userID : String = (Auth.auth().currentUser?.uid)!
let userRef = Firestore.firestore().collection("users").document(userID)
userRef.getDocument { (document, error) in
if let document = document, document.exists {
let userData = document.data(as: UserProfile.self)
print("Document data: \(userData)")
} else {
print("document does not exist")
}
}
}
Here is how to set up an auth state change listener: https://firebase.google.com/docs/auth/ios/start#listen_for_authentication_state
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)
}
Is there a way of converting an array of type Dictionary.Values? into an one-dimension array of type String?
Code:
docRef.getDocument { (document, error) in
if let document = document, document.exists {
let dataDescription = document.data()?.values // Type 'Dictionary<String, Any>.Values?'
self.array.append(dataDescription) // Tried dataDescription.values, but it doesn't work
print("Document data: \(String(describing: dataDescription))")
} else {
print("Document does not exist")
}
}
You can unwrap data as well and then map the values from Any to String:
docRef.getDocument { document, error in
if let document = document, document.exists,
let data = document.data()?.values {
let values = data.values.compactMap{$0 as? String}
print(values)
} else {
print("Document does not exist")
}
}
}
I have migrated user post, followers and following from from firebase to firestore. Now i have migrated post, followers and following and post, followers count too.
Here the code i have migrated from firebase to firestore.
import Foundation
import FirebaseDatabase
import FirebaseFirestore
class FollowApi {
var REF_FOLLOWERS = Database.database().reference().child("followers")
var REF_FOLLOWING = Database.database().reference().child("following")
let db = Firestore.firestore()
func followAction(withUser id: String) {
let docRef = db.collection("user-posts").document(id)
docRef.getDocument { (document, error) in
if let document = document, document.exists {
let dataDescription = document.data().map(String.init(describing:)) ?? "nil"
print("Document data: \(dataDescription)")
self.db.collection("feed").document(API.User.CURRENT_USER!.uid).setData([document.documentID: true])
} else {
print("Document does not exist")
}
}
self.db.collection("followers").document(id).setData([API.User.CURRENT_USER!.uid: true])
self.db.collection("following").document(API.User.CURRENT_USER!.uid).updateData([id: true])
}
func unFollowAction(withUser id: String) {
let docRef = db.collection("user-posts").document(id)
docRef.getDocument { (document, error) in
if let document = document, document.exists {
let dataDescription = document.data().map(String.init(describing:)) ?? "nil"
print("Document data: \(dataDescription)")
self.db.collection("feed").document(API.User.CURRENT_USER!.uid).delete()
} else {
print("Document does not exist")
}
}
self.db.collection("followers").document(id).setData([API.User.CURRENT_USER!.uid: NSNull()])
self.db.collection("following").document(API.User.CURRENT_USER!.uid).setData([id: NSNull()])
}
func isFollowing(userId: String, completed: #escaping (Bool) -> Void) {
let docRef = db.collection("followers").document(userId)
docRef.getDocument { (document, error) in
if let document = document, document.exists {
print("documnetData::::\(String(describing: document.data()))")
if let dataDescription = document.data(), let _ = dataDescription[API.User.CURRENT_USER!.uid] as? Bool {
completed(true)
}
completed(false)
} else {
completed(false)
}
}
}
func fetchCountFollowing(userId: String, completion: #escaping (Int) -> Void) {
// REF_FOLLOWING.child(userId).observe(.value, with: {
// snapshot in
// let count = Int(snapshot.childrenCount)
// completion(count)
// })
db.collection("following").document(userId).getDocument { (querySnapshot, error) in
let count = Int((querySnapshot?.documentID)!)
print("followingCount::::\(String(describing: count))")
completion(count!)
}
}
}//followAPI
I tried to get following counts from firestore.
let count = Int((querySnapshot?.documentID)!)
print("followingCount::::\(String(describing: count))")
completion(count!)
but does not show any any yet all. I do not know what mistake i have done ?
Any help much appreciated pls....
If you're querying for a collection then its snapshot will contain an array of documents. What are you trying to get is a documentID which is same as key in Firebase.
Firestore | Firebase
documentID = snapshot.key
documentData = snapshot.value
Now, Come to the main point and here is what you need to get the count.
let count = querySnapshot?.documents.count
print(count)
EDIT For Comment: how can i migrate REF_FOLLOWING.child(userId).observe(.value, with: { snapshot in let count = Int(snapshot.childrenCount) completion(count) }) to firestore
Based on attached DB structure you're fetching following corresponding to userId which is a Document.
REF_FOLLOWING.document(userId).getDocument { (snapshot, error) in
if let _error = error {
print(_error.localizedDescription)
return
}
guard let _snapshot = snapshot else {return}
/// This is a single document and it will give you "[String:Any]" dictionary object. So simply getting its count is the result you needed.
let dict = _snapshot.data()
print(dict.count)
}