What I learned From Using Firebase Realtime Database

Bonsu Adjei-Arthur
11 min readApr 26, 2020

I began using Firebase Realtime database right at the end of my Final Year of College at a time when a good working project could save you from a lifetime of issues with your Supervisors and Academic board. I had been working on an Android Java project and thus needed a Backend for my project. Looking at the timeline involved, a friend recommended Firebase to me. Since I needed a backend that could manage an all required inclusive tool such as authentication, hosting, storage and at the same time providing a great database structure to store my data, I decided to opt-out of the MySQL world at the time which with its several experienced programmers and went into this new JSON based tool presented by Google.

As a new tool, it was not long before I experienced, the harm of being a first-time user. From countless nights of seeing no results to figuring out ways of making simple queries which would have taken just a few minutes using other database systems, I undertook a journey of pain and stress as experienced by most programmers on their path to explore a new tool and leverage it to the max. This article is thus for anyone who is on the beginning path of using Firebase. Although Google has moved on and made this tool even better with several versions each year and new ways of implementations and improvements such as the inclusion of Firestore, its new scalable Database, and others, Firebase Realtime Database is still used by the majority of Android Programmers due to its tried years of use. It is important for the beginner to understand how this tool works in order not to make assumptions that are brought from using another Database. This article with codes written in Android Java has thus been written to enlist what I learned on the path to discovering what and what not to do in attempting to use Firebase in building Mobile Applications.

It is an Asynchronous API

First of all, it is important for the beginner to understand that Firebase is an asynchronous API since it makes use of the HTTP client protocol which is synchronous in the sense that every request will get a response but asynchronous in the sense that response takes a long time and multiple operations can be run in parallel. This means that response from your Firebase server may take a long time to load on your screen and although everything may well be put in place, sometimes your application may seem not to respond due to this short time drag. It is also easy for your request to not appear on your screen for some period of time. Again, it is easy for you to miss values if the right implementation is not done. For instance, in the following code, we attempt to bind a Firebase request to our Viewholders.

String name;
String productnames;
String productnamesinsnapshot;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView((R.layout.productactivity));
productnames = "";
}
@Override
public int getItemCount() {
return super.getItemCount();
}

@Override
protected void onBindViewHolder(@Nullable final ViewHolder holder, int position, @Nullable Products model) {
if (model != null) {
name = "Hand Bag";
pid = "M3WgbcMJRrIUK6RMS7";

holder.productsname.setText(name);

Log.d(TAG, " BeforeProductName " + name);

FirebaseDatabase myfirebaseDatabase;
myfirebaseDatabase = FirebaseDatabase.getInstance();
DatabaseReference Products;
Products = myfirebaseDatabase.getReference().child("Products");
Products.keepSynced(true);
Query firebasequery =
Products.orderByChild("pid").equalTo(pid);

firebasequery.addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {

for (DataSnapshot dataSnapshot1 : dataSnapshot.getChildren()) {
if (dataSnapshot1.child("name").getValue() != null) {
productnamesinsnapshot = dataSnapshot1.child("name").getValue(String.class);
holder.productsname.setText(productnames);
Log.d(TAG, "AtProductName " + productnamesinsnapshot);
}
}
}
@Override
public void onCancelled(DatabaseError databaseError) {
}
});
holder.productsname.setText(productnames);
Log.d(TAG, "AfterProductName" + productnamesinsnapshot);

The log results for this will be

D/Google Activity: BeforeProductName Hand Bag
D/Google Activity: AfterProductName null
D/Google Activity: AtProductName Hand Bag

It is easy for one to assume that since productnamesinsnapshot is accessed publicly, it should populate the Viewholder since it is already set in at the ValueEventListener, but this is where the understanding of Asynchronous APIs comes in handy. Because the database attempts to make operations even when operations have not been received, it obtains the value from the ValueEventListener but since it has not received the value yet, it proceeds with the rest of the operation, thus productnamesinsnapshot remains empty. When set to the last holder, it does not populate and renders null. Later the value is received and binds to the second holder with value logged at “AtProductName”.After several attempts at testing frustratingly, I figured it out.

