1

I am trying to increment unseenMessage by 1 everywhere everywhere the groupName is the same. The code I currently have returns a null snapshot back.

Database Structure

  • Notifications
    • user1
      • group1
        • groupName: group1
        • unseenMessage: 0
      • group2
        • groupName: group2
        • unseenMessage: 0
    • user2
      • group1
        • groupName: group1
        • unseenMessage: 0

Code:

ref.child("Notifications").queryOrdered(byChild: "groupName").queryEqual(toValue: "group1").observeSingleEvent(of: .value, with: { snapshot in
            print("Snapshot:", snapshot.childrenCount)
            for child in snapshot.children {
                let snap = child as! DataSnapshot
                print("Key:", snap)
                let unreadCountRef = snap.ref.child("unseenMessage")
                print(unreadCountRef)
                unreadCountRef.runTransactionBlock( { (currentData: MutableData) -> TransactionResult in
                    var currentCount = currentData.value as? Int ?? 0
                    currentCount += 1
                    currentData.value = currentCount
                    return TransactionResult.success(withValue: currentData)
                })
            }
        })

Rules:

{
  "rules": {
    ".read": true,
    ".write": true,
    "Notifications": {
      ".indexOn": ["groupName"]
    }
  }
}
John C
  • 517
  • 2
  • 6
  • 16
  • Firebase Realtime Database queries work on a flat list. The database orders and filters each direct child node of the location on a single value that exists at a fixed path of that direct child node. As Jay said, you have two dynamic levels in your JSON, which you can't query. In your current structure you can query the groups of a specific user, but not across all users. See https://stackoverflow.com/questions/27207059/firebase-query-double-nested and https://stackoverflow.com/questions/40656589/firebase-query-if-child-of-child-contains-a-value – Frank van Puffelen Mar 26 '21 at 20:05

1 Answers1

2

Your structure is too deep for that kind of query. Queries generally work one level deep. This structure enabled a query on some_field within each child node:

Notifications
   child_0
     some_field
   child_1
     some_field

Your structure has another layer which prevents that type of query:

Notifications
   user_0
      group_0 //too deep
         some_field
      group_1
         some_field
   user_1
      group_0
         some_field
      group_1
         some_field

One option is to denormalize the data and create a separate node that is queryable because its shallower

With this separate structure, you can query for groupName: group_1 and retrieve and update the unseenMessage in that node

unseen_messages
    node_0 //created with childByAutoId
       parentUser: user_1
       groupName: group_1
       unseenMessage: 0
    node_1
       parentUser: user_1
       groupName: group_1
       unseenMessage: 0

or you can use this structure to build a reference to where the data is stored so you can then update unseenMessages

unseen_messages
    node_0 //created with childByAutoId
       parentUser: user_1
       groupName: group_1
    node_1
       parentUser: user_1
       groupName: group_1

The query would return the parent user and groupName so you could build the path to read, and then update your original structure:

Notifications/parentUser/groupName/unseenMessages

OR (and this is really the answer)

you can totally change the structure - I noted that you're storing the group name as the key as well as within the node itself which is really just duplicate data and not necessary. Storing it as a child node gives a lot more flexibility.

Notifications
   node_0 //created with childByAutoId
      parentUser: user_1
      groupName: group_1
      unseenMessage: 0
   node_1 //created with childByAutoId
      parentUser: user_1
      groupName: group_1
      unseenMessage: 0
   node_2 //created with childByAutoId
      parentUser: user_2
      groupName: group_1
      unseenMessage: 3

Then you could directly query on notifications for group_1

EDIT

I copy and pasted your code and commented out the transaction parts and ran it against the exact structure in the bottom part of this answer and it worked perfectly.

func testQuery() {
    ref.child("Notifications").queryOrdered(byChild: "groupName").queryEqual(toValue: "group1").observeSingleEvent(of: .value, with: { snapshot in
        for child in snapshot.children {
            print(child)
        }
    })
}

and the result

Snap (node_0) {
    groupName = group1;
    parentUser = "uid_0";
    unseenMessage = 0;
}
Snap (node_1) {
    groupName = group1;
    parentUser = "uid_0";
    unseenMessage = 0;
}
Jay
  • 34,438
  • 18
  • 52
  • 81
  • I have tried the database structure above, but my code still produces null – John C Mar 26 '21 at 19:28
  • @JohnC Oops! I left out the important part! lol. Sorry. See my edit and update at the top of the answer – Jay Mar 26 '21 at 19:57
  • Sorry that was a typo on my part as well. The groupName inside my database is supposed to be groupId, but I changed it here to make it easier. The function still does not work. Would it have something to do with the index in my rules? – John C Mar 26 '21 at 20:02
  • @JohnC See the bottom part of my answer. The code works correctly to *read* firebase, which is what the question title was. – Jay Mar 26 '21 at 20:10
  • I found out the issue. I'm not sure why, but labelling it as groupName doesnt work. When I changed it to gName in the database, it works now... – John C Mar 26 '21 at 20:12
  • 1
    @JohnC Glad you found the issue but the field name is not relevant - the field name can be any string; `groupName` is totally valid. I think there was something else involved but I am glad you got it working! – Jay Mar 26 '21 at 20:16