Chat messages are being fetched every time I open the viewcontroller

Viewed 380

I'm currently building a chat app and when I click on a user, it takes me to the chat log controller. Here I call a function fetchChatMessages() in viewdidload() that essentially fetches the conversation from firestore. Problem is, whenever I go to the previous controller and open the chat again, it again fetches the messages.

Not sure if it's fetching from cache or from the server itself. But I did write a print statement under the firestore fetch code that prints every time.

Now I'm new to swift so my question is, in other chat apps, you can see that messages seemed to be fetched just once from the server and after that you add a listener and update the collection view to display the new messages. In my case, it seems like everything is fetched over and over again. Even though I have added a listener and followed a highly acclaimed tutorial.

Also, I added a scroll to bottom code whenever messages are fetched, so every time a new message is fetched, the controller automatically scrolls to the bottom. But this happens every time I open the chat. I was trying to fix this bug where the controller keeps scrolling every time the view appears which made me wonder, am I contacting firebase again and again when the controller is opened?

override func viewDidLoad() {
    super.viewDidLoad()

    fetchCurrentUser()
    fetchMessages()
    setupLoadView()
}

//MARK: Fetch Messages

var listener: ListenerRegistration?

fileprivate func fetchMessages(){
    print("Fetching Messages")
    guard let cUid = Auth.auth().currentUser?.uid else {return}

    let query = Firestore.firestore().collection("matches").document(cUid).collection(connect.uid).order(by: "Timestamp")

    listener = query.addSnapshotListener { (querySnapshot, error) in
        if let error = error{
            print("There was an error fetching messages", error.localizedDescription)
            return
        }

        querySnapshot?.documentChanges.forEach({ (change) in
            if change.type == .added{
                let dictionary = change.document.data()
                self.items.append(.init(dictionary: dictionary))
                print("FIRESTORE HAS BEEN CONTACTED FETCHING MESSAGES")
            }
        })

        self.collectionView.reloadData()
        self.collectionView.scrollToItem(at: [0, self.items.count - 1], at: .bottom, animated: true)
        print("Fetched messages")
    }
}

//MARK: View Disappears

override func viewWillDisappear(_ animated: Bool) {
    super.viewWillDisappear(animated)

    if isMovingFromParent{
        listener?.remove()
    }
}

I want the controller to remember its state. Like if I scroll the messages, exit the controller and enter it again, it stays at the scrolled position like in whatsapp.

2 Answers

The problem here is that your controller gets deallocated every time you leave the screen, because you probably push the controller on to the stack and pop it afterwards, this will erase all of the internal state of the controller. This behavior is indeed intended (viewDidLoad is called once the screen is loaded). You can solve this problem in several ways. An easy one would be to introduce a singleton service (a service which is shared across the app) which holds the state of the controller, so every time the controller is created it will ask the service for its state. Keep in mind this is not a very good solution, but it should be sufficient as starting point. If you need an example I will edit my answer accordingly later on.

I cannot give you a definite answer on this, because it really depends on what features the app and the server support, in the end I would have some kind of database service and chat services (these should not be accessed over the singleton pattern, but rather via dependency injection). The chat service would define some kind of policy when the data should be fetched, which means the controller should not be aware of this. The chat service would then store the messages via the database layer in some persistent store like user defaults, realm or core data for each chat. Every time the user enters an already fetched chat the chat service will check if persisted data is available if not it will fetch it from the server.

Related