Always getting nil in completion - swift

I'm trying to get Map data I have in Firestore, this is how it looks:
I'm trying to get the data, and create an array of Friend Object and return the array in the completion handler.
This is what I have:
func fetchFriendList(_ id: String, completion: #escaping([Friend]?)->()) {
var fetchedFriends: [Friend]?
db.collection(USERS_COLLECTION).document(id).getDocument { (doc, err) in
if err == nil && doc != nil {
guard let results = doc?.data()?[USER_FOLLOWING] as? [String: Any] else { return }
for result in results { // Getting the data in firebase
if let resultValue = result.value as? [String: Any] { // Getting only the value of the MAP data, we do not need the key.
//Getting the fields from the result
guard let id = resultValue[FRIEND_ID] as? String else { return }
guard let profilePic = resultValue[FRIEND_PROFILE_PIC] as? String else { return }
guard let username = resultValue[FRIEND_NAME] as? String else { return }
guard let email = resultValue[FRIEND_MAIL] as? String else { return }
//Creating a new Friend object from the fields
let friend = Friend(id: id, profilePicture: profilePic, username: username, email: email)
fetchedFriends?.append(friend)
}
completion(fetchedFriends)
}
}else {
print(err!.localizedDescription)
completion(nil)
}
}
}
I tried printing the results, and resultValue etc, they are not nil.
But, after trying to append and print the fetchedFriends Array, I get nil, and the completion is also nil.
I don't really understand why this is happening.

The problem is that you haven't initialized variable fetchedFriends and you have used optional type when appending data to it. Since it has not been initialized, it will skip appending to it. You should initialize it in the beginning. The updated code would be as follows.
func fetchFriendList(_ id: String, completion: #escaping([Friend]?)->()) {
var fetchedFriends: [Friend] = []
db.collection(USERS_COLLECTION).document(id).getDocument { (doc, err) in
if err == nil && doc != nil {
guard let results = doc?.data()?[USER_FOLLOWING] as? [String: Any] else { return }
for result in results { // Getting the data in firebase
if let resultValue = result.value as? [String: Any] { // Getting only the value of the MAP data, we do not need the key.
//Getting the fields from the result
guard let id = resultValue[FRIEND_ID] as? String else { return }
guard let profilePic = resultValue[FRIEND_PROFILE_PIC] as? String else { return }
guard let username = resultValue[FRIEND_NAME] as? String else { return }
guard let email = resultValue[FRIEND_MAIL] as? String else { return }
//Creating a new Friend object from the fields
let friend = Friend(id: id, profilePicture: profilePic, username: username, email: email)
fetchedFriends.append(friend)
}
completion(fetchedFriends)
}
}else {
print(err!.localizedDescription)
completion(nil)
}
}
}
Hope it helps.

Related

Get data from firestore and assign it to an array of dictionaries

I am trying to get data from firestore collection and assign it to an array of dictionaries. for this part of the code below... i get the error "Cast from 'QuerySnapshot?' to unrelated type '[[String : Any]]' always fails" and the console prints "is not working".
guard let snap = snapshot as? [[String:Any]] else {
print("is not working")
completion(.failure(DatabaseError.failedToFetch))
return
}
Here is the full code.
// fetches and returns all conversations for the user with passed in uid
public func getAllConversations(for uid: String, completion: #escaping(Result<[Conversation], Error>) -> Void) {
print("fetching all convos")
//NEW
let db = Firestore.firestore()
let CurrentUser = Auth.auth().currentUser?.uid
let ListRef = db.collection("users").document(CurrentUser!).collection("conversations")
// fetch the current users convo list
ListRef.getDocuments { snapshot, error in
if let err = error {
debugPrint("Error fetching documents: \(err)")
} else {
guard let snap = snapshot as? [[String:Any]] else {
print("is not working")
completion(.failure(DatabaseError.failedToFetch))
return
}
print("is working")
let conversations: [Conversation] = snap.compactMap({ dictionary in
guard let id = dictionary["id"] as? String,
let name = dictionary["name"] as? String,
let otherUserUID = dictionary["other_user-uid"] as? String,
let latestMessage = dictionary["latest-message"] as? [String:Any],
let date = latestMessage["date"] as? String,
let message = latestMessage["message"] as? String,
let isRead = latestMessage["is-read"] as? Bool else {
return nil
}
//save other user ID to a global var
self.test = otherUserUID
//assign data into an array of dictionaries
let latestConvoObject = LatestMessage(date: date, text: message, isRead: isRead)
return Conversation(id: id, name: name, otherUserUid: otherUserUID, latestMessage: latestConvoObject)
})
completion(.success(conversations))
}
}
}
There are a numbers of way to read that data, and the process can be simplified by conforming objects to the codable protocol but let me provide a straight forward example. I don't know what your Conversation object looks like so here's mine
class ConversationClass {
var from = ""
var to = ""
var msg = ""
var timestamp = 0
convenience init(withDoc: DocumentSnapshot) {
self.init()
self.from = withDoc.get("from") as? String ?? "no from"
self.to = withDoc.get("to") as? String ?? "no to"
self.msg = withDoc.get("msg") as? String ?? "no msg"
self.timestamp = withDoc.get("timestamp") as? Int ?? 0
}
}
and then here's the the code that reads in all the conversation documents from a Collection, stores each in a ConversationClass object, puts those in an array and returns it through an escaping completion handler
func getConversations(completion: #escaping( [ConversationClass] ) -> Void) {
let conversationCollection = self.db.collection("conversations")
conversationCollection.getDocuments(completion: { snapshot, error in
if let err = error {
print(err.localizedDescription)
return
}
guard let docs = snapshot?.documents else { return }
var convoArray = [ConversationClass]()
for doc in docs {
let convo = ConversationClass(withDoc: doc)
convoArray.append(convo)
}
completion(convoArray)
})
}

