Getting an empty array from Firestore although it is not nil - swift

I'm trying to get a String array I have inside a document in my Firestore Database.
I added (Manually) one item to this array, but when I'm trying to get the items in that array, I always get an empty array instead.
This is my code so far:
func getUserFollowingList(id userId: String, completion: #escaping(Array<String>)->()) {
let followingArray = [String]()
db.collection(USERS_COLLECTION).document(userId).getDocument { (doc, error) in
if let err = error {
print(err.localizedDescription)
completion(followingArray)
}
guard let following = doc?.get(USER_FOLLOWING) else { return }
let followingList = following as! [String]
completion(followingList)
}
}
I'm trying to get the firends list of the current user.
This is my Firestore database:
following
0 "9RtM0wgiKee6I5Lo8EugTFihyXE3"
profile_image: "IMAGE_URL"
useremail: "user#test.com"
username: "Testuser"

Related

Swift+Firestore - return from getDocument() function

I'm currently dealing with this problem. I have Firestore database. My goal is to fill friends array with User entities after being fetched. I call fetchFriends, which fetches currently logged user, that has friends array in it (each item is ID of friend). friends array is then looped and each ID of friend is fetched and new entity User is made. I want to map this friends array to friends Published variable. What I did there does not work and I'm not able to come up with some solution.
Firestore DB
User
- name: String
- friends: [String]
User model
struct User: Identifiable, Codable {
#DocumentID var id: String?
var name: String?
var email: String
var photoURL: URL?
var friends: [String]?
}
User ViewModel
#Published var friends = [User?]()
func fetchFriends(uid: String) {
let userRef = db.collection("users").document(uid)
userRef.addSnapshotListener { documentSnapshot, error in
do {
guard let user = try documentSnapshot?.data(as: User.self) else {
return
}
self.friends = user.friends!.compactMap({ friendUid in
self.fetchUserAndReturn(uid: friendUid) { friend in
return friend
}
})
}
catch {
print(error)
}
}
}
func fetchUserAndReturn(uid: String, callback:#escaping (User)->User) {
let friendRef = db.collection("users").document(uid)
friendRef.getDocument { document, error in
callback(try! document?.data(as: User.self) as! User)
}
}
One option is to use DispatchGroups to group up the reading of the users but really, the code in the question is not that far off.
There really is no need for compactMap as user id's are unique and so are documentId's within the same collection so there shouldn't be an issue where there are duplicate userUid's as friends.
Using the user object in the question, here's how to populate the friends array
func fetchFriends(uid: String) {
let userRef = db.collection("users").document(uid)
userRef.addSnapshotListener { documentSnapshot, error in
guard let user = try! documentSnapshot?.data(as: User.self) else { return }
user.friends!.forEach( { friendUid in
self.fetchUserAndReturn(uid: friendUid, completion: { returnedFriend in
self.friendsArray.append(returnedFriend)
})
})
}
}
func fetchUserAndReturn(uid: String, completion: #escaping ( MyUser ) -> Void ) {
let userDocument = self.db.collection("users").document(uid)
userDocument.getDocument(completion: { document, error in
guard let user = try! document?.data(as: User.self) else { return }
completion(user)
})
}
Note that I removed all the error checking for brevity so be sure to include checking for Firebase errors as well as nil objects.

How to Fetch Multiple Types From CloudKit using CKRecord.ID?