I learned surprisingly here that values outside the Firebase Request are not publicly available hence the need for Callbacks and other implementations before using those values in any part of your Activity. One way was to make all the operations in the OnDataChange() method and so the Viewholder, holder, as seen in the example, was not set outside the ValueEventListener but rather in the OnDataChange() method. This small difference could save you lots of time if you try it that way. Trust me I found out late. This article here provides the best resource online for understanding and solving this issue.

It Is Can Be Hard To Retrieve From Nested Nodes

At the first look, Firebase looks very simple. Just pulling values from a list of JSON tree structure and following a sequence of paths to make retrieval, with some key pair values. The Database structure could also seem simple but could end up one of the biggest surprises. Most of the Firebase Documentation normally deals with retrieving values from one node, usually the root node of the database. But practically implementations are more complex than that. Retrieving from child nodes could be a great issue if an understanding is not seen from the way a Firebase sees differently from the way you see it. Let’s check this Firebase Tree Structure:

Retrieving values from this structure means moving to each node at a time. This is where the terminologies come into play. Words like Snapshot, Key could mess up and I needed to figure out what each means. I realized to my great excitement that regularly checking your logs to know precisely what it is retrieving could help prevent assumptions from what you think the Database should and must retrieve. For every node you reference, it retrieves all the child nodes from that reference point.

For instance, using the parseSnaphot along with the Firebaseadapter to retrieve values from our database:

