0

In real time you can't do a single query across multiple rootnodes, so I was wondering if doing multiple queries (nested one after the other) is a bad practice? I understand this can be done in one query in firestore, but I am specifically using realtime for this portion of my app due to high number of user read/writes.

reference.addListenerForSingleValueEvent(new ValueEventListener() {
    @Override
    public void onDataChange(@NonNull DataSnapshot dataSnapshot) {

          //get Data from query one here  

         reference2.addListenerForSingleValueEvent(){
            //get Data from query two here

                    reference3.addListenerForSingleValueEvent(){
                       //get Data from query three here
                   }

          }


    }
Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
DIRTY DAVE
  • 2,523
  • 2
  • 20
  • 83

2 Answers2

3

In a very general sense, nested callbacks is considered poor programming style. Casually speaking, it's called callback hell. As you get deeper into these callbacks, the code gets increasingly difficult to read and manage.

That said, if it works for you, go for it. You're the boss of your code. If it doesn't work for you, you can do searches to find strategies for avoiding this situation.

Doug Stevenson
  • 297,357
  • 32
  • 422
  • 441
  • `callbackhell.com` provides `JavaScript` examples... while an `interface` method, which takes a `DataSnapshot` as argument, might rather be the way to do that in `Java`. I personally find one level of nesting still acceptable/readable, but at a certain point, unwrapping it makes sense. – Martin Zeitler Mar 10 '19 at 20:29
  • Gotcha, Thank you – DIRTY DAVE Mar 10 '19 at 20:30
  • @MartinZeitler The term can apply to any language that allows inline callbacks to asynchronous methods. The same patterns appear when the language allows it. JS is often used as a reference language, since that's where you see it most often (at the outset, JS didn't have proper classes, made it super easy to inline functions, and lots of web programming noobs popularized the approach). Java just makes it harder with its verbosity, though lambda ease the nesting a bit. But it's all the same pattern to me. – Doug Stevenson Mar 10 '19 at 20:32
  • @DougStevenson of course it is exactly the same anti-pattern... just wanted to explain the difference in how to unwrap the nesting in the given environment; which is a declared function name vs. an interface method (besides other ways how it can be done). – Martin Zeitler Mar 10 '19 at 20:38
3

The alternative to doing multiple calls to read the corresponding entries from each reference, is to duplicate the data of the other entries under each reference.

It's a bit hard to reason in your abstract example, so let's pick a more concrete, simple use-case: a chat app. Say you have two-level entities: users, and chat messages. Each chat message is written by a user, and a user has a display name. In the most normalized data model this could be:

users: {
  user1: {
    name: "david s."
  },
  user2: {
    name: "Doug Stevenson"
  },
  usere: {
    name: "Frank van Puffelen"
  }

},
messages: {
  message1: {
    message: "In real time you can't do a single query across multiple...",
    uid: "user1"
  },
  message2: {
    message: "In a very general sense, nested callbacks...",
    uid: "user2"
  }
  message1: {
    message: "The alternative to doing multiple calls...",
    uid: "user3"
  }
}

Now let's say that we have a use-case where we want to display the latest 10 chat messages, each with the name of the user who posted that message.

The above data works fine, but you will need to read the user names from the /users node, pretty much how you're doing now. This is known as a client-side join, and is quite efficient since Firebase pipelines the requests over a single connection.

You'd could deduplicate the lookup of the names using a cache, since the user names typically change much less frequently than the chat messages. This reduced the overhead since you're only loading each user's data once, but the code can get a bit verbose.

An alternative is to duplicate the minimal data that you need for your use-case. This means your data model would look like this:

users: {
  user1: {
    name: "david s."
  },
  user2: {
    name: "Doug Stevenson"
  },
  usere: {
    name: "Frank van Puffelen"
  }

},
messages: {
  message1: {
    message: "In real time you can't do a single query across multiple...",
    name: "david s.",
    uid: "user1"
  },
  message2: {
    message: "In a very general sense, nested callbacks...",
    name: "Doug Stevenson",
    uid: "user2"
  }
  message1: {
    message: "The alternative to doing multiple calls...",
    name: "Frank van Puffelen",
    uid: "user3"
  }
}

Now you can display the list of the latest 10 messages with a single read. The cost is that you use more storage, but typically storage should be considered cheap. The disadvantage in code is that you now need to write the duplicate data, which is more complex. And of course the duplicated data could get out of sync.

All above approaches are valid. Which one you pick depends on the use-cases of your app, your comfort level in duplicating data, and how much you personally value things like the amount of code, bandwidth consumption, etc. There is no one-size-fits-all answer, so you will have to make your own call.

For some great reading/viewing on the topic, see:

Alex Mamo
  • 130,605
  • 17
  • 163
  • 193
Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807