0

I am building a one way messaging app using Firebase Firestore, messaging and cloud functions. Messages are sent from a React based web app to mobile devices ('client only'), essentially a cloud pager for a workplace to bulk message staff from multiple sites.

Each message that is sent is saved as a new document in the Firestore database. One of the document fields is { targetSite: 'Site Name Here' }. A cloud function then sends push notifications to a Cloud Messaging topic with the same name as targetSite. This essentially sums up the web back end.

The client apps can select from a settings menu what sites they want to subscribe to, then they will receive any notifications targeted at those sites. Due to the limitation of firebase messaging not being able to return a list of devices subscribed to a topic I handle the topic subscriptions by

  1. Getting a list of sites from the Firestore database and putting them in a topics array
  2. Unsubscribing the device from all topics in the topics array using .forEach
  3. Getting an array that I store locally in UserDefaults with the topics the user has chosen to subscribe to.
  4. Subscribe to all the topics in the UserDefaults array

This works fine for keeping the devices subscribed to the topics that they need for push notifications.

Where I'm stuck is returning the list of messages from Firestore database to display in the UI. I am aware that Firestore does not currently have an OR query. My array of subscribed topics could be anywhere from 1 to 30 sites.

If the syntax was valid my query would look something like this:

this.messageListener = db.collection('messages')
            .where('targetSite', '==', 'SiteA')
            .or('targetSite', '==', 'SiteB')
            .or('targetSite', '==', 'SiteC')
            .or('targetSite', '==', 'SiteD')
            .and('expired', '==', 'false')
            .onSnapshot( (snap) => {

Obviously the above syntax isn't valid but it should give some idea of what I'm trying to achieve. What's the best way to dynamically increase or decrease the size of the query and what's the best alternative to not having an OR statement. Surely there is a better way than simply querying the database 30 times?

Solution

A solution now exists thanks to @Philip for pointing it out. Firebase now supports this functionality albeit with some limitations. See the Firebase Blog

Unfortunately this cannot be marked as an answer due to the thread being flagged as a duplicate and linked to an answer that is now incorrect.

iShaymus
  • 512
  • 7
  • 26
  • There's currently no better way than performing a query for each different logical OR condition. – Doug Stevenson Oct 10 '19 at 14:08
  • I'm aware the logical `OR` doesn't exist. What I'm asking is what alternative approaches have people used to get around this when the amount of `OR` queries is dynamic. Considering I want to use an `onSnapshot()` listener instead of a `.get()` I could potentially have 30+ `onSnapshot()` listeners. You can mark as duplicate all you like. I spent an hour looking and didn't find anything that answers my question. – iShaymus Oct 10 '19 at 18:46
  • That's because this is pretty much your only option unless you want to restructure your data so that you can get everything you need with fewer queries. If you want to try the latter, I suggest giving it a shot, then posting another question with what you've tried to do with your data, and where you got stuck. – Doug Stevenson Oct 10 '19 at 19:48
  • There is a way you can do this, but it is painful. You add data to each doc that allows you to do AND instead of OR. Add a map to each doc map sites { siteA: true, siteB: false, siteC: false, } 2nd doc map sites { siteA: false, siteB: true, siteC: false, } If you want siteA == true & siteC == true do .where("siteB", "==", false) i.e. the inverse of the OR query you would do but matching false. You will need to generate all combinations of the possible complex indexes that are required for every query. – Philip Oct 10 '19 at 20:33
  • Thanks @Philip that does sound painful. I might have to add sites in future so I'm not sure its worth it as the adjustment I would have to do to add a new site would be painful. – iShaymus Oct 10 '19 at 21:14
  • I think I might be best to just get all docs from the DB and then apply them to the UI using `.forEach` and then using an `if` or `switch` statement.. – iShaymus Oct 10 '19 at 21:15
  • There is one benefit to the method I mention and that is that firestore will do the orderBy you need if you add additional fields to the query which means there is no sorting needed on the client but this does increase the number of complex indexes needed quite quickly. I have 35 fields and this uses almost all the 200 available indexes. I also use a cloud function to return the fields that are needed for any given query to give me flexibility in the future. Hopefully, firestore will change and make all of this redundant. – Philip Oct 16 '19 at 12:21
  • 1
    Just in case anyone finds this, from mid November, 2019, Firestore now allows the IN operation so you can do a check against multiple values in a field. You can also do the same for arrays. There are limits on this. See this blog for more information. https://firebase.googleblog.com/2019/11/cloud-firestore-now-supports-in-queries.html – Philip Nov 20 '19 at 08:51
  • Thanks @Philip that's great news. I would love to mark it as an answer but since this and other duplicates have been locked I cannot mark it for future people looking for this solution. – iShaymus Nov 21 '19 at 05:32

0 Answers0