androidfirebasekotlingoogle-cloud-platformgoogle-cloud-firestore

Firebase Firestore getting data from a nested collection in jetpack compose


I'm new to Firestore but I am trying to get all the details from a collection inside a document inside a collection as shown below, store it in a list and display it in a LazyColumn inside a Scaffold. However, when I tried using an if-else statement, it shows that the list is empty:

(https://i.sstatic.net/3KWjs2Il.png) (https://i.sstatic.net/fzkVuje6.png)

This is the code that I am working with getting the data

class RestaurantViewModel: ViewModel() {
    private val firestore = FirebaseFirestore.getInstance()

    val restaurantList: MutableState<List<restaurantInfo>> = mutableStateOf(emptyList())

    init {
        fetchItems()
    }

    private fun fetchItems(){
        val tempList = mutableListOf<restaurantInfo>()
        firestore.collection("restaurants")
            .get()
            .addOnSuccessListener {
                querySnapshot->
                val documents = querySnapshot.documents

                documents.forEach {
                    document->
                    val documentRef = document.reference.collection("details")
                    documentRef
                        .get()
                        .addOnSuccessListener {
                            subDocuments->
                            subDocuments.forEach {
                                subDocument->
                                val data = subDocument.data
                                val restaurantItem = restaurantInfo(
                                    distance = data["distance"] as String?: "",
                                    location = data["location"] as String?: "",
                                    name = data["name"] as String?: "",
                                    picture = data["picture"] as String?: "",
                                    rating = data["rating"] as String?: "",
                                )
                                tempList.add(restaurantItem)
                            }
                        }
                        .addOnFailureListener{
                            exception->
                            Log.e("Firestore", "Error getting documents: ", exception)
                        }
                }
                restaurantList.value = tempList.toList()
            }
            .addOnFailureListener {
                exception->
                Log.e("Firestore", "Error getting restaurants", exception)
            }
    }

}`

Here is the data class:

data class restaurantInfo(
    var distance: String? = "",
    var location: String? = "",
    var name: String? = "",
    var picture: String? = "",
    var rating: String? = ""
)

Here is where I am displaying it:

@Composable
fun Restaurants(
    navController: NavController,
    accountViewModel: AccountViewModel,
    restaurantList: List<restaurantInfo>
){
    val userInfo by accountViewModel.itemList

    Scaffold(
        topBar = {
            TopBar(
                title = "Account",
                isRestaurantsScreen = true,
                onBackNavClicked = {navController.navigateUp()},
                userInfo = userInfo.firstOrNull()
            ) },
        bottomBar = { BottomBar(navController) }
    ) {
        paddingValues->
        if(restaurantList.isEmpty()){
           Text("No restaurants found", modifier=Modifier.padding(paddingValues))
        }else{
            LazyColumn(modifier = Modifier.padding(paddingValues)){
                items(restaurantList){
                    item->
                    Card(
                        modifier = Modifier
                            .fillMaxWidth()
                            .padding(10.dp),
                        colors = CardDefaults.cardColors(
                            containerColor = Color.White
                        ),
                        elevation = CardDefaults.cardElevation(
                            defaultElevation = 5.dp
                        ),
                        shape = RoundedCornerShape(10.dp)
                    ) {
                        Row{
                            Image(
                                painter = rememberImagePainter(item.picture),
                                contentDescription = null,
                                modifier = Modifier
                                    .size(100.dp)
                                    .padding(end = 8.dp)
                            )

                            Column(
                                modifier = Modifier.padding(8.dp)
                            ) {
                                Text(
                                    text = item.name.toString(),
                                    fontSize = 15.sp,
                                    fontWeight = FontWeight.Bold,
                                    modifier = Modifier.padding(bottom = 2.dp),
                                    color = Color.Black
                                )

                                Text(
                                    text = item.distance.toString(),
                                    fontSize = 13.sp,
                                    fontWeight = FontWeight.Light
                                )

                                Row {
                                    Icon(
                                        imageVector = Icons.Default.LocationOn,
                                        contentDescription = null,
                                        tint = Color.Green,
                                        modifier = Modifier.padding(end = 5.dp)
                                    )

                                    Text(
                                        text = item.location.toString(),
                                        fontSize = 11.sp,
                                        fontWeight = FontWeight.ExtraLight
                                    )
                                }

                                Row{
                                    Text(
                                        text = item.rating.toString(),
                                        fontSize = 11.sp,
                                        fontWeight = FontWeight.Bold,
                                        color = Color.Green,
                                        modifier = Modifier.padding(end = 5.dp)
                                    )

                                    Icon(
                                        imageVector = Icons.Default.Star,
                                        contentDescription = null
                                    )
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

And here is the result result Any help is appreciated and thank you in advance!


Solution

  • The issue with your code maybe the restaurantList being updated outside the nested Firestore addOnSuccessListener callbacks, which leads to the list being empty when it's accessed by your Composable function.

    Firestore's data retrieval is asynchronous, so when the restaurantList.value is set, the tempList might not have been fully populated yet. To fix this, you should move the update of the restaurantList inside the nested callback after you've finished adding items to tempList.

    Here's an updated version of your fetchItems method:

    private fun fetchItems() {
        firestore.collection("restaurants")
            .get()
            .addOnSuccessListener { querySnapshot ->
                val tempList = mutableListOf<restaurantInfo>()
    
                val documents = querySnapshot.documents
                val totalDocuments = documents.size
    
                var completedDocuments = 0
    
                documents.forEach { document ->
                    val documentRef = document.reference.collection("details")
                    documentRef
                        .get()
                        .addOnSuccessListener { subDocuments ->
                            subDocuments.forEach { subDocument ->
                                val data = subDocument.data
                                val restaurantItem = restaurantInfo(
                                    distance = data["distance"] as String? ?: "",
                                    location = data["location"] as String? ?: "",
                                    name = data["name"] as String? ?: "",
                                    picture = data["picture"] as String? ?: "",
                                    rating = data["rating"] as String? ?: ""
                                )
                                tempList.add(restaurantItem)
                            }
                            completedDocuments++
                            if (completedDocuments == totalDocuments) {
                                restaurantList.value = tempList.toList()
                            }
                        }
                        .addOnFailureListener { exception ->
                            Log.e("Firestore", "Error getting documents: ", exception)
                        }
                }
            }
            .addOnFailureListener { exception ->
                Log.e("Firestore", "Error getting restaurants", exception)
            }
    }