Search code examples
javascriptnode.jsfirebase-realtime-databasesubscription

Firebase Realtime database - only subscribe to single adds, updates, deletes


I have a collection of items in a Firebase Realtime database. Clients subscribe to modifications in the /items path of the database. But this has the effect of sending all items to the client each time a single item is added, updated or deleted. This could be up to 1000 items being sent to the client just because an item text has been updated with as little as one character.

This code works, but does not behave the way I want:

export const startSubscribeItems = () => {
  return (dispatch, getState) => {
    return new Promise(resolve => {
      database.ref('items')
      .orderByChild(`members/${uid}`)
      .equalTo(true)
      .on('value', (snapshot) => {
        let items = []
        snapshot.forEach( (childSnap) => {
          const id = childSnap.key
          const item = {id, ...childSnap.val()}
          items.push(item)
        })
        dispatch(setItems(items))
        resolve()
      })
    })
  }
}

I wish to make this more network cost effective by only sending the item that has been updated - while keeping client subscriptions.

My initial thought was to implement a subscription for each item:

export const startSubscribeSingleItems = () => {
  return (dispatch, getState) => {
    return new Promise(resolve => {
      database.ref('items')
      .orderByChild(`access/members/${uid}`)
      .equalTo(true)
      .once('value', (snapshot) => {
        let items = []
        snapshot.forEach( (childSnap) => {
          const id = childSnap.key
          const item = {id, ...childSnap.val()}
          items.push(item)
          // .:: Subscribe to single item node ::.
          database.ref(`items/${id}`).on('value', (snap)=>{
             // Some logic here to handle updates and deletes (remove subscription)
          })
        })
        dispatch(setItems(items))
        resolve()
      })
    })
  }
}

This seems a bit cumberstone, and only handles updates and deletes. It does not handle the case of additions made by another client. Additions would have to happen via a separate database node (eg. 'subscriptionAdditions//')? Also - initial load would have to clear all items in "subscriptionAdditions//" since first load reads all items.

Again, cumberstone. :/

In conclusion; Is there a simple and/or recommended way to achieve subscribing to single items while taking several clients into account?

Kind regards /K


Solution

  • Firebase Realtime Database synchronizes state between the JSON structure on the server, and the clients that are observing that state.

    You seem to want to synchronize only a subset of that state, as far as I can see mostly about recent changes to the state. In that case, consider modeling the state changes themselves in your database.

    As you work with NoSQL databases more, you'll see that is quite common to modify your data model to allow each use-case.


    For example, if you only need the current state of nodes that have changed, you can add a lastUpdated timestamp property to each node. Then you can query for only the updates nodes with:

    database.ref('items')
      .orderByChild('lastUpdated')
      .startAt(Date.now())
    

    If you want to listen for changes since the client was last online, you'll want to store the timestamp that they were last online somewhere, and use that instead of Date.now().


    If you want to synchronize all state changes, even if the same node was changed multiple times, you'll need to store each state change in the database. By keeping those with chronological keys (such as those generated by push()) or storing a timestamp for each, you can then use the same logic as before to only read state change that your client hasn't processed yet.


    Also see: