skip to Main Content

A solution can be found at the bottom. If you wish to understand the context read along!

Bloody beginner in Kotlin/Android here and trying his best.

I am trying to push a list of data objects into Firestore and managed to pull that off with the following function:

fun createData(collectionName:String){
//Create Data here
    val docData: MutableMap<String, Any> = HashMap()
    docData["data"] = Arrays.asList(
        JournalEntry("Title 1", 0, 0),
        JournalEntry("Title 2", 0, 1),
        JournalEntry("Title 3", 0, 0)
    )

    db.collection(collectionName)
        .add(docData)
        .addOnSuccessListener { documentReference ->
            Log.d(TAG, "DocumentSnapshot added with ID: ${documentReference.id}")
        }
        .addOnFailureListener { e ->
            Log.w(TAG, "Error adding document", e)
        }
}

This creates the following in Firestore:
enter image description here

Now I try to receive my data using a function provided by the docs. However, I am stuck on turning the data back into a list containing my data classes that I just created. I tried a few things and I didn’t manage to resolve the issue reading the docs.

fun readFromDB(collectionName: String){
    db.collection(collectionName)
        .get()
        .addOnSuccessListener { result ->
            for (document in result) {
                Log.d(TAG, "${document.id} => ${document.data}")
                var values = ArrayList(document.data.values).toList()

                //--------------------------------------------------
                //I have no clue what to do starting from this point.
                //--------------------------------------------------
                println(values)
            }
        }
        .addOnFailureListener { exception ->
            Log.w(TAG, "Error getting documents.", exception)
        }
}

Printing out the result which is a hashMap gives me:

[[{moodLevel=0, imageId=0, text=Title 1}, {moodLevel=1, imageId=0, text=Title 2}, {moodLevel=0, imageId=0, text=Title 3}]]

Question:
How can I turn this result into a list of objects again?

Tried a few things but nothing worked. Also read through a few threads here and didn’t find an answer that could apply to my use case. Any help would be appreciated. Sorry for this question if it’s really obvious in its solution.

Edit

After some tinkering following a guide from Mr Mamo I did this:

  1. Add a data class JournalEntryFirebaseHolder which looks like this
data class JournalEntryFirebaseHolder (
    var journalEntryList: MutableList<JournalEntry>? = null
)
  1. Change the way I push my data into Firestore using the following function
    fun createUserWithUID(UID:String){
        val docData: MutableMap<String, Any> = HashMap()
        docData["entries"] = Arrays.asList(
            JournalEntry("Title 1", 0, 0, "1"),
            JournalEntry("Title 2", 0, 1, "2"),
            JournalEntry("Title 3", 0, 0, "3")
        )

        db.collection("entries")
            .document("user1")
            .set(docData)
            .addOnFailureListener { e ->
                Log.w(TAG, "Error adding document", e)
            }
    }

This creates the following in Firestore:
enter image description here

  1. Recieve data using the following function like this
    fun readFromDB(UID: String){
        val rootRef = FirebaseFirestore.getInstance()
        val applicationsRef = rootRef.collection("entries")
        val applicationIdRef = applicationsRef.document("user1")

        applicationIdRef.get().addOnCompleteListener { task: Task<DocumentSnapshot> ->
            if (task.isSuccessful) {
                val document = task.result
                if (document.exists()) {
                    val resultJournalEntries = document.toObject(JournalEntryFirebaseHolder::class.java)?.journalEntryList
                    //--------
                    //This returns null
                    //--------
                    println(resultJournalEntries)

                    //--------
                    //This returns the following
                    //{entries=[{moodLevel=0, imageId=0, text=Title 1, id=1}, {moodLevel=1, imageId=0, id=2, text=Title 2}, {moodLevel=0, imageId=0, id=3, text=Title 3}]}
                    //--------
                    println(document.data)
                }
            }
        }
    }
  1. My JournalEntry data class looks like this
data class JournalEntry (val text : String, val imageId : Int, val moodLevel : Int, val id :String )

Problem: I made some progress but I am still struggling to convert the data I receive back into JournalEntry Objects. Any help is appreciated.

Solution

Thank you Mr Mamo for helping me find this solution! Here is what I did:

  1. Change my JournalEntry data class like this to include default values. This is because my data class needs a no-arg constructor else my code will throw a exception later on. Read this question for more information.
data class JournalEntry (val text : String = "", val imageId : Int = 0, val moodLevel : Int = 0, val id :String = "0")
  1. In my readData function read my data like this:
    fun readFromDB(){
        val rootRef = FirebaseFirestore.getInstance()
        val applicationsRef = rootRef.collection("entries")
        val applicationIdRef = applicationsRef.document("user1")

        applicationIdRef.get().addOnCompleteListener { task: Task<DocumentSnapshot> ->
            if (task.isSuccessful) {
                val document = task.result
                if (document.exists()) {
                    //This returns an ArrayList with JournalEntry Objects!
                    val entries = document.toObject(JournalEntryFirebaseHolder::class.java)?.entries
                }
            }
        }

JournalEntryFirebaseHolder basically holds the received list that I fetch from Firestore. You can find detailed information in my -Edit- Section as well as in the marked answer.

2

Answers


  1. You can convert your list to a HashMap as follows:

    val map = list.associateBy({it.title}, {it})
    

    So you would have a map with a title as your key and the content of the object. (Since the titles can be repeated, I recommend assigning an ID so that you associate the object with that ID)

    Then you just upload your map by doing:

    db.collection(/*your collection*/).document(/*your document*/).set(map, SetOptions.merge() /*Optional if you don't want all fields with the same ID to be overwritten */)
    

    Then to get it:

    db.collection().document().get().addOnSuccessfullyListener{
       val list = it.toObjects(JournalEntry::class.java)
    }
    

    Or if you don’t want to modify what you already have, when you get the data, take it out of the array, since you would be doing nested lists, just leave:

    val values = document.data.values.toList()
    

    And I recommend installing the Gson library to convert the objects to Json and vice versa, and you just get it:

    val myEntriesList = values.map { Gson().fromJson(it.toString, JournalEntry::class.java) }
    
    Login or Signup to reply.
  2. Unfortunately, Firestore doesn’t provide a toList() method inside the DocumentSnapshot class. However, there is a really simple solution that can solve this issue. So to get the content of the "data" array and convert it into a list of JournalEntry objects, then you should simply create a new class that contains that specific list like this:

    data class JournalEntryDoc (
        var entries: MutableList<JournalEntry>? = null
    )
    

    Now to read the data, you can simply use:

    val entries = document.toObject(JournalEntryDoc.class).entries
    

    I have also written an article on this topic called:

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search