When fetching multiple types from CloudKit using CKRecord.ID I get the following error.
Error
Cannot invoke 'map' with an argument list of type '(#escaping (CKRecord.ID, String, CKAsset, Int) -> Lib)'
CloudKit Fetch Function
static func fetch(_ recordID: [CKRecord.ID], completion: #escaping (Result<[Lib], Error>) -> ()) {
let recordID: [CKRecord.ID] = recordID
let operation = CKFetchRecordsOperation(recordIDs: recordID)
operation.qualityOfService = .utility
operation.fetchRecordsCompletionBlock = { (record, err) in
guard let record = record?.values.map(Lib.init) ?? [] //returns error here
else {
if let err = err {
completion(.failure(err))
}
return
}
DispatchQueue.main.async {
completion(.success(record))
}
}
CKContainer.default().publicCloudDatabase.add(operation)
}
Lib
struct Lib {
var recordID: CKRecord.ID
var name: String
var asset: CKAsset
var rating: Int
}
How can I retrieve multiple types from CloudKit using the CKRecord.ID?
You haven't defined an initializer that accepts a CKRecord.
This will make it compile:
extension Lib {
init(_: CKRecord) { fatalError() }
}
Get rid of your ?? [] and go from there!
It may help you if you use accurate pluralization:
operation.fetchRecordsCompletionBlock = { records, error in
guard let libs = records?.values.map(Lib.init)
The answer from #Jessey was very helpful but kept returning a fatalError(). What ended up working was adding to the init.
Lib
struct Lib {
var recordID: CKRecord.ID
var name: String
var asset: CKAsset
var rating: Int
init(record: CKRecord){
recordID = record.recordID
name = (record["name"] as? String)!
asset = (record["asset"] as? CKAsset)!
rating = (record["rating"] as? Int)!
}
}
The "name", "asset", and "rating" are the custom field names in CloudKit dashboard records. I also got rid of the ?? [] in the fetch function per the instruction.
CloudKit Tutorial: Getting Started was a good reference.

Why converting a Firestore querySnapshot into custom objects with compactMap returns empty although the querySnapshot contains documents?

Screenshot of a Firestore Document
I am using Swift, Xcode and a Firestore database.
I created a TableView and a Custom Object Class (MediumSample) with a dictionary and want to load my Firestore documents and show them in the TableView.
The documents (example in the screenshot) are loaded from Firestore correctly but the conversion into the object did not work. The list of objects returned from compactMap is always empty.
Here is my code snippets. It would be great, if someone has a hint on what is going wrong.
Custom Object Class (simplified):
import Foundation
import FirebaseFirestore
protocol MediumSampleDocumentSerializable {
init?(dictionary: [String:Any])
}
struct MediumSample {
var id: String
var field_t: String
var field_i: Int64
var field_b1: Bool
var field_b2: Bool
var field_g: FirebaseFirestore.GeoPoint
var field_d: Date
var field_a: [String]
var usecase: String
var dictionary: [String:Any] {
return [
"id": id,
"field_t": field_t,
"field_i": field_i,
"field_b1": field_b1,
"field_b2": field_b2,
"field_g": field_g,
"field_d": field_d,
"field_a": field_a,
"usecase": usecase
]
}
}
extension MediumSample: MediumSampleDocumentSerializable {
init?(dictionary: [String:Any]) {
guard let id = dictionary ["id"] as? String,
let field_t = dictionary ["field_t"] as? String,
let field_i = dictionary ["field_i"] as? Int64,
let field_b1 = dictionary ["field_b1"] as? Bool,
let field_b2 = dictionary ["field_b2"] as? Bool,
let field_g = dictionary ["field_g"] as? FirebaseFirestore.GeoPoint,
let field_d = dictionary ["field_d"] as? Date,
let field_a = dictionary ["field_a"] as? [String],
let usecase = dictionary ["usecase"] as? String else {return nil}
self.init (id: id, field_t: field_t, field_i: field_i, field_b1: field_b1, field_b2: field_b2, field_g: field_g, field_d: field_d, field_a: field_a, usecase: usecase)
}
}
Declaration of the database and array and calling the loading function:
import UIKit
import FirebaseFirestore
class MediumTableViewController: UITableViewController {
//MARK: Properties
var db: Firestore!
var mediumsamples = [MediumSample]()
override func viewDidLoad() {
super.viewDidLoad()
db = Firestore.firestore()
loadMediumSamples()
}
Function for loading the Firestore documents to fill the Array:
private func loadMediumSamples() {
//run the Firestore query
db.collection(Constants.MEDIUMS).whereField("usecase", isEqualTo: Constants.USECASE)
.getDocuments() { querySnapshot, err in
if let err = err {
print("Error getting documents: \(err)")
} else {
//initialise an array of medium objects with Firestore data snapshots
self.mediumsamples = querySnapshot!.documents.compactMap({MediumSample(dictionary: $0.data())})
//fill the tableView
DispatchQueue.main.async {
self.tableView.reloadData()
print(self.mediumsamples)
}
print("Mediums List", self.mediumsamples) // This line returns: Mediums List []
print("Mediums List size", (self.mediumsamples.count)) // This line returns: Mediums List size 0
for document in querySnapshot!.documents {
print("\(document.documentID) => \(document.data())") // This line returns the snapshot documents correctly!
}
}
}
}
Here is how the screenshot object object is added:
func addMediumSamples() {
let currentDateTime = Date()
let location = FirebaseFirestore.GeoPoint(latitude: 0, longitude: 0)
let mediumsample = MediumSample(id: "an id", field_t: "field_t", field_i: 10, field_b1: true, field_b2: false, field_g: location, field_d: currentDateTime, field_a: [Constants.SAMPLE_DEV], usecase: Constants.SAMPLE_DEV)
var ref: DocumentReference? = nil
ref = self.db.collection(Constants.MEDIUMS).addDocument(data: mediumsample.dictionary) {
error in
if let error = error {
print("Error writing city to Firestore: \(error)")
} else {
print("Document added with id : \(ref!.documentID)")
}
}
}
The problem is in the MediumSample struct, in the field_d type (Date).
The type of that field in your Cloud Firestore database is Timestamp.
The field "field_d" in the MediumSample struct expects a value of type Date.
You can change the type to the FirebaseFirestore.Timestamp, or you can convert it to Date when mapping and before passing to the MediumSample.
eg. for converting Timestamp to Date in Swift
let date = timestamp.dateValue()

Firebase Swift 3 Fetching multiple values from a group

I want to be able to list users in a tableview by fetching their UID Value from a key stored in my database. As of now, the code only fetches the first value in the database rather than all of the values. Here is the code for fetching the applicants.
func loadApplicants() {
let usersRef = ref.child("users")
usersRef.observe(.value, with: { (users) in
var resultArray = [UserClass]()
for user in users.children {
let user = UserClass(snapshot: user as! DataSnapshot)
if user.uid == self.job.userID {
let appRef = self.ref.child("jobs").child(self.job.postID).child("applicants")
appRef.queryOrderedByKey().observeSingleEvent(of: .childAdded, with: { (snapshot) in
let sValue = snapshot.value
resultArray.append(user)
})
}
}
}) { (error) in
print(error.localizedDescription)
}
}
This is what my database looks like where the User's UIDs are stored.
jobs
"Job ID"
applicants:
-KtLJaQnFMnyI-MDWpys:"8R6ZAojX0FNO7aSd2mm5aQXQFpk1"
-KtLLBFU_aVS_xfSpw1k:"GGqjtYvwSwQw9hQCVpF4lHN0kMI3"
If I was to run the app, it fetches UID: "8R6ZAojX0FNO7aSd2mm5aQXQFpk1"
How can I implement a for loop or an if statement to ensure that all of the values are taken and appended into the table view
I know that I need a for loop before the fetchApplicants is called from AuthService because it is only fetching one UID but I can't work out where it would go.
Thanks.
P.S. This is what I have tried
func loadApplicants() {
let jobID = job.postID
let appRef = ref.child("jobs").child(jobID!).child("applicants")
appRef.queryOrderedByKey().observeSingleEvent(of: .childAdded, with: { (snapshot) in
if let applicants = snapshot.value! as? [String:AnyObject] {
for (value) in applicants {
self.authService.fetchApplicants(applicantID: "\(value!)", completion: { (users) in
self.usersArray = users
self.tableView.reloadData()
})
}
}
})
}
but the output is:
(key: "-KtLLBFU_aVS_xfSpw1k", value: GGqjtYvwSwQw9hQCVpF4lHN0kMI3)
(key: "-KtLJaQnFMnyI-MDWpys", value: 8R6ZAojX0FNO7aSd2mm5aQXQFpk1)
Needed to use observe instead of observeSingleEvent
Answer:
appRef.queryOrderedByKey().observe(.childAdded, with: { (snapshot) in

how to get the .child key in an array?

I want to take the save all of my blocked user child as a string in my code. But as simple as it might sound I was not able to figure it out.
static func block(myself: String, posterUID: String){
var blockedUserRef = Database.database().reference().child("users").child(myself).child("blockedUsers").child(posterUID)
blockedUserRef.setValue(true)
var blokedArray:[String] = []
var handle:DatabaseHandle!
blockedUserRef = Database.database().reference()
handle = blockedUserRef.child("blockedUsers").observe(.childAdded, with:{ (DataSnapshot) in
if let item = DataSnapshot.value as? String{
blokedArray.append(item)
}
})
}
I want to take the array of my blocked users go through in for loop and filter them from showing.
ref.observeSingleEvent(of: .value, with: { (snapshot) in
guard let snapshot = snapshot.children.allObjects as? [DataSnapshot]
else { return completion([]) }
// 3 filter the user from the array
let users =
snapshot
.flatMap(User.init)
.filter { $0.uid != currentUser.uid }
Here is my firebase:
"users" : {
"3YLVUDA8YnSuVYUTxv6cvOQPSjm1" : {
"username" : "🌸😍🌸"
},
"4duY2hhTv5S7sSXtZYBfiP7JBLz1" : {
"username" : "Love from NYC"
},
"58t0M2Fxxhg6GRT96vVbKMFHRKO2" : {
"blockedUsers" : {
"4duY2hhTv5S7sSXtZYBfiP7JBLz1" : true,
"SqarOdPJUydcdV6deXTeIdzkarE2" : true
},
"username" : "IStandWithYou"
},
some new ways I am trying I debugged it and it seems that it is not saving it onto the array
///adedblock
var blockedUserRef = Database.database().reference().child("users").child(currentUser.uid).child("blockedUsers")
var blokedArray:[String] = []
var handle:DatabaseHandle!
blockedUserRef = Database.database().reference()
handle = blockedUserRef.child("blockedUsers").observe(.childAdded, with:{ (DataSnapshot) in
if let item = DataSnapshot.value as? String{
blokedArray.append(item)
}
})
for user in blokedArray{
let users =
snapshot
.flatMap(User.init)
.filter { $0.uid != user }
}
This can be done by saving all of the child entries of blockedUsers to an array that will hold all your strings.
To do this, first create an empty array like
myList:[string] = []
Then, you need to create a reference to your Firebase database like:
var ref: DatabaseReference!
You also need a database handle to navigate around your database:
var handle:DatabaseHandle!
Now, you need to go to your ViewDidLoad function and add code like:
ref = Database.database().reference()
handle = ref?.child("blockedUsers").observe(.childAdded, with:{ (DataSnapshot) in
if let item = DataSnapshot.value as? String{
self.myList.append(item)
}
})
This piece of code is checking whether an entry is added to the child database "blockedUsers", and from that, it is adding whatever was just entered into the array.
Now, you have an array filled with whatever was in the child database, blockedUsers. You can now use the array in a UITableView, or list it in a label.
Hope this helped!