avatarAlex Mamo

Summary

This article provides a solution for mapping an array of objects from the Firebase Realtime Database into a list of custom objects in an Android application.

Abstract

The Firebase Realtime Database does not store arrays but associative arrays, which can be translated into arrays when read in application code using Firebase SDKs. The article demonstrates how to read an array of custom objects from the database and map it into a List<Product> using a GenericTypeIndicator object. It also discusses the limitations of using arrays in the Realtime Database and suggests using the DatabaseReference#push() method instead.

Bullet points

  • The Firebase Realtime Database stores associative arrays, which can be translated into arrays when read in application code using Firebase SDKs.
  • To read an array of custom objects from the database and map it into a List<Product>, a GenericTypeIndicator object can be used.
  • The DatabaseReference#push() method is recommended over using arrays in the Realtime Database.
  • The article provides code examples for reading an array of custom objects and mapping it into a List<Product>.
  • The limitations of using arrays in the Realtime Database are discussed, including the lack of an atomic operation for adding an object directly to the array.
  • The article suggests using the DatabaseReference#push() method instead of arrays for storing data in the Realtime Database.

How to map an array of objects from Realtime Database to a List of objects?

A simple solution for mapping an array of objects from the Realtime Database into a list of custom objects

Before starting, if you are using Cloud Firestore and you’re looking for a similar solution, I have already written an article regarding the same topic:

Getting back to our subject, technically speaking, the Firebase Realtime Database doesn’t store arrays. It can store structures called associate arrays, which represent:

An abstract data type that stores a collection of (key, value) pairs.

So if you think of something like this:

{
  "products": [
    "Laptop",
    "Tablet",
    "Mobile Phone",
    "LCD TV",
    "Smartwatch"
  ]
}

That’s definitely possible. You can add such a structure either from code or directly from the Firebase Console.

When we read the array in our application code with one of the Firebase SDKs, it always will be translated into an array. In Android, we can do it like this:

val db = Firebase.database.reference
db.child("products").get().addOnCompleteListener {
    if (it.isSuccessful) {
        val products = it.result.value as List<String>
        Log.d(TAG, products.toString())
    } else {
        Log.d(TAG, it.exception?.message.toString())
    }
}

The result in the console will be:

[Laptop, Tablet, Mobile Phone, LCD TV, Smartwatch]

If we need to read the content of the entire array, there is one thing that needs to be mentioned, the DataSnapshot#getValue() method returns an object of type Object. Since the products node in the Realtime Database is an array, the type of object that is returned can be cast to a List<String>. There is also another solution that can allow us to iterate through all the children of the node, which is by using the DataSnapshot#getChildren() method. In code, it looks like so:

db.child("products").get().addOnCompleteListener {
    if (it.isSuccessful) {
        val products = it.result.children.mapNotNull { doc ->
            doc.getValue(String::class.java)
        }
        //Do what you need to do with your list.
        Log.d(TAG, products.toString())
    } else {
        Log.d(TAG, it.exception?.message.toString())
    }
}

There is one more solution that is worth to be mentioned. When we’re using the DataSnapshot#getValue() method, we can also cast the object to Map<String, Any>.

How about reading an array of custom objects?

To be able to translate the array that exists in the database into a List<Product>, the corresponding JSON structure should look like this:

{
  "products": [
    {
      "name": "Laptop",
      "price": 899.99
    },
    {
      "name": "Tablet",
      "price": 1099.99
    },
    {
      "name": "Mobile Phone",
      "price": 599.99
    },
    {
      "name": "LCD TV",
      "price": 1399.99
    },
    {
      "name": "Smartwatch",
      "price": 699.99
    }
  ]
}

How can we map the products node into a List<Product>?

Obviously, we can create a Product class:

data class Product (
    var name: String? = null,
    var price: Double? = null
)

And use the exact same solution as above:

db.child("products").get().addOnCompleteListener {
    if (it.isSuccessful) {
        val products = it.result.children.mapNotNull { doc ->
            doc.getValue(Product::class.java)
        }
        //Do what you need to do with the product list.
    } else {
        Log.d(TAG, it.exception?.message.toString())
    }
}

And instead of reading the value as a String, we read it as a Product.

But is there any other (simpler) solution?

For sure there is. We can use an GenericTypeIndicator object. In code, it should look as simple as:

db.child("products").get().addOnCompleteListener {
    if (it.isSuccessful) {
        val gti = object : GenericTypeIndicator<List<Product>>() {}
        val products = it.result.getValue(gti)
        //Do what you need to do with the product list.
    } else {
        Log.d(TAG, it.exception?.message.toString())
    }
}

Now, there is more question that comes to my mind.

How to add a new object inside the products array?

Unfortunately, there is no atomic operation that can allow us to add an Product object directly to the array. This means we’ll need to:

  1. Read the content of the array into our Android application code.
  2. Append the Product object to the array.
  3. Write the entire array back to the Realtime Database.

This is one of the reasons why the Firebase team recommends against using arrays. Instead, it’s better to use the DatabaseReference#push() method, which generates a unique key each time is called. For more on this topic, I recommend reading this blog post:

However, if we only need a small amount of data, then storing data in an array is an option that you can take into consideration.

Conclusion

That’s the simplest solution for mapping an array of objects into a list of custom objects. I hope you found this article useful and if you have any questions regarding this topic, feel free and leave a comment in the section below.

You can also see it on YouTube:

#BetterTogether🔥

Firebase
Firebaserealtimedatabase
Android
Kotlin
Realtime Database
Recommended from ReadMedium