I have a ViewModel that accesses the database (Firebase) and counts documents. The structure of the database is this: Collection "Posts" –> Document –> Collection "Likes" –> Documents.
So I access the collection "Posts" and then for each document that contains my username, I access the collection "Likes" and get the snapshot size to count how many likes are there. The problem is that when a new Like document is created the ViewModel won’t get the updates until the app is restarted.
The way I am using the ViewModel:
I have a navigation graph and that’s where I create an instance of the ViewModel, then I call the function that retrieves the data and then I pass this instance as an argument in the other screen where I need the data. In the other screen, I access the variable where the data is stored. The variable where data is stored in a StateFlow (because I pass here the counter value not the documents)
Code (ViewModel):
class PointsViewModel : ViewModel() {
private val firebase = Firebase.firestore
private var _points = MutableStateFlow(0)
val points: StateFlow<Int> = _points
fun calculate(username: String){
firebase.collection("Posts")
.whereEqualTo("PostUser",username)
.get()
.addOnSuccessListener { posts ->
for(post in posts){
post.reference.collection("Likes").get().addOnSuccessListener { likes ->
_points.value += likes.size()
}
}
}
}
}
Code (Where I create instance in NavGraph):
val pointsViewModel: PointsViewModel = viewModel()
//sharedViewModel contains the username
sharedViewModel.user?.username?.let { pointsViewModel.calculate(it) }
Code (Where I need the data):
fun AwardsScreen(sharedViewModel: SharedViewModel,pointsViewModel: PointsViewModel){
val points by pointsViewModel.points.collectAsState()
Column(modifier = Modifier
.fillMaxWidth()
.padding(15.dp, 15.dp)) {
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
Text(text = points.toString())
}
}
I tried to do all these operations inside the screen I need the data. So I tried to access the database and retrieve all data from there, adding a snapshotListener to get the updates but it didn’t work
2
Answers
First, just a small nitpick, the
_points
can beval
and actually should be since you do not want to change the actual instance, just its value.The other thing I noticed is that you are using the
+=
operand which might be the culprit here. Try assigning a new value like_points.value = _points.value + likes.size()
.The docs say
It might need a completely new value without referencing itself in the assignment.
And in the end, I would recommend using a state holder. So you would have a class like this:
And you would use it in the
viewModel
like so:And finally, to observe it in a composable:
This is not how you should do when it comes to counters in Firestore. When you count documents as you do now, it will cost you one read for each document your query returns, and this can be expensive when it comes to a larger number of likes. Unfortunately, count() doesn’t provide real-time updates. However, it will be less expensive if you create and maintain a counter yourself. That can simply be a field of type number that exists in a document. This means that each time a new document is added to a collection you can simply increment the counter, and decrement it when a document is deleted. On this document, you can attach a real-time listener and you’ll have a counter that is always up to date. This means that in order to display the number of likes, you’ll only need to pay for a single read.
Such operations should not be performed in the client-side code but in a trusted environment. So I recommend you write a function in Cloud Functions for Firebase that does that automatically for you.