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:
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:
Add a data class JournalEntryFirebaseHolder which looks like this
data class JournalEntryFirebaseHolder (
var journalEntryList: MutableList<JournalEntry>? = null
)
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:
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)
}
}
}
}
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:
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")
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.
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:
How to map an array of objects from Cloud Firestore to a List of objects?
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) }
I have managed to add data as hashMap into Firestore as follows but I have a hard time reading all documents from Firebase without a model class.
val itemRequest = hashMapOf(
"item" to newItem,
"item_detail" to itemDetails,
"requested_by" to getCurrentUserID(),
"timestamp" to FieldValue.serverTimestamp(),
)
mFireStore.collection("item_request")
.document()
.set(itemRequest)
.addOnSuccessListener {
fragment.successAddingItemRequest()
}
.addOnFailureListener {
}
You can use DocumentSnapshot.data property to convert the DocumentSnapshot to Map<String, Any>.
In your case, you can use:
firestore.collection(collectionId).document(documentId).get()
.addOnSuccessListener { documentSnapshot ->
val data = documentSnapshot.data
val item = data["item"]
val itemDetails = data["item_detail"]
...
}
Here you will have to cast the returned Any map value to its appropriate data type.
But a better way to do all this would have been to create a model class for storing data and using DocumentSnapshot.getObject to get the data back.
Following is the structure of my Firestore database. I want to get all the documents from the collection 'products' where the 'pin_code' (is an Array) matches with the list of pin codes I have. The list of pin codes is from the collection 'addresses' which I have managed to get with the following code. But I am not able to get documents from the collection 'products' that match the list of pin codes.
Following is the code I have to get the pin code list from the collection 'addresses'
fun getPins(context: DashboardFragment) {
mFireStore.collection("addresses")
.whereEqualTo("user_id", getCurrentUserID())
.get()
.addOnSuccessListener { document ->
val codeList: MutableList<String> = mutableListOf()
for (i in document.documents) {
val code = i.toObject(Address::class.java)
code!!.user_id = i.id
codeList.add(code.pinCode)
}
context.getProductListBasedOnPin(codeList)
}
.addOnFailureListener { e ->
}
}
I tried to get the data from the collection 'products' with the following code. But with this code I can get the list of products only when my 'pin_code' is not an array. But I had to make the pin_code an array for some reason and I am not able to get the product list.
fun getProductListBasedOnPin(pinList: List<String>?) {
val mFireStore = FirebaseFirestore.getInstance()
mFireStore.collection("products")
.whereIn("pin_code", pinList!!)
.get()
.addOnSuccessListener { document ->
for (i in document.documents) {
val product = i.toObject(Product::class.java)!!
product.product_id = i.id
srchProductsList.add(product)
}
srchTempProductsList.addAll(srchProductsList)
if (newView == "ListView") {
successDashboardItemsListListView(srchTempProductsList)
} else {
successDashboardItemsList(srchTempProductsList)
}
}
.addOnFailureListener {
}
}
Can someone help me with this?
Thank you.
I want to get all the documents from the collection 'products' where the 'pin_code' (is an Array) matches with the list of pin codes I have.
You can definitely do that using Query's whereArrayContainsAny(String field, List<? extends Object> values) method, which:
Creates and returns a new Query with the additional filter that documents must contain the specified field, the value must be an array, and that the array must contain at least one value from the provided list.
Assuming that you want to get all documents from the "products" collection in which the "pin_code" arrays contains a List with two values ("123456" and "159874"), please use the following lines of code:
val pinCodeList = listOf("123456", "159874")
productsRef.whereArrayContainsAny("pin_code", pinCodeList).get().addOnCompleteListener {
if (it.isSuccessful) {
for (document in it.result) {
Log.d(TAG, document.id + " => " + document.data)
}
} else {
Log.d(TAG, "Error getting documents: ", task.exception)
}
}
This is done using an Array Contains conditions, using one of the various methods, you can see if an array contains some, any, all, and not-in.
val citiesRef = db.collection("products")
citiesRef.whereIn("pin_code", listOf("123456", "150875"))
Once you fetch this snapshot, it will return an array of matching documents which you can then use with other sections of your app logic and secondary Firestore queries as needed.
Source: https://firebase.google.com/docs/firestore/query-data/queries#kotlin+ktx_6
I am learning to use Firebase Firestore and I have created a list of items that I want to display in my app. When trying to load the list, I don't receive the data but I also don't get any error. I cannot display the data in the Log. What could be happening?
fun getDriverData(): LiveData<MutableList<Driver>> {
val mutableData = MutableLiveData<MutableList<Driver>>()
FirebaseFirestore.getInstance().collection("drivers").get().addOnSuccessListener { result ->
val listData = mutableListOf<Driver>()
Log.i("repo","called")
for (document in result) {
val photoUrl = document.getString("photoUrl")!!
val name = document.getString("name")!!
val team = document.getString("team")!!
Log.i("repo", "${document.id}} => ${document.data}")
val driver = Driver(name,team,photoUrl)
listData.add(driver)
}
mutableData.value = listData
}.addOnFailureListener {
Log.i("repo", "getDriverData: ${it.message}")
}
return mutableData
}
Your collection is actually called "drivers" - WITH the quotation marks. Whatever is generating the documents here is using extra quota when building the name of the collection.
You could read them back by adding those quotes:
FirebaseFirestore.getInstance().collection("\"drivers\"").get()
But you probably want to fix the code that generates the document to not add those quotes.
I have a spinner. Into that spinner I added one of my firestore collections. I thought but calling the collection I could load any data into my spinner, but when I added a second document to my collection it stopped working.
This is what I have right now:
db.collection("KitList").get().addOnSuccessListener { snapshot ->
for (document in snapshot.documents) {
val data = document.data
val skipRope = data["rope"] as String
spinnerArray.add(skipRope)
val kettle = data["kettle"] as ArrayList<String>
for (item in kettle) {
val kettleWeight = "kettle $item"
spinnerArray.add(kettleWeight)
}
}
}
I tried db.collection("KitList").document("documentname")get().addOnSuccessListener { snapshot -> but it didn't work because my snapshot.documents got an error.
Could anyone help me out here? Just want to know how I can can call multiple documents. Thanks :D
1st, create your custom data model (I will call it DataClass).
Then, in addOnSuccessListener put this code:
snapshot.documents.mapTo(spinnerArray) { it.toObject(DataClass::class.java)}
Of course, replace DataClass with your own :)