How to properly organize Firebase Database structure?

Structuring your Firebase Database is one of the first things that you will need to do before implementation of your app. You can do it in two ways: so bad or so good ;)

I mean JSON tree with many levels or flat data structure as much as possible. It takes a moment to ponder, but it’s not that difficult to organize your data wisely.

As you know Firebase Database allows nesting data up to 32 levels deep, but this is not recommended, because

(…) when you fetch data at a location in your database, you also retrieve all of its child nodes. In addition, when you grant someone read or write access at a node in your database, you also grant them access to all data under that node.

Source: Firebase Structure Data

The best solution is to split data into smaller pieces e.g. each of the classes in a separate path. With this data organization all reads, writes and moves are very fast. You only have access to the data that you need.

Below you can see correctly organized data in my Cataloging App (Android). There are three different classes: Spot, Category and Thing. Let me explain how they depend from each other.

Spot – base class containing only information about place.
Category – Spot can contain many Categories.
Thing – Category can contain many Things.

So we have three different paths and none of them depend on each other:
/users/$uid/spots/$spot_id
/users/$uid/categories/$spot_id/$category_id
/users/$uid/things/$category_id/$thing_id

It’s now possible to iterate through each list by downloading only a few bytes. Everything can be fetched separately and displayed in concrete Activities.

Okay, now you know how it looks in Firebase Console. Let’s have a look at the code for Android platform (Java).

//Our connection with database
DatabaseReference mDatabaseReference
                    = FirebaseDatabase.getInstance().getReference();
//Our userID
String mUserId = mFirebaseUser.getUid();
 
//----Spot----
//Reference to Spots path: users -> user_id -> spots
DatabaseReference placeRef
                    = mDatabaseReference.child("users").child(mUserId).child("spots");
//Generating our spotID + timestamp
String id = placeRef.push().getKey();
 
//Creating Spot instance - spotID, spotName
Spot p1 = new Spot(id, "Miejsce 1");
 
//Creating map of objects to upload: key -> generated spotID, value -> object
Map<String, Object> childUpdates = new HashMap<>();
childUpdates.put(id, p1.toFirebaseObject());
 
//This line writes our object to Firebase Database
placeRef.updateChildren(childUpdates);
 
//----Category----
//Reference to Categories path: users ->user_id -> categories
DatabaseReference categoryRef
                    = mDatabaseReference.child("users").child(mUserId).child("categories");
String catId = categoryRef.push().getKey();
 
//Creating Category instance - categoryID, spotID, categoryName
Category c1 = new Category(catId, id,"Kategoria 1");
 
Map<String, Object> childUpdates2 = new HashMap<>;();
//And here is some magic. We need to connect programmatically our Spot with Category.
//So we need to add our Category to path with concrete spotID !!!under categories path!!!.
//id - this is our spotID
//catID - this is our categoryID
//id + '/' + catId - with this construction we create the path to place the object
childUpdates2.put(id + '/' + catId, c1.toFirebaseObject());
categoryRef.updateChildren(childUpdates2);
 
//----Thing----
//Reference to Things path: users-> user_id -> things
DatabaseReference thingRef
                    = mDatabaseReference.child("users").child(mUserId).child("things");
String thingId = thingRef.push().getKey();
 
//Creating Thing instance - thingID, categoryID, thingName
Thing t1 = new Thing(thingId, catId,"Rzecz 1");
 
Map<String, Object> childUpdates3 = new HashMap<>();
//This is similar situation as above. 
//Now we need to place Thing under concrete categoryID !!!under things path!!!.
//catId - this is our categoryID
//thingId - this is our thingID
//catId + '/' + thingId - with this construction we create the path to place the object
childUpdates3.put(catId + '/' + thingId, t1.toFirebaseObject());
thingRef.updateChildren(childUpdates3);

As you can see it’s quite easy to insert object under concrete parent id. But you probably don’t know what toFirebaseObject() method does. Let’s look at this.

//I create HashMap of my object. All attributes are converted into key -> value pairs.
public HashMap<String,String> toFirebaseObject() {
    HashMap<String,String> spot =  new HashMap<String,String>();
 
    //Key: id, Value: our spotID
    spot.put("id", id);
 
    //Key: spotName, Value: our spotName
    spot.put("spotName", spotName);
 
    return spot;
}

I hope that I have explained the issue in a meaningful way. After copying the above code and writing few classes (in this case Spot, Category, Thing) by yourself you will get database structure as at the beginning of the entry.

Of course during implementation you have to split this code into several Activities and pass concrete id’s to other Activities using Intents.

I keep my fingers crossed for you ;)

PS.
Of course you can include parent object id in your ref. After that you only put concrete object id in Map.

//----Thing----
//Reference to Things path: users -> user_id-> things -> !!!categoryID!!!
DatabaseReference thingRef
                    = mDatabaseReference.child("users").child(mUserId)
                        .child("things").child(catID);
String thingId = thingRef.push().getKey();
 
Thing t1 = new Thing(thingId, catId,"Rzecz 1");
 
Map<String, Object> childUpdates3 = new HashMap<>();
 
//There is only thingId. Not catId + '/' + thingId.
childUpdates3.put(thingId, t1.toFirebaseObject());
thingRef.updateChildren(childUpdates3);

Join my Newsletter! 👨‍💻

Subscribe to get my latest content by email 🦾

Also read...

The best entries...