15

sorry for my bad English level, I'm from Argentina.

I have the following messages data structure in Firebase:

"messages"
   "-KezmqXSdKCNFFA432Uc___-KfCEwklG_y3naRDIUiY"
         "messageDate": "20170620"
         "messageTime": "18:44" 
         "message": "Hi"
   "-KezFDSAADFASFFS3221___-KASDF32324SDFASD1FS"
         "messageDate": "20170620"
         "messageTime": "22:23" 
         "message": "How are you?"

Where -KezmqXSdKCNFFA432Uc, -KfCEwklG_y3naRDIUiY, -KezFDSAADFASFFS3221 and -KASDF32324SDFASD1FS are users.

My problem is that I created a childEventListener in "messages" node to receive new users messages but I am receiving all the new messages of all the users (I'm logged in one user per app) because my childListener is in "messages" node.

Is it correct that if I have 1000 users when adding a message, a new message reaches the 1000 users? (Assuming that within the app, you can check to which user that message belongs).

Thanks!

2
  • Do you have chat rooms or there is only a single chat room for those 1000 users? Commented Jun 14, 2017 at 7:38
  • 1
    It's not a Chat Room. It's an app like whatsapp. The problem is the childlistener in "Messages" node. This node has all messages of all users, then if a user sends a message to another user, it reaches all users. Commented Jun 14, 2017 at 11:24

8 Answers 8

59

If you do a structure like similar to this:

-chats
   - chatUID
       - members
           - userUID
       - lastMessageSent:messageUID
       - ... more properties  

-chatMessages
   - chatUID
     - messageUID
         - sentBy: userUID
         - messageDate:""
         - messageTime:""
         - message:""

-userChats
    - userUID
       - chatUID

you can attach a listener to /userChats/userUID, which will display active chats, and a listener to /chatMessages/chatUID, which will get all chat messages for a specific chat conversation.

This way is a lot easier to setup firebase security rules, and users will only receive chat messages which they are apart of.

Sign up to request clarification or add additional context in comments.

4 Comments

I see. Anyway I would need to implement functions and triggers from firebase, right?
@Linxy userA and userB are in a chat room, and both have added a listener to /chatMessages/chatUID. Now if userA pushes a new message in chatUid, both users A&B will receive this new message through their listeners ? Isnt this a waste of Bandwidth for userA, since he himself pushed it. Is it possible to have a Db structure so that userA will only listen to messages from userB ?
How would you count unred messages in this case? This question is old and uses realtime database instead of firestore which I use but I have the same thing pretty much with collections. I also asked question
Also, this will require a listener for each charUid. Maintaining all these listeners could be memory intensive where a user generally has 1k chat rooms.
7

Thanks to @Linxy for a brilliant answer

I have created a firebase database regarding @Linxy answer

enter image description here

Here is the complete JSON export

{
  "Chats" : {
    "-Lsfsd234xda" : {
      "lastMessageSent" : "-LrDEBo1-Message",
      "members" : [ "-LrDEBoLokW-5mhaT3ys", "-LrDEBoLokW-5mhaT3yz" ],
      "more_properties" : "goes here"
    }
  },
  "Users" : {
    "-LrDEBoLokW-5mhaT3ys" : {
      "id" : "-LrDEBoLokW-5mhaT3ys",
      "userDisplayName" : "Qadir Hussain",
      "userEmail" : "[email protected]",
      "userPhotoUrl" : "https://lh3.googleusercontent.com/a-/AAuE7XXXXXXXXX"
    },
    "-LrDEBoLokW-5mhaT3yz" : {
      "id" : "-LrDEBoLokW-5mhaT3yz",
      "userDisplayName" : "Ishaq Bhojani",
      "userEmail" : "[email protected]",
      "userPhotoUrl" : "https://lh3.googleusercontent.com/a-/AAuE7mB3KTbXXXXXXXX"
    }
  },
  "chatMessages" : {
    "-Lsfsd234xda" : {
      "-LrDEBo-MessageUID" : {
        "message" : "Hi there!",
        "messageDate" : "10/10/2019",
        "messageTime" : "10:16pm",
        "sentBy" : "-LrDEBoLokW-5mhaT3ys"
      },
      "-LrDEBo1-MessageUID" : {
        "message" : "Hello",
        "messageDate" : "10/10/2019",
        "messageTime" : "10:17pm",
        "sentBy" : "-LrDEBoLokW-5mhaT3yz"
      }
    }
  },
  "userChats" : {
    "-LrDEBoLokW-5mhaT3ys" : {
      "0" : "-Lsfsd234xda",
      "1" : "-Lsfsd234xda1",
      "chatUID" : "-Lsfsd234xda"
    }
  }
}

2 Comments

Can you please share the code you used to setup this structure?
how do you query for the receiver of the message without knowing the chatUID only the userUID of the sender?
6

I know it's late to answer but for future readers although Linxy's answer is neater, I would like to point out a more efficient one having been tried both structures:

ChatMessages
   smallerUID_biggerUID
      messageUID
         sentBy : userUID
         messageDate : ""
         message : ""
      .
      .
   .
   .
UserChats
   userUID
      pairUID
        lastMessage : ""       
      .
      .
   .
   .

In this way, instead of first finding out the chatId then finding out which user is associated with that chatId, we can directly search which users should appear in our active chat tab and get thouse users' information (username, profilePicture). The reason for that is we can always calculate the chatId if we know the user's id we would like to message with. So for the message tab, we calculate the chatId (smallerUID_biggerUID) in client side and search for the messages in referencing it.

4 Comments

Can you explain your reasoning a little more, I am confused about how you would calculate the uid clientside. Wouldn't you have to go through all of the users and mix and match?
Not really sure if I got the unclear point so let me explain with an example. Say we have an activity displaying active chat users (users the client has at least 1 message with). To display this, we get the data under UserChats/userUID(the client's id). Then when the client clicks one of the users, we need to show the messages between the client and the clicked user. Every messaging pair is uniquely identified as "smallerUID_biggerUID". We already got the uid we would like to see the messages the client has with and we know our own id. So we can calculate this pair's chatId and get their mssgs
The smaller and larger UID makes sense to me, but using this system, wouldn't you still have to query all of the other users UIDs in order to calculate all of the other chat node ids
You can calculate the chatIds of the people we chat with before by attaching a listener to UserChats/userUID. But i think i got what you are asking now. To get all the userUIDs, You can simply get all the userUIDs from Users with any type of query you like and display them. Only when you click one of the users, you will need to calculate the chatid and attach a listener to chatMessages. So what im trying to say is you dont need to get or calculate all the chatIds. The recyclerview elements should contain only the pairInfo (pairUsername, pairUID etc), not the chatId.
1

In order to structure your database, please read this post: Structuring your Firebase Data correctly for a Complex App. You'll find here for sure the answer to your question.

As a conclusion, try to flatten(denormalize) your database as much as possible.

Hope it helps.

Comments

0

this structure doesn't support what you want to do, it better to change it by using something like channels, where a channel contains the messages between two persons, so when any one of them send a message the other one will be notified.

6 Comments

I understand but then I should create channels for each conversation between two users and let these users listen to that channel by a childlistener? In this case, should you create dynamic channels and dynamic childevents?
the channel will be created once one of them send a message to the other and add it to both users channel list, you can use firebase functions to add trigers on the database to send the push notification or add a listener on the channels node and test if the user own that channel, or add event listener on the specified nodes
In that case, a user should be connected to as many childlisteners as they have conversations. This is the right way?
unfortunately yes, that why you need to use firebase functions so you don't need local notification and get push
So I read about firebase functions, I should create a new channel in firebase from user "A" and then create a function to indicate to user "B" that he has received a message and that he must connect a listener to the channel. But that function, should be created from android?
|
0
{
    "users": {
        "userId": {
            "conversations": {
                "conversationId": {
                    "unseenCount": 0
                },
                "conversationId2": {
                    "unseenCount": 3
                }
        }
    },
    "conversations": {
        "conversationId": {
            "displayedMessage": "Message",
            "members": {
                "userId1": true,
                "userId2": true
            },
            "messages": {
                "messageId": {
                    "type": "text",
                    "text": "Hello",
                    "createdAt": "",
                    "senderId": "userId",
                    "status": "sent",
                    "payload": ""
                }
            },
            "lastMessage": "my last message"
        }
}

}

1 Comment

While usable, that's very dumb. If I have 5000 conversations, every time you log in or the user gets updated, you will load all 5000 conversation IDs :)
0

import FirebaseDatabase

class AuthViewModal: ObservableObject { @Published var chatUsers: [UserAFK] = [] // List of chat users

private var databaseRef = Database.database().reference()

func getChatList(currentUserId: String) {
    databaseRef.child("chats").observeSingleEvent(of: .value) { snapshot in
        var chatData: [String: UserAFK] = [:] // Dictionary to store unique users with latest messages

        for case let chatSnapshot as DataSnapshot in snapshot.children {
            if let messages = chatSnapshot.childSnapshot(forPath: "messages").children.allObjects as? [DataSnapshot] {
                for message in messages {
                    if let data = message.value as? [String: Any] {
                        let senderId = data["sender_id"] as? String ?? ""
                        let receiverId = data["receiver_id"] as? String ?? ""
                        let messageText = data["msg"] as? String ?? "No message"
                        let profileImage = data["user_profile"] as? String ?? "defaultImage"

                        // Find the other user in the chat
                        let otherUserId = senderId == currentUserId ? receiverId : senderId

                        // Store only the latest message per user
                        chatData[otherUserId] = UserAFK(
                            id: otherUserId,
                            username: "User \(otherUserId)", // Set username dynamically
                            userMessages: [messageText],
                            image: profileImage
                        )
                    }
                }
            }
        }

        DispatchQueue.main.async {
            self.chatUsers = Array(chatData.values) // Convert dictionary to array
        }
    }
}

}

1 Comment

As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.
-5

I think this will be the best structure for it:

{
  messages: {
    A8Fcn28ak9ask46: {
      chat_id: "combination of sender and receivers number",
      sender_id: "person sending the message", 
      receiver_id: "person send it to",
      text: "message that the user types",
      timestamp: "123981849404"
    },
    ...
  }
 }

then when u get the results, you can filter through the chat_id's in forward and in reverse, which will get the conversation between two people.

Hope it helps.

2 Comments

This is the most inefficient solution. You are making all users to get every data in the system. Your app will never scale.
@Adi agree that it is the most inefficient one, in the meantime can you advise why it can't scale?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.