I want to show a movie list from Firebase realtime database, but I'm not getting the list from the ViewModel.
When I update the apiResource.data
in the Repository, it is not updating the mutableList on the screen.
Screen:
val movies = viewModel.data.value.data?.toMutableList()
if (viewModel.data.value.loading == true) {
CircularProgressIndicator()
} else {
movies?.forEach {
Log.d("Movies", it.name) // I'm not getting the movie list on the screen
}
}
ViewModel:
val data: MutableState<ApiResource<List<Movie>, Boolean, Exception>> = mutableStateOf(
ApiResource(null, true, Exception(""))
)
private fun getMovies() {
viewModelScope.launch {
data.value.loading = true
data.value = repository.getAllMovies()
if (data.value.data.toString() != "[]") {
data.value.loading = false
}
}
}
Repository:
class Repository {
private val apiResource = ApiResource<List<Movie>,Boolean, Exception>()
suspend fun getAllMovies(): ApiResource<List<Movie>, Boolean, java.lang.Exception> {
try {
apiResource.loading = true
var movies: List<Movie>
Firebase.database.reference.child("movies").addValueEventListener(object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
movies = dataSnapshot.children.map { snapshot ->
snapshot.getValue(Movie::class.java)!!
}
apiResource.data = movies // set data in apiResource
if (apiResource.data.toString() != "[]") {
apiResource.loading = false
}
}
override fun onCancelled(error: DatabaseError) {}
})
} catch (exception: java.lang.Exception) {
apiResource.e = exception
}
return apiResource
}
}
Compose cannot track changes of an object contained by a mutable state. It can only track updates to the value of the mutable state.
Possible solutions:
Updating your mutable state by creating a copy of the object with the updated properties. Data class is extremely handy in this case:
data class ApiResource(val isLoading: Boolean = false)
class VM : ViewModel() {
var apiResource by mutableStateOf(ApiResource())
fun update() {
apiResource = apiResource.copy(isLoading = true)
}
}
Making the necessary properties of ApiResource
mutable states.
class ApiResource {
var isLoading by mutableStateOf(false)
}
class VM: ViewModel() {
val apiResource = ApiResource()
fun update() {
apiResource.isLoading = true
}
}
The second problem with your code, is that you're messing coroutines with asynchronous code. addValueEventListener
is not gonna wait until onDataChange
is called, so getAllMovies
always returns an empty object.
To make a coroutine wait until an async call finishes, you need to use suspendCoroutine
.
Also, listener provided to addValueEventListener
can be called multiple times if result changes. Your current code doesn't expect that, I suggest you using addListenerForSingleValueEvent
instead.
val movies = suspendCoroutine<List<Movie>> { continuation ->
Firebase.database.reference.child("movies").addListenerForSingleValueEvent(object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
continuation(
dataSnapshot.children.map { snapshot ->
snapshot.getValue(Movie::class.java)!!
}
)
}
override fun onCancelled(error: DatabaseError) {}
})
}