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);