Search code examples
androidfirebasefirebase-realtime-databasenosqlmany-to-many

How do I load/retrieve a Many to Many relationship in Firebase Realtime Database


In my Android project I need Firebase to load/retrieve my list of 'children' (user with name) when retrieving an object (group) that is in a Many-to-Many relationship, like the structure below:

App {
   "users" : {
        "user123jsbgkjwroi" : {
            "name" : "Mr Guy", 
            "groups" : {
                "group567-alkfhhiuall_dajk" : true,
                ...
            }
        }
    }
    "groups" : {
        "group567-alkfhhiuall_dajk" : {
            "name" : "My Group 1",
            "groupShortCode" : "567",
            "groupPassPhrase" : "ABC",
            "members" : {
                 "user123jsbgkjwroi" : {
                     "A": 2,
                     "B": 1,
                     "C": 33
                 },
                 ...    
            }
        }
    }
} 

I've likely made my life harder than necessary because I'm new to NoSQL, my group object uses the user as a map key, against another object which contains the list (A, B, C). Something like the following:

class Group {
    private String name;
    private Map<User, GroupItems> members;
}

When a new User joins the group, by entering the groupShortCode and groupPassPhrase I need to load the whole Group object, with the other Users (members) of the group.

But I do not want to do this recursively (I would rather fetch as few documents as possible - I should not load the User's other groups). Here's what I've tried:

dbReference.child("groups").orderByChild("groupShortCode").equalTo(...)
           .addListenerForSingleValueEvent(new ValueEventListener() {

    @Override
    public void onDataChange(@NonNull DataSnapshot dataSnapshot) {
        List<User> users = new ArrayList<>();

        //TODO: Here I need load each User, but it means tons of calls   
        Iterable<DataSnapshot> profileKeys = dataSnapshot.child("members").getChildren();
        for (DataSnapshot keySnapshot : profileKeys) {
            dbReference.child("users").child(keySnapshot.getKey())
              .addListenerForSingleValueEvent( ... );
        }         

        //this constructor is because of the Object-to-Object Map so dataSnapshot.getValue() fails
        Group group = new Group(dataSnapshot, users);
        ...
    }

    @Override
    public void onCancelled(@NonNull DatabaseError databaseError) {
        ...
    }

});

How do I do this properly, efficiently!?


Solution

  • It is more common to have four top-level lists for this scenario: groups, users, groupUsers, and userGroups. This allows you to load precisely what you need, at the cost at some extra code. See Many to Many relationship in Firebase.

    If you're worried about problems, it may help to know that all requests go over a single connection, so it's quite unlikely that one succeeds and other fails. Chances of one failing due to connection problems are no bigger with multiple calls than they'd be with a singe call. See Speed up fetching posts for my social network app by using query instead of observing a single event repeatedly