How to get an Dictionary from Firebase Firestore in Swift

I wonder how to get an Dictionary from my Firestore. For normal Arrays I've done it like that:
func returnArray(){
let newpath = Firestore.firestore().collection(path)
newpath.addSnapshotListener { (querySnapshot, error) in
guard let documents = querySnapshot?.documents else {
return
}
self.Array = documents.map { (queryDocumentSnapshot) -> String in
let data = queryDocumentSnapshot.data()
let Name = data["myField"] as? String ?? ""
return Name
}
}
}
That works perfectly fine. My question is now how I have to change my code that its getting an Array out of the Firebase. I had an idea, but it doesn't work:
func returnDictionary(){
let newpath = Firestore.firestore().collection(path)
newpath.addSnapshotListener { (querySnapshot, error) in
guard let documents = querySnapshot?.documents else {
return
}
self.Dictionary = documents.map { (queryDocumentSnapshot) -> // I don't know what to fill here
in
let data = queryDocumentSnapshot.data()
let Name = data["AnzeigeName"] as? String ?? ""
let ID = data["selfID"] as? String ?? ""
return ID: Name
}
}
}
What can I try next?
.map is going to give you an array, but you can use Dictionary's init(uniqueKeysWithValues:) to turn this into a Dictionary:
let dictionary : Dictionary<String,String> = .init(uniqueKeysWithValues: documents.compactMap { queryDocumentSnapshot -> (String,String)? in
let data = queryDocumentSnapshot.data()
if let name = data["AnzeigeName"] as? String, let id = data["selfID"] as? String {
return (id, name)
}
return nil
})
It's important to know that uniqueKeysWithValues will crash if the keys are not in fact unique, so you'd want to check for that first (look at #New Dev's comment about init(_:uniquingKeysWith:) to handle this). I'm also using compactMap to get rid of nil values.
(Note: In Swift, generally variable names are lowercased and type names are uppercased. Going against that pattern can make your code challenging to read for others)
Firestore Example
{
name: "Anonymous"
pet {
name: "Max"
age: "3"
}
}
Query
Firebase.firestore().collection(path).getDocuments { querySnapshot, error in
if let error = error {
print(error.localizedDescription)
} else {
for document in querySnapshot!.documents {
let data = document.data()
let name = data["name"]
let pet = data["pet"] as! Dictionary<String, String>
print("Name: \(name)")
print("Pet-name: \(pet["name"]!)")
print("Pet-age: \(pet["age"]!)")
}

Else on If Else statement won't get triggered, can't understand why

I have this block of code:
func fetchFriends() {
if let window = UIApplication.shared.keyWindow {
guard let userId = Auth.auth().currentUser?.uid else { return }
DispatchQueue.main.async {
FirestoreService.shared.fetchFriendList(userId) { (fetchedFriends) in
//// WONT GET HERE ////
if fetchedFriends != nil {
self.fetchedFriends = fetchedFriends! // Can force unwrap here because we already know that fetchedFriends in not nil.
self.friendsTable.reloadData()
}else {
self.fetchedFriends = []
self.friendsTable.reloadData()
}
}
}
}
}
This block of code is using this function:
func fetchFriendList(_ id: String, completion: #escaping([Friend]?)->()) {
var fetchedFriends: [Friend] = []
db.collection(USERS_COLLECTION).document(id).getDocument { (doc, err) in
if err == nil && doc != nil {
guard let results = doc?.data()?[USER_FOLLOWING] as? [String: Any] else { return }
for result in results { // Getting the data in firebase
if let resultValue = result.value as? [String: Any] { // Getting only the value of the MAP data, we do not need the key.
//Getting the fields from the result
guard let id = resultValue[FRIEND_ID] as? String else { return }
guard let profilePic = resultValue[FRIEND_PROFILE_PIC] as? String else { return }
guard let username = resultValue[FRIEND_NAME] as? String else { return }
guard let email = resultValue[FRIEND_MAIL] as? String else { return }
//Creating a new Friend object from the fields
let friend = Friend(id: id, profilePicture: profilePic, username: username, email: email)
fetchedFriends.append(friend)
}
completion(fetchedFriends)
}
}else {
print(err!.localizedDescription)
completion(fetchedFriends)
}
}
}
Whats happening here, is that I'm going into a user's document, getting it's 'Friends' from a Map I have in the document, creating a Friend Array and sending it in the completion to the first function.
In the first function, I'm checking if what I got is nil, if not, I'm assigning it to an array, else, if it is nil, I want the array to be empty.
The purpose here is to show the "Friends" in the tableView if the user has any.
My problem is this situation:
For start, the list of friends is empty, adding a friend and viewing the list, the friend I just added is showing, which is good. the problem is, when I'm removing this friend from the list (and it is deleted in the Database in Firestore), showing the list again does not deletes him from the list and still showing it.
It seems that after removing a friend from the "following" section, and showing the list again, after FirestoreService.shared... it just returns and won't get to the "Won't get here" line.
The FetchFriends() function does gets called everytime I'm opening the FriendsList.
This is a picture of the list I'm referring to, this demouser is removed from the friends list but still showing up.
EDIT: Just noticed that when I have more than one user on the list, it does gets deleted and it works as I want. When I have just one user (or just one left on the list) it won't delete it.
fetchFriendList never calls the callback with a nil value:
var fetchedFriends: [Friend] = []
Therefore your else branch is unnecessary and the completion handler could be #escaping ([Friend]) -> Void without optionals.
By the way, there is also a situation when your method does not call completion at all:
guard let results = doc?.data()?[USER_FOLLOWING] as? [String: Any] else { return }
In general, there are many unsafe places. For example, when err is nil and doc is nil, then your else will crash unwraping err!.
A better alternative:
guard err == nil, let doc = doc else {
print(err?.localizedDescription)
completion([])
return
}
let results = (doc.data()?[USER_FOLLOWING] as? [String: Any]) ?? [:]
let fetchedFriends = results.compactMap { result in
guard
let resultValue = result.value as? [String: Any],
let id = resultValue[FRIEND_ID] as? String,
let profilePic = resultValue[FRIEND_PROFILE_PIC] as? String,
let username = resultValue[FRIEND_NAME] as? String,
let email = resultValue[FRIEND_MAIL] as? String
else { return nil }
return Friend(id: id, profilePicture: profilePic, username: username, email: email)
}
completion(fetchedFriends)

Changing from Firebase to Firestore caused Fatal error: Unexpectedly found nil ... in a query

I am trying to retrieve a user's data from firestore, after migrating my data there from firebase. The code below was working fine with firebase, and retrieved the user's data.
However, after changing the query to firestore query, I got this error.
Print statement here "document.data()" contains the data, But I got this error. I don't know where this error is coming from.
When I compare document.data() with nil, I got "Document data: contains nil".
I don't know how I suppoused to get the data.
here is the code where I get the error,
static func getUser(uid: String, setUserDefaults: #escaping (NormalUser) -> Void){
DataService.ds.REF_USERS_NORMAL.document(uid).getDocument { (document, error) in
if error != nil{
print("\(String(describing: error?.localizedDescription))")
}else{
if document != nil{
let data = document?.data() as! [String: String]
print("Document data: \(String(describing: document?.data() as! [String: String]))")
let user = NormalUser(userData: (data as Dictionary<String, AnyObject>))
setUserDefaults(user)
}else{
print("Document data: contains nil")
}
}
}
}
Here is how I defined the variables,
import Foundation
import Firebase
class NormalUser: User {
private var _email: String?
private var _city: String?
private var _country: String?
private var _name: String?
private var _phone: String?
private var _profileImgUrl: String?
var email: String {
return _email!
}
var city: String {
return _city!
}
var country: String {
return _country!
}
var name: String {
return _name!
}
var phone: String {
return _phone!
}
var profileImgUrl: String {
set{
self.profileImgUrl = _profileImgUrl!
}
get{
if let pI = _profileImgUrl{
return pI
}
return ""
}
}
init(userData: Dictionary<String, AnyObject>) {
super.init(userId: userData["uid"] as! String, user: userData)
if let email = userData["email"] as? String {
self._email = email
}
if let city = userData["city"] as? String {
self._city = city
}
if let country = userData["country"] as? String {
self._country = country
}
if let name = userData["name"] as? String {
self._name = name
}
if let phone = userData["phone"] as? String {
self._phone = phone
}
if let profileImgUrl = userData["imgUrl"] as? String {
self._profileImgUrl = profileImgUrl
}
}
static func createNormalUser(uid: String, userData: Dictionary<String, String>) {
// add user to database
//DataService.ds.REF_USERS_NORMAL.child(uid).setValue(userData)
DataService.ds.REF_USERS_NORMAL.document(uid).setData(userData) { (err) in
if let err = err {
print("Error writing document: \(err)")
} else {
print("Document successfully written!")
}
}
addUserToGroup(uid:uid, group:"normal")
}
static func updateUserProfile(uid: String, userData: Dictionary<String, String>) {
//DataService.ds.REF_USERS_NORMAL.child(uid).updateChildValues(userData)
DataService.ds.REF_USERS_NORMAL.document(uid).updateData(userData)
}
static func getUser(uid: String, setUserDefaults: #escaping (NormalUser) -> Void){
DataService.ds.REF_USERS_NORMAL.document(uid).getDocument { (document, error) in
if error != nil{
print("\(String(describing: error?.localizedDescription))")
}else{
if document != nil{
let data = document?.data() as! [String: String]
print("Document data: \(String(describing: document?.data() as! [String: String]))")
let user = NormalUser(userData: (data as Dictionary<String, AnyObject>))
setUserDefaults(user)
}else{
print("Document data: contains nil")
}
}
}
}
}
I solved the issue!
For any one who may come across this error, the problem was with userId. I was getting the user id from firebase, which, in my case, no longer serves my query, and eventually getting a nil. When I got the ID directly from authentication,
let userID = Auth.auth().currentUser?.uid
It SOLVED the issue!

Updating child in firebase not working as expected

I am trying to save data into firebase, by first generating a child using .childByAutoId() and then update the child with the necessary data. But it doesn't seem to work as expected.
The structure I am trying to achieve is
events
attendees
-L0P1D5arR0OkBf8h
userEmail: "user#user.com"
userName: "User name"
userPhone: "0864567182"
Here's what I have done so far:
guard let fee = events?["eventFee"] else {
return
}
guard let key = events?["eventKey"] else {
return
}
guard let eventTitle = events?["title"] else {
return
}
if fee == "0" {
var values = [String: String]()
self.ref = Database.database().reference()
let attendeekey = ref.child("events").child(key).child("attendees").childByAutoId().key
let userDetails = UserDetails()
for user in userDetails.currentUserDetails {
guard let userEmail = user.email else {
return
}
guard let firstName = user.firstName, let lastName = user.lastName else {
return
}
guard let userPhone = user.phoneNo else {
return
}
let userName = "\(firstName) \(lastName)"
values = ["userEmail": userEmail, "userName": userName, "userPhone": userPhone as! String]
}
ref.updateChildValues(["events/\(key)/attendees/\(attendeekey)": values], withCompletionBlock: {
(err, ref) in
if err != nil {
self.displayAlertMessage(message: err as! String, title: "Oops!")
//print(err ?? "An error occured")
return
}
let message = "You have successfully registered for \(eventTitle)"
self.displayAlertMessage(message: message, title: "Success!")
})
}
Is anything wrong with my approach?