Chat App Lesson 15 Help - Chats not showing

Hi,

Have just completed lesson 15 of the Chat App. thought everything was as good as the lesson had taught me but for the life of me, the dummy chats we built in the database are not showing up in my app!

I have triple checked the database, the database naming, the creation of the dummy chat & cross checked my code with the completed code from the course assets & I can’t find any differences? I have tried debugging but it seems once we get past the no errors and the snapshot isn’t empty check the snapshot can’t seem to unpack any chat even though its not empty but ends up no value?

My brain feels frazzled now, so I am reaching out in hope that I have missed something super simple to make me feel like a wally ha ha

Here is my DatabaseService()

//  DatabaseService.swift
//  ChatApp
//
//  Created by Billie Blanchard on 07/09/2023.
//

import Foundation
import Contacts
import Firebase
import FirebaseStorage

class DatabaseService {
    
    func getPlatformUsers(localContacts: [CNContact], completion: @escaping ([User]) -> Void) {
        
        var platformUsers = [User]()
        
        // Construct an array of string phone numbers to look up
        var lookupPhoneNumbers = localContacts.map { contact in
            // Turn the contact into a phone number string
            return TextHelper.sanitizePhoneNumber(contact.phoneNumbers.first?.value.stringValue ?? "")
        }
        
        // Make sure that there are lookup numbers
        guard lookupPhoneNumbers.count > 0 else {
            // Callback
            completion(platformUsers)
            return
        }
        // Set database
        let db = Firestore.firestore()
        
        // Query database until no more phone numbers to query
        while !lookupPhoneNumbers.isEmpty {
            
            // Get the first 10 or < phone numbers to look up
            let firstTenPhoneNumbers = Array(lookupPhoneNumbers.prefix(10))
            
            // Remove the < 10 that we're looking up
            lookupPhoneNumbers = Array(lookupPhoneNumbers.dropFirst(10))
            
            let query = db.collection("users").whereField("phone", in: firstTenPhoneNumbers)
            
            // Retrieve the users that already hold an account
            query.getDocuments { snapshot, error in
                // Check for errors
                if error == nil && snapshot != nil {
                    // For each doc/user that was fetched, create a new user
                    for doc in snapshot!.documents {
                        if let user = try? doc.data(as: User.self) {
                            //Append to the platform users array
                            platformUsers.append(user)
                        }
                    }
                    // Check if we have more phone numbers to look up
                    // If not, we can call completion block
                    if lookupPhoneNumbers.isEmpty {
                        // Return the users with an account
                        completion(platformUsers)
                    }
                }
            }
        }
        
        
        
        
    }
    
    func setUserProfile(firstname: String, lastName: String, image: UIImage?, completion: @escaping (Bool) -> Void) {
        // Ensure user is logged in
        guard AuthViewModel.isUserLoggedIn() != false else {
            // User not logged in
            return
        }
        
        // Get user's phone number
        let userPhone = TextHelper.sanitizePhoneNumber(AuthViewModel.getLoggedInUserPhone())
        
        // Get a reference to Firestore
        let db = Firestore.firestore()
        
        // Set the profile data
        
        let doc = db.collection("users").document(AuthViewModel.getLoggedInUserId())
        doc.setData(["firstname": firstname,
                     "lastname": lastName,
                     "phone": userPhone
                    ])
        
        // Check  if image is passed through
        if let image = image {
            // Create storage reference
            let storageRef = Storage.storage().reference()
            
            // Turn our image into data
            let imageData = image.jpegData(compressionQuality: 0.8)
            
            // Check that we were able to convert it to data
            guard imageData != nil else {
                return
            }
            
            // Specify the file path and name
            let path = "images/\(UUID().uuidString).jpg"
            let fileRef = storageRef.child(path)
            
            _ = fileRef.putData(imageData!, metadata: nil) { meta, error in
                
                if error == nil && meta != nil {
                    
                    // Get full URL to image
                    fileRef.downloadURL { url, error in
                        // Check for errors
                        if url != nil && error == nil {
                            // Set that image path to the profile
                            doc.setData(["photo": url!.absoluteString], merge: true) { error in
                                if error == nil {
                                    // Success, notify user
                                    completion(true)
                                }
                            }
                        } else {
                            // Unsuccesful in getting download of url for photo
                            completion(false)
                        }
                    }
                } else {
                    // Upload was not successful, notify user
                    completion(false)
                }
            }
        } else {
            // No image was set
            completion(true)
        }
        
        
        
        
    }
    
    func checkUserProfile(completion: @escaping (Bool) -> Void) {
        
        // Check that the user is logged in
        guard AuthViewModel.isUserLoggedIn() != false else {
            return
        }
        
        // Create firebase ref
        let db = Firestore.firestore()
        
        // Look for user
        db.collection("users").document(AuthViewModel.getLoggedInUserId()).getDocument { snapshot, error in
            // TODO: Keep the users profile data
            
            if snapshot != nil && error == nil {
                // Notify that profile exists
                completion(snapshot!.exists)
            } else {
                // TODO: Look into using result type to indicate faliure vs profile exists
                completion(false)
            }
        }
    }
    
    // MARK: - Chat Methods
    