String traderuser = "qqKFb5Jag5asMUVFWU2DQUc8fSL1qqKFb5Jag5asMUVFWU2DQUc8fSL1";
String userID = "qqKFb5Jag5asMUVFWU2DQUc8fSL1";
Query queryhere =
FirebaseDatabase.getInstance().getReference().child("Orders").orderByChild("traderuser").equalTo(traderoruser);
Query previoususerorderquery = queryhere.getRef().orderByChild("uid").equalTo(userID);
if (previoususerorderquery != null) {
FirebaseRecyclerOptions<Order> options =
new FirebaseRecyclerOptions.Builder< Order >().setQuery(previoususerorderquery, new SnapshotParser< Order>() {
@Nullable
@Override
public Order parseSnapshot(@Nullable DataSnapshot snapshot) {
Log.i(TAG, "Admin New Customers " + snapshot);
if (snapshot.child("orderkey").getValue(String.class) != null) {
orderkey = snapshot.child("orderkey").getValue(String.class);
}
if (snapshot.child("name").getValue(String.class) != null) {
name = snapshot.child("name").getValue(String.class);
}
return new Order (orderkey, name);
}
}).build();

We can, therefore, retrieve values from this Database using this structure

snapshot.child("name").getValue(String.class);

The easy method may create an easy assumption for the beginner until he begins to go deeper into the node. From the previous example, the routine for retrieving values for products may be:

snapshot.child("products").child("pid").getValue(String.class);

This is a wrong attempt since Firebase begins from snapshot, Firebase loops to each node in a step by step order and thus several nodes have been missed in this attempt. A request may return null when trying to thus retrieve values from the products node.

Let us create a new retrieval means to understand it better. First, let’s check up this example here.

@Nullable  Query queryhere =                     FirebaseDatabase.getInstance().getReference().child("Orders").orderByChild("traderuser").equalTo(traderuser);if (queryhere != null) {FirebaseRecyclerOptions<Order> options =
new FirebaseRecyclerOptions.Builder< Order >()
.setQuery(queryhere, new SnapshotParser< Order >() {

@Nullable
@Override public Order parseSnapshot(@Nullable DataSnapshot snapshot) {
snapshotkeys = snapshot.getKey();
Log.d("SNAPshotkey", snapshotkeys);

for (DataSnapshot snapshot1 : snapshot.getChildren()) {
secondsnapshotkey = snapshot1.getKey();

Log.d("SNAPSHOTKEYS", secondsnapshotkey);
for (DataSnapshot snapshot2 : snapshot1.getChildren()) {
thirdsnapshotkey = snapshot2.getKey();
Log.d("THIRDSNAPSHOTKEYS", thirdsnapshotkey);
if (snapshotkeys != null) {
if (secondsnapshotkey != null) {
if (thirdsnapshotkey != null) {
if(snapshot.child("products").child(thirdsnapshotkey).child("image").getValue() != null) { image = snapshot.child("products").child(thirdsnapshotkey).child("image").getValue(String.class);
}
if (snapshot.child("products").child(thirdsnapshotkey).child("name").getValue() != null) {
name = snapshot.child("products").child(thirdsnapshotkey).child("name").getValue(String.class);
}
}
}
}
}
}

return new Order (image, name);
}
}).build();

We keep looping through the nodes. From each snapshot, we can see the key we retrieved by calling the getKey() method. Here are a few logs to understand what happens when we call for the keys for each snapshot to be displayed.

D/SNAPshotkey: -Lyyv6smOjUFylfUSnho
D/SNAPSHOTKEYS: address
D/SNAPSHOTKEYS: amount
D/SNAPSHOTKEYS: city
D/THIRDSNAPSHOTKEYS: ASAHSGDSKHGGKJDSFHHKJDGHKJKDS
D/SNAPSHOTKEYS: products
D/THIRDSNAPSHOTKEYS: ADHDLKHDLKHLKDHKJHDKHKFDKHFDKLHDFKJHF

Hence from example two, notice in our attempt to get the values in the product node, we actually jumped from using snapshot1.getKey() to using snapshot2.getKey() before calling on pid. In fact, snapshot1.getKey() is actually products as shown in the log. So, in traversing the node, we can imagine us making a rewrite of our attempt as:

FirebaseDatabase.getInstance().getReference().child("Orders").child(snapshot.getKey()).child(snapshot1.getKey()).child(snapshot2.getKey()).child("image");

Hence the same way we see in retrieving that value we see. This breakdown made this whole technical stuff way simple!

if(snapshot.child("products").child(thirdsnapshotkey).child("image").getValue() != null) {                                                         
image = snapshot.child("products").child(thirdsnapshotkey).child("image").getValue(String.class);
}

The Models Must Follow All Required Rules

Models may look that innocent, but I learned that it is very important for one to avoid many problems that come along with Models in Firebase. Model Classes interface with your Firebase Database and as such provide a sort of Key Pair structure to retrieve the information unto your Database.

Let’s look at this example Model Class:

public class Order {

private String desc, image, name;
public Cart() {
}
public Order (
String desc, String image, String name;

) {
this.desc = desc;
this.image = image;
this.name = name;

}
public String getdesc () {
return desc;
}
public void setdesc (String desc) {
this.desc = desc;
}
public String getimage () {
return image;
}

public void setimage (String image) {
this.image = image;
}

public String getname () {
return name;
}

public void setname (String name) {
this.name = name;
}

}

Taking our Sample Model Class for instance there are some few things to note well.

Have an empty constructor: Make sure you call an empty constructor. Not calling an empty constructor could prove the difference between your happy working and a late-night sleep.

Same name: Although it may look extremely simple and has been specified in many documentations, it is easy to miss this. Your data will not be fetched if the name of your variables does not correspond with the names specified in your Firebase. Make sure the names are consistent. For instance, image is the same as image in our Firebase Database. In fact think of it simply as whatever names are in my Database Tree is going to be the same in my Model class as well, Capiche?

Now let’s check this Firebase Realtime Database tree here:

Image showing numerical values inputted as strings in Firebase

Do not retrieve the value from Non-Value Nodes: It took me missing a vital hackathon to realize this important thing. When calling from your Model Class avoid adding the name of your Nodes. Firebase does not see it as a point to retrieve values but a place to retrieve a tree of values also known as Hashmap. The best practice is to have, for instance, in this example a string called one. Having a string variable inside your Model Class called “l” could be a problem since the information could be retrieved as a Hashmap and cannot be converted in the way you do not want it, if, for instance, you want to just retrieve a string value.

The “Cannot Convert Object of Type String to HashMap Error” could cause you a lot of sleepless nights.

You Must Be Sure Of Your Input Values

For numbers make sure that to avoid having double quotes if you are dealing with numerical figures. Sometimes the value type of what is on your client-side may be different from the value specified in Firebase. I recommend making every number into a string value inside your Firebase Database with double quotes as seen in the previous Firebase Database Tree and manipulating the value in your Android Code with the Integer.parse(value) in Java or whatever language you will like to write in.

Normalization Makes Your Structure Simple And Easy To Deal With

Countless to say Normalization of your database is so important if you want to avoid several frustrations when retrieving values and also avoid several database complexities as your Database grows with time. Also speed retrieval is also thus important. Many recommendations as to the best practices are specified in https://howtofirebase.com/firebase-data-modeling-939585ade7f4 as well as the means of optimization.

Listeners and Adapters must be well Handled:

Firebase is an API with a lot of Listeners, listening to calls to understand changes and respond to them. One of the ways Listeners are very important is in the authentication. Make sure to understand the best way to authenticate. Also, in attaching adapters such as Recyclerview it is important to know the best way. I have created a simple means to make sure the reader understands the best way to work with listeners.

1. First, make your adapter public as well as anything you may need to access publicly

public FirebaseRecyclerAdapter adapter;
RecyclerView recyclerView;
FirebaseAuth mAuth;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView((R.layout.activity_home));

2. Set your Firebase Adapter Layouts

recyclerView = findViewById(R.id.recycler_menu);

LinearLayoutManager layoutManager = new LinearLayoutManager(this);
if (recyclerView != null) {
recyclerView.setLayoutManager(layoutManager);
}

3. Get your instance of your Firebase Authenticator in your OnCreate() Method

mAuth = FirebaseAuth.getInstance();

4. Make sure you deal with every Firebase call before you bring in your adapter;

FirebaseDatabase myfirebaseDatabase = irebaseDatabase.getInstance();
DatabaseReference myProducts= myfirebaseDatabase.getReference().child("Product");

5. Your adapter procedures could all be bundled up as a function called fetch() and called in your OnCreate() method

fetch();
recyclerView.setAdapter(adapter);

6. As shown in 5, set the adapter immediately after, binding your FirebaseAdapter immediately to your recyclerview.

Now in dealing with your listeners, make sure to call them in the onStart() method, hence your application will start listening immediately the application begins to call.

@Override
protected void onStart() {
super.onStart();
firebaseAuthListener = new FirebaseAuth.AuthStateListener() {
@Override
public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) {
user = mAuth.getCurrentUser();
if (mAuth != null) {
if (user != null) {

traderoruser = user.getUid();
}
}
} };

if (mAuth != null) {
mAuth.addAuthStateListener(firebaseAuthListener);
}

if (adapter != null) {
adapter.startListening();

}

}

7. Now with all the right sequence, your app will listen to Firebase, you can also read more on Firebase Listeners and Firebaseadapters to fully comprehend the best way to manipulate it, else you can be left with a blank screen since maybe you seem to jump some vital steps each time. Don't worry you will be just fine!

Check your Firebase Versions Carefully:

Make sure to check the version of the Firebase version you are using to know what is the best implementation method. Most documentation follows after a particular version which is updated periodically. So don't be shocked that the amazing YouTube tutorial did not work exactly the way you wanted it to end up!

Index your Database as Database grows:

Indexing your database helps Firebase to easily retrieve your Database so always make sure to index your Database. Indexing is best explained here.

Multiple Queries Could Prove A Big Hack!

Firebase RealTime Database structure with traderuser used here for a unique index for multiple queries

Finally, be aware of the restriction in writing multiple queries in Firebase Realtime Database. It has certain limitations as compared with Firestore, as shown in https://firebase.google.com/docs/database/rtdb-vs-firestore and thus multiple queries at a time could result in an error. It is thus important to create a unique index in your database. Even trying to sort in descending could feel like rocket science if you don't get the information needed. So let’s see. This is what I learned. In trying to query for multiple query clauses to retrieve tid and uid, we create a unique index of traderuser which contains the two values, thus enabling us to make an effective order. As seen in this query.

Query queryhere =
FirebaseDatabase.getInstance().getReference().child("Orders").orderByChild("traderuser").equalTo(traderuser);

With this unique index, you can query for multiple values where it would have been just simple with somewhere clauses somewhere. Anyway, an improvement has been made with Firestore and hence these queries have become way easier.

Conclusion: Using Firebase can sometimes prove a challenge, but understanding how it works enables you to leverage its powerful tools in creating an enterprise application that can help leverage other Google-based platforms and enable your business to accelerate as you get acquainted with its features and with its internal mechanism, whether on Web or on Mobile. You have what it takes but like they always say, “experience is the best teacher”. Happy Firebase Realtime Experience!

--

--

Bonsu Adjei-Arthur

Bonsu writes on current trends in the TechSpace during his free time. He is also the CTO TradeXGh and the Lead Designer for PArtNetwork.