pellyadolfo pellyadolfo - 13 days ago 6
Android Question

Missing Firebase node metadata (one2one chat app)

I am trying to create a "one to one Firebase Database chat for Android". So far is working by using the structure below. The large string represents user_TO_user and the KW records are chat messages.

chat
D_57f337e7e55df9ca167d0421_TO_O_57f337e7e55df9ca167d0421
-KWd06dvkmcoCJd6fNWs
-KWd7yBoUy-RkEJAlVOB
-KWd8Au5bPAwMsx3gYhp
....
O_57f337e7e55df9ca167d0421_TO_D_57f337e7e55df9ca167d0421
-KWd06da-Cx0qjnPUE3l
-KWd7yBvyLoLd2fiBcia
-KWd8Au3A_5FJZS8S0qz
....


From the Android activity showing the list of conversations for a specific user, I am creating a ListView and reading the list from Firebase with:

String calculated = "O_57f337e7e55df9ca167d0421"; // calculated userId
firebase_chat.orderByKey().startAt(calculated).endAt(calculated + "\uf8ff").addChildEventListener(new ChildEventListener() {
@Override
public void onChildAdded(DataSnapshot dataSnapshot, String s)
{
// add item to list adapter

}


}

and this works fine.

But I have 2 issues here.

First. I needed to make a copy of the node with reversed name (shown above i.e. A_TO_B and B_TO_A) to allow both users involved in the chat using Firebase Database querying capabilities (since substring() or contains() are not available filters in Firebase). Any better querying approach which prevents me to duplicate all the messages? Any node alias perhaps?

Second. The node name itself is helpful for the code to recognise the chatter names.... but it does not help at all for user presentation. I would need to associate somewhere that user O_57f337e7e55df9ca167d0421 is "John Smith" to show John Smith in the screen. And this mechanism should be applied for every single conversation node.

I was thinking in using the first record within every node to store node metadata so, after fetching the node name, I could query the DataSnapshot from the Android device to find out info about this node. However, I am afraid this will trigger a new backend (Firebase DB) request for every conversation node to get its metadata and this could not be efficient and cause too many server calls from every device. The right solution would be bringing metadata within the name of every node... so I could save this second call.... but this does not seems feasible in Firebase.

Do you figure out any more efficient approach?

Thanks




update: so, according to vzsg's answer, a better structure could be:


  • msgs


    • A_TO_B


      • msg1

      • msg2

      • msg3


    • A_TO_C


      • msg1

      • msg2

      • msg3


    • A_TO_J


      • msg1

      • msg2

      • msg3



  • chats


    • A


      • A_TO_B


        • B


      • A_TO_C


        • C


      • A_TO_J


        • J



    • B


      • A_TO_B


        • A



    • C


      • A_TO_C


        • A



    • J


      • A_TO_J


        • A






This would allow:


  • creating a chats entry when opening the conversation screen

  • creating a msgs entry on first message

  • querying chats/A node to find out conversations for A user, including the data of the other user

  • querying msgs/A_TO_B node to fetch the actual msgs of a conversation



(I will wait a bit to accept your answer but I like the proposal)




update2: or simplifying chats node:


  • chats


    • A


      • B

      • C

      • J


    • B


      • A


    • C


      • A


    • J


      • A





and compose the conversation name dinamically to fetch messages




My final solution described here: https://github.com/cvivek07/Firebase_Chat/issues/1#issuecomment-260925121

Answer

First

There is a very simple way to spare the duplication: sort the two user IDs lexicographically when calculating the combined ID. This way, no matter which user is looking for the other, they will find the one single conversation and you don't have to jump through hoops.

E.g.:

  • A to B becomes A_to_B
  • B to A becomes A_to_B

That said, there are no querying options that would help here, and there is no support for aliasing nodes at all.


Second

You have two options with different pros and cons:

  1. Copy the participant user names into the chat nodes, what you coined as the "right solution". It's not necessarily true, but it's feasible: storage is cheap, and eliminating the extra queries makes the client code simpler. But having copies causes problems when a user changes his name – all relevant chat nodes should be updated too.

  2. Keep user nodes somewhere else, and fetch them lazily to load the user names (and possibly other profile data, like picture URL). Displaying the list becomes harder to implement, but updating only needs to be done in one place.
    You don't need to worry about "too many server requests" however, this is something the SDK can easily handle. You can also cache known users in memory.

Both solutions work in practice and you have to decide which strategy works better in your scenario.


Extra

There is a serious flaw in the presented schema: as you are using the chat node for both listing conversations and then listing messages under it, the client has to download everything, including every message without bounds, a lot of CPU time and bandwidth is wasted.

You should consider storing the conversation list and the messages separately; and in general, aiming for a flat database structure.

Comments