This is a question related to parsing list/array data. I can create and update collection documents and I can read the total collection of all documents. Once the collection is read, I can cycle through the result and parse all of the fields with the exception of the "sitePages" field, saved as an array in Firebase firestore. There are 10 instances of this array saved within the sitePages variable. I have included a screen capture of the database structure below.

The following screen capture is how the sitePages variable (List) is stored within the SitesList variable in the app. The sitepages variable is stored as an "Arrays$ArrayList"

Below is a screen capture of how the sitePages array is read from the database. The sitePages variable is read from the database as an "Object[10]" of an "ArrayList". How do I access the data within the elementData object?

How do I access the 10 lists within the elementData object, contained in the result, and transfer the contents to the sitePages list. Any assistance would be appreciated. If there is an alternative way I should be saving or reading the data, I am open to modifying that code too.
I have included the code that I am using to both read and write to the database. I think I have included a sufficient subset.
data class SitesList(
var siteId: Int = 0,
var siteImage: Int = R.drawable.paraglider1,
var siteName: String = "",
var sitePages: List<SitePagesList>
) {
fun toMap() = mapOf(
"siteId" to siteId,
"siteImage" to siteImage,
"siteName" to siteName,
"sitePages" to sitePages
)
}
data class SitePagesList(
var pageId: Int = 0,
var pageContent: String = "",
var pageImage: String? = null
) {
fun toMap() = mapOf(
"pageId" to pageId,
"pageContent" to pageContent,
"pageImage" to pageImage,
)
}
var sitesList = mutableStateListOf<SitesList>()
init {
getSitesList()
}
fun getSitesList() {
getSitesListFromDb()
}
private fun createOrUpdateSite(site: SitesList) {
// create or update an individual site from SitesList
sitesDb.collection("sites")
.document(site.siteId.toString())
.get()
.addOnSuccessListener {
if (it.exists()) {
it.reference.update(site.toMap())
.addOnSuccessListener {
// completed successfully
}
.addOnFailureListener {
// error handling logic removed
}
} else {
sitesDb.collection("sites")
.document(site.siteId.toString())
.set(site)
}
}
.addOnFailureListener {
// error handling logic removed
}
}
private fun getSitesListFromDb() {
sitesDb.collection("sites")
.get()
.addOnCompleteListener { task ->
if (task.isSuccessful) {
val listOfDocuments = task.result
sitesList.clear()
for (document in listOfDocuments) {
val sitePages = document.data["sitePages"] // used to view contents of the arrayList
@Suppress("UNCHECKED_CAST") val list = SitesList(
siteId = document.data["siteId"].toString().toInt(),
siteImage = document.data["siteImage"].toString().toInt(),
siteName = document.data["siteName"].toString(),
sitePages = document.data["sitePages"] as List<SitePagesList>
)
sitesList.add(list)
}
} else {
// error handling logic removed
}
}
}
Edited with comments/code/logs added as requested:
Here is the code for the log ...
Log.d(TAG, "sitePages => $sitePages")
Log.d(TAG, "list.sitePages => ${list.sitePages}")
Here is the result (my apologies for the quantity of text):
> 2026-02-06 18:31:50.317 19033-19033 ContentValues com.example.cloudbase D sitePages => [{pageImage=null, pageContent=This ridge site ..., pageId=0}, {pageImage=null, pageContent=From Highway 1A, turn west..., pageId=1}, {pageImage=null, pageContent=The site is a ridge, and launching..., pageId=2}, {pageImage=null, pageContent=Top landing at the ridge is..., pageId=3}, {pageImage=null, pageContent=The site itself is designated..., pageId=4}, {pageImage=null, pageContent=Don’t even think..., pageId=5}, {pageImage=null, pageContent=This is the Muller...!, pageId=6}, {pageImage=null, pageContent=, pageId=7}, {pageImage=null, pageContent=https://www.mullerwindsports.com/, pageId=8}, {pageImage=null, pageContent=Local Site Contact: > > > fly@mullerwindsports.com > > > 1(403)932-6760, pageId=9}] > > 2026-02-06 18:31:50.317 19033-19033 ContentValues com.example.cloudbase D list.sitePages => [{pageImage=null, pageContent=This ridge site is..., pageId=0}, {pageImage=null, pageContent=From Highway 1A, turn west..., pageId=1}, {pageImage=null, pageContent=The site is a..., pageId=2}, {pageImage=null, pageContent=Top landing at..., pageId=3}, {pageImage=null, pageContent=The site itself..., pageId=4}, {pageImage=null, pageContent=Don’t even think..., pageId=5}, {pageImage=null, pageContent=This is the Muller...!, pageId=6}, {pageImage=null, pageContent=, pageId=7}, {pageImage=null, pageContent=https://www.mullerwindsports.com/, pageId=8}, {pageImage=null, pageContent=Local Site Contact: > > >fly@mullerwindsports.com > > > 1(403)932-6760, pageId=9}]
Here is a loop attempting to access the variables in sitePages ...
var index = 0
val size = list.sitePages.size
while (index < 10) {
Log.d(TAG, "pageId => ${list.sitePages[index].pageId}")
Log.d(TAG, "pageImage => ${list.sitePages[index].pageImage}")
Log.d(TAG, "pageContent => ${list.sitePages[index].pageContent}")
index++
}
Using list.sitePages.size gives me a correct result of 10 but accessing "list.sitePages[index].pageId" crashes.
I've tried .toString().toInt() after pageId.
I've also tried "list.sitePages.toMutableList()[0]" but this also crashes.
- - -
Here is another small code example.
Log.d(TAG, "listOf(list.sitePages[index]) => ${listOf(list.sitePages[0])}")
val list2 = listOf(list.sitePages[0])
val tempId = list2[0].pageId
The result of the Log.d is:
listOf(list.sitePages[index]) => [{pageImage=null, pageContent=This ridge site is ..., pageId=0}]
When the line "val tempId = list2[0].pageId" executes the app crashes.
The value of list2 is below - from debug mode.
list2 = {Collections$SingletonList@40522} size = 1
0 = {HashMap@40525} size = 3
"pageImage" -> null
key = "pageImage"
value = null
"pageContent" -> "This ridge site is ..."
key = "pageContent"
value = "This ridge site is ..."
"pageId" -> {Long@40535} 0
key = "pageId"
value = {Long@40535} 0
HashMap in the debugger viewAs mentioned in the comments to your question:
> Hint: the logged inner contents being enclosed in curly braces {} indicates that it's a key-value like structure, and from the inner "object" in the debugger screenshot you would notice that it's actually a HashMap. So you would then access its contents as per a Map's semantics as documented in Kotlin's collections document, for e.g. given a specific list element it would be element["propertyToAccessFromMap"] using the get operator
Firestore likely internally converts key-values and arrays stored in your Firestore document to a Java Map and List respectively (if you don't go the custom data way mentioned later in this answer) rather than as your custom data classes, so you will have to cast the data you get back from Firestore to a Map and List, and likely also write custom logic to manually map them to your desired data classes (as they are not a 1-to-1 replacement, so you cannot just do list2[0].pageId as-is to access a property from a map like you might do in a dynamic language like JavaScript, if you're familiar with that language, where that is how you would do it).
As for getting the data from that Map:
// You will actually need to cast it like this rather than List<SitePagesList> val sitePages = document.data["sitePages"] as List<Map<String, Any?> val element = sitePages[0] // You can then access each key in the key-value map with the `get()` operator function val pageImage = element["pageImage"] val thisCanBeNamedAnything = element["pageContent"] val myId = element["pageId"]
With a for-loop:
for (page in sitePages) { val pageImage = element["pageImage"] val thisCanBeNamedAnything = element["pageContent"] val myId = element["pageId"] }
From my comment:
> Also, just a heads-up that you don't need all this code to manually parse the data to your data class equivalent when you could use the toObject extension function on your document snapshot (as documented in the "Custom Object" section of the "Get data" docs) to do it for you
Here's an example of what I meant:
Instead of this code (in your inner getSitesListFromDb lambda call in addOnCompleteListener):
val listOfDocuments = task.result sitesList.clear() for (document in listOfDocuments) { val sitePages = document.data["sitePages"] // used to view contents of the arrayList @Suppress("UNCHECKED_CAST") val list = SitesList( siteId = document.data["siteId"].toString().toInt(), siteImage = document.data["siteImage"].toString().toInt(), siteName = document.data["siteName"].toString(), sitePages = document.data["sitePages"] as List<SitePagesList> ) sitesList.add(list) }
This can be simplified down to:
val listOfDocuments = task.result sitesList.clear() for (document in listOfDocuments) { sitesList.add(document.toObject<SiteList>()) }
Or even further simplified down to (as toObjects also exists for collection snapshots):
sitesList.addAll(listOfDocuments.toObjects<SitesList>())
And another nitpick as I mentioned in the comments:
> Another note that you can simplify your code to use Kotlin Coroutines by await()ing each Firestore call that returns a Task (this is what you're actually calling addOnCompleteListener/addOnFailureListener/addOnSuccessListener on) - see https://stackoverflow.com/questions/71585047/how-to-await-task-completion-and-return-a-variable for more information [...]
With await(), you can simplify the callback handling down to this (note that you'll have to run this code within a suspending CoroutineScope):
private suspend fun createOrUpdateSite(site: SitesList) { // You would use the standard error catching (try-catch) to catch errors // that you would've otherwise caught from addOnFailureListener try { val document = sitesDb.collection("sites") .document(site.siteId.toString()) .get() // This would return Task<DocumentSnapshot> or similar .await() // This gives you an unwrapped version of the result (so DocumentSnapshot) if (document.exists()) { document.reference.update(site.toMap()).await() } else { sitesDb.collection("sites") .document(site.siteId.toString()) .set(site) .await() // <-- Added await() here too as you seem to be doing a fire-and-forget here } } catch (e: Exception) { // Handle failures here } } private suspend fun getSitesListFromDb() { try { val collectionData = sitesDb.collection("sites") .get() .await() // This already gives you the result data, so no need for another .getResult()/.result call sitesList.clear() sitesList.addAll(collectionData.toObjects<SitesList>()) // There's probably no need for an explicit generic here, so you might remove it if desired } catch (e: Exception) { // etc } }
I would make your variables immutable where desired:
mutableStateListOf property here should not be mutable - its contents are mutable, not the container itselfval sites = mutableStateListOf<...>()
copy() function that data classes have:data class SitesList( val siteId: Int = 0, @field:DrawableInt // I would annotate this so that the IDE can pick-up that *only* drawable resources should be used and not just any random integer that isn't a valid resource val siteImage: Int = R.drawable.paraglider1, val siteName: String = "", val sitePages: List<SitePagesList> ) // To modify an element in the MutableStateList: val sites = mutableStateListOf<SiteList>() // If you know the index: sites[index] = sites[index].copy(siteName = "new value") // If you don't know the index but have the current object val existingSite = SiteList(...) val index = sites.indexOf(existingSite) sites[index] = sites[index].copy(...)
For Compose state of only a single object:
var site by mutableStateOf<SiteList>(...) site = site.copy(...) // Or val site = mutableStateOf<SiteList>(...) site.value = site.value.copy(...)
If you want to get real-time updates on your data, you can use the snapshots() extension function available on the Query class (which the FirestoreCollection class extends from) to get the data as a Flow<DocumentSnapshot>:
val siteSnapshots = sitesDb.collection("sites").snapshots()
(See the Kotlin docs on what a Flow is)
Or you can get the data parsed as your data class with the dataObjects<T>() extension function, which expects a generic of the data class that a document in the collection would be using:
val sitesList = sitesDb.collection("sites").dataObjects<SiteList>() // This would then return a Flow<List<SiteList>>
(Internally, dataObjects<T>() is really just snapshots().map { it.toObjects<T>() })
dataObjects<T>() also works with a document reference, but it'll return a Flow<T?>:
val site = sitesDb.collection("sites").document("1").dataObjects<SiteList>() // Returns Flow<SiteList?> // You can do filterNotNull() if you do desire a non-null instance val notNullSite = site.filterNotNull() // Flow<SiteList>
You can then collect it as a Compose state with the collectAsState/collectAsStateWithLifecycle extension functions:
// I'm assuming all that code is in a ViewModel @Composable fun SitesListScreen(viewModel: SitesListViewModel) { val data by viewModel.sitesList.collectAsStateWithLifecycle() // collectAsStateWithLifecycle() returns a State<T> or a State<List<SiteList>> in this case // ... }