Realtime Database (Java)
Firebase Realtime Database is Firebase's original cloud database product, predating Cloud Firestore from the previous lesson. Instead of collections and documents, it stores everything in one giant JSON tree — the whole database is, conceptually, a single nested object that every connected client reads and writes pieces of.
Realtime Database vs Firestore: An Honest Comparison
| Concern | Realtime Database | Cloud Firestore |
|---|---|---|
| Data model | One large JSON tree | Collections of documents |
| Querying | Limited — hard to combine multiple filters | Rich queries: whereEqualTo, orderBy, compound filters |
| Scaling & structure | Gets awkward as data grows and nests deeper | Scales cleanly with flatter, indexable documents |
| Latency | Extremely low — very fast updates | Fast, though typically a touch behind Realtime DB |
| Google's guidance | Still supported, maintenance mode mindset | The recommended default for new projects |
Writing Data with setValue()
A DatabaseReference points at a location in the tree, the same way a StorageReference will point at a file location in a later lesson. .setValue() writes (or completely overwrites) whatever is at that location.
DatabaseReference databaseReference = FirebaseDatabase.getInstance().getReference();
Recipe recipe = new Recipe();
recipe.title = "Weekend Pancakes";
recipe.category = "Breakfast";
recipe.prepMinutes = 15;
recipe.ownerUid = FirebaseAuth.getInstance().getCurrentUser().getUid();
String recipeId = databaseReference.child("recipes").push().getKey();
databaseReference.child("recipes").child(recipeId).setValue(recipe)
.addOnSuccessListener(unused ->
Log.d("RecipeBox", "Recipe saved at recipes/" + recipeId))
.addOnFailureListener(e ->
Log.e("RecipeBox", "Save failed", e));
- .child("recipes") descends into the recipes branch of the tree; .push() generates a unique, chronologically-sortable key for the new recipe.
- .child(recipeId).setValue(recipe) writes the full Recipe object as JSON at that exact path.
Reading Data with ValueEventListener
Reading works through a listener interface rather than a one-shot call: onDataChange() fires immediately with the current data, and again every time it changes — conceptually similar to Firestore's addSnapshotListener(), just with a different API shape.
databaseReference.child("recipes").addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot snapshot) {
List<Recipe> recipes = new ArrayList<>();
for (DataSnapshot child : snapshot.getChildren()) {
Recipe recipe = child.getValue(Recipe.class);
recipes.add(recipe);
}
adapter.submitList(recipes);
}
@Override
public void onCancelled(@NonNull DatabaseError error) {
Log.e("RecipeBox", "Read failed", error.toException());
}
});
- snapshot.getChildren() iterates every recipe currently stored under the recipes branch.
- child.getValue(Recipe.class) converts one branch's JSON back into a Recipe object.
- onCancelled() fires for permission errors — for example, if Security Rules reject the read.
Which One Should RecipeBox Use?
Starting a new project? Choose Firestore, covered in the previous lesson — its richer querying and cleaner scaling make it the safer long-term default, and it's what Google itself now points new developers toward.
Already maintaining an app built on Realtime Database? There's rarely a compelling reason to migrate an existing, working data model just to switch products.
Need extremely low-latency presence detection (e.g., "is this user online right now")? Realtime Database's connection-state features remain a genuine, specific strength.