    /// This method returns all chat documents where the logged in user is a participant
    /// If no chats then an empty chat array is returned
    func getAllChats(completion: @escaping ([Chat]) -> Void) {
        
        // Get a reference to the database
        let db = Firestore.firestore()
        
        // Perform a query against the chat collection for any chats where the user is a participant
        let chatsQuery = db.collection("chats")
            .whereField("participantids",
                        arrayContains: AuthViewModel.getLoggedInUserId())
        
        
        chatsQuery.getDocuments{ snapshot, error in
                
            // Check no errors have happened
            if snapshot != nil && error == nil {
                
                // Store the chats
                var chats = [Chat]()
                
                // Loop through all the returned chat documents
                for doc in snapshot!.documents {
                    
                    // Parse the data into Chat structures
                    let chat = try? doc.data(as: Chat.self)
                    
                    
                    // Add the chat into the chat array if not nil
                    if let chat = chat {
                        chats.append(chat)
                        
                    }
                }
                // Return the data
                completion(chats)
                
            } 
            else {
                // Errors exist
                print("Error in database retrieval of chats")
            }
        }
    }
    
    /// This method returns all messages for a given chat
    func getAllMessages(chat: Chat, completion: @escaping ([ChatMessage]) -> Void) {
        
        // Check chat has a chat id & is not nil
        guard chat.id != nil else {
            // Can't fetch data - return empty chatMessage
            completion([ChatMessage]())
            return
        }
        
        // Get a reference to the database
        let db = Firestore.firestore()
        
        // Create the query
        let msgsQuery = db.collection("chats")
            .document(chat.id!)
            .collection("msgs")
            .order(by: "timestamp")
        
        // Perform the query
        msgsQuery.getDocuments { snapshot, error in
            
            
            // Parse the data
            
            // Check no errors exist
            if snapshot != nil && error == nil {
                
                // Loop through the msg documents & create ChatMessage instances
                
                // Store the messages
                var messages = [ChatMessage]()
                
                for doc in snapshot!.documents {
                    
                    let msg = try? doc.data(as: ChatMessage.self)
                    
                    if let msg = msg {
                        // Add messages to message store
                        messages.append(msg)
                    }
                }
                // Return the results
                return completion(messages)
            } 
            else {
                // Errors exist
                print("Error in database retrieval of messages")
            }
        }
    }
    
}

Thanks in advance!

If I understand correctly, your problem is that the getAllChats method is not returning any chats, yet it’s not printing any errors.

Specifically, I understand you’re referring to this snippet of your code:

It would seem that chats are not getting parsed correctly. Can you please try this:

// Parse the data into Chat structures
if let chat = try? doc.data(as: Chat.self) {
    chats.append(chat)
} else {
    print("Error parsing chat \(chat.id!)") /// I assume chat.id is a @DocumentID
}                 

If the error gets printed, it means indeed the parsing is failing, and there must be a discrepancy between the Chat model and your Firebase data. If that is the case, review it carefully again!
If it’s printing but you still can’t find the mistake, post a screenshot of your Firestore data here and share the exact code for the Chat model :slight_smile:

If it’s not printing… I’d say probably problem lies elsewhere

Thanks for this, added it in replace of the original parse and append and you are right, this is at least where the problem is.

I couldn’t get use of chat.id in the print statement though as it say’s it doesn’t exist but yes I set it up like so as a DocumentId @DocumentID var id: String?

Sadly out of time to keep debugging it today, but I shall have another look in the morning. At least I now have the start point of a bug to work on. lol

Putting this here, just in case…can’t see me fixing it tonight, maybe tomorrow but all ideas welcome :slight_smile: Thank you :pray:

Chat struct:

//
//  Chat.swift
//  ChatApp
//
//  Created by Billie Blanchard on 27/09/2023.
//

import Foundation
import FirebaseFirestore
import FirebaseFirestoreSwift


// Chat document
struct Chat: Codable, Identifiable {
    
    @DocumentID var id: String?
    
    var numparticipants: Int
    
    var participantids: [String]
    
    var lastmsg: String?
    
    @ServerTimestamp var updatded: Date?
    
    var msgs: [ChatMessage]?
    
}

// Message document
struct ChatMessage: Codable, Identifiable {
    
    @DocumentID var id: String?
    
    var imageurl: String?
    
    var msg: String
    
    @ServerTimestamp var timestamp: Date?
    
    var senderid: String
}

Chats database:

Message database:

Still can’t seem to figure out how to fix the issue. I can’t get access to print the chat.id. I either get the error that it is not in the scope or "Generic parameter ID could not be inferred. All ideas welcome. :slight_smile:

FIXED IT

My bad… I had added an extra d to the word updated when setting up the Chat Structure doh :woman_facepalming:

Thanks for the help :slight_smile:

1 Like

Oh yeah my bad obviously you can’t access chat.id :sweat_smile: it should probably have been:

if let chat = try? doc.data(as: Chat.self) {
    chats.append(chat)
} else {
    print("Error parsing chat \(doc.documentID)")
}  

Good job fixing it :slight_smile:

1 Like

ha ha I thought it was weird I couldn’t access it, now I see the correction I think why did my dumb brain not figure that one out lol :rofl:

Thank you :+1: