0

Following is the schema of my Firebase Realtime Database:

Chats node

  • chats
    • chatId
      • chatId
      • adminUserId
      • chatSubject
      • chatIconUrl
      • chatCretionDate
      • lastUpdated

User chats node

  • user_chats
    • userId
      • chatId: true

What I want to do:

What I want to do is to present a list of all chats a user is subscribed to in the ChatListFragment of my app and observe it for changes, e.g., new chat added, any chat removed, a change in chatSubject, chatIconUrl, lastUpdated etc.

What I have done:

Since I've already denormalized the data, the first step would be to retrieve a list of chat ids a given user is subscribed to, and this I can very easily achieve as follows:

FirebaseDatabase.getInstance().getReference("user_chats").child(userId).addValueEventListener()

Next task is to get the complete chat data from chats node against each of the chat ids retrieved above. I know that I can't write a query to achieve this because the retrieved chat ids are a random subset out of the hundreds or thousands of total chat ids. So, I loop through the retrieved chat ids and fire a query for each as follows and collect the retrieved Chat (A POJO that maps to the fields in chats node) object one by one in an ArrayList:

FirebaseDatabase.getInstance().getReference("chats").child(chatId).addValueEventListener()

Here, I've to ensure completeness of data by matching the size of ArrayList<Chat> with the size of chat ids retrieved from user_chats node. When both sizes are equal, I supply the ArrayList<Chat> to the ListAdapter.

BUT, in addition to above, I also have to deal with the following:

  • See why onDataChanged() of the listener attached to each chat id has been called: to supply a new chat snapshot or report changes in an already supplied chat snapshot.
  • Remove listener associated with a chat id that the given user is no longer subscribed to.

This becomes even more complicated when I try to do it all in a ViewModel using LiveData.

Here are my questions:

  • Do I really have to go through such a trouble just to compile a list of chats that a user is subscribed to? This is supposed to be the first screen of my app and I can't happen to get past it.

  • Is there a standard / better way to achieve my objective with the same data structure?

  • Do I need to make further changes in my data structure to achieve my objective? What I'm thinking is to expand the user_chats node by adding the same fields that chatsnode has. That seems pretty weird though because, first, if there are lets say, 500 members in a chat, there would be 500 ditto copies of the chat in user_chats node and second, in case of any update in data, I would be updating the chat info in chats node and then all the 500 copies in user_chats node. That would be ridiculous when we think about how frequently the lastUpdated field would be updated.

Any help would be really appreciated!

Shahood ul Hassan
  • 745
  • 2
  • 9
  • 19
  • If you consider at some point in time to try using [Cloud Firestore](https://firebase.google.com/docs/firestore/), here you can find a tutorial on how to create a complete and functional [Firestore Chat App](https://www.youtube.com/playlist?list=PLn2n4GESV0Ak1HiH0tTPTJXsOEy-5v9qb). – Alex Mamo Jul 07 '20 at 06:44
  • @AlexMamo Thanks for taking time. I've already used (and loved) Firestore but Firestore is quite expensive. However, what can be very easily achieved in Firestore is close to impossible in Realtime Database. For example, this one question that I've asked. Anyhow, I would really appreciate if you could point me in the right direction here. This is a very simple requirement, I've used denormalization but still I'm unable to present data even on the first screen of my app. – Shahood ul Hassan Jul 07 '20 at 06:55
  • @AlexMamo I've tried to expand and improve the question. Would appreciate if you could have a second look at it. Thanks! – Shahood ul Hassan Jul 07 '20 at 08:06
  • Have you tried Frank van Puffelen's answer? – Alex Mamo Jul 07 '20 at 08:24

1 Answers1

0

how to know when all the queries get completed.

One common way to do this is to count them. Since you know how many chat IDs are under /user_chats/$uid, you know how many nodes you have to load from under /chats. Each time you get the first result for one of these nodes, you increment a counter. When the number of chats you've loaded is equal to the number of chat IDs for the user, you're done.

Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
  • Is there a way to combine all these queries using Tasks API and still observe the changes in data? Note: I've already gone through the part 4 of Doug Stevenson's https://firebase.googleblog.com/2016/10/become-a-firebase-taskmaster-part-4.html but here we have to use a single `ValueEventListener`, which means that changes in data won't be notified. – Shahood ul Hassan Jul 06 '20 at 15:04
  • The Firebase Realtime Database doesn't implement the Task API, but in the "Tasking Realtime Database" section of that post Doug shows how to wrap the API in a task, which you can then pass to `Tasks.whenAll()`. Also see Doug's answer here: https://stackoverflow.com/questions/38173569/only-load-layout-when-firebase-calls-are-complete – Frank van Puffelen Jul 06 '20 at 15:19
  • Even in this answer that you referred, he used `ref.addListenerForSingleValueEvent(listener);` which means that the changes in data can't be observed. This approach I've just tried and it retrieves data very well but it fails to observe changes in the data. – Shahood ul Hassan Jul 06 '20 at 15:31
  • The suggestion u made in ur answer, I'm already using it....not by incrementing a counter but matching the size of the list of chats and chatIds. But in addition to that, I had to build a mechanism to see if the retrieved chat snapshot was not already there in the `ArrayList`. If yes, I would use set() method to replace the existing object with the new one; otherwise add() to add a new object. That prevents the possibility of adding an object multiple times. But, that doesn't seem like an appropriate way to handle this rather very simple task....or does it? – Shahood ul Hassan Jul 06 '20 at 15:36
  • You can also use `ref.addValueEventListener` in the same way. Your counter would then just check if you've ever received data for that chat. – Frank van Puffelen Jul 06 '20 at 15:45
  • Well, if u have a look at the `onDataChanged()` of `MyValueEventListener` in Doug's answer, he calls `setResult()` on the `TaskCompletionSource`. Once the task represented by that `TaskCompletionSource` is completed, calling `setResult()` will return an exception. Hence, it doesn't seem possible to use `ValueEventListener` in this case. – Shahood ul Hassan Jul 06 '20 at 16:08
  • It depends: a `addValueEventListener` continues to monitor for updates, while `addListenerForSingleValueEvent` only gets data once. So while `addListenerForSingleValueEvent` has a single moment to consider itself "completed", a `addValueEventListener` has no natural single moment for that. But if you for example say "I consider the task completed once the listener has received its first data" then you *can* wrap it in a task and complete on the first call to `onDataChange`. – Frank van Puffelen Jul 06 '20 at 16:13
  • Keep in mind that this all comes from your original question: "how to know when all the queries get completed". This implies that you think a query completes at some point, and I'm trying to help you by showing how to do that. – Frank van Puffelen Jul 06 '20 at 16:14
  • Just to clarify, "how to know when all the queries get completed" is not my original question. My original question is that ends with a question mark, i.e., "So how would I now retrieve the complete chat data from 'chats' node, of all the above retrieved chat ids all at once and collect that in an `ArrayList` which can then be supplied to the Adapter?" – Shahood ul Hassan Jul 06 '20 at 20:21
  • I've tried to expand and improve the question. Would appreciate if you could have a second look at it. Thanks! – Shahood ul Hassan Jul 07 '20 at 08:06
  • It sounds a bit like you're new to NoSQL databases and struggling with the type of trade-offs you have to make there. If that's the case, I recommend reading [NoSQL data modeling](https://highlyscalable.wordpress.com/2012/03/01/nosql-data-modeling-techniques/) and watching [Firebase for SQL developers](https://www.youtube.com/playlist?list=PLl-K7zZEsYLlP-k-RKFa7RyNPa9_wCH2s). – Frank van Puffelen Jul 07 '20 at 14:05