In this demo, you’ll use what you learned in the previous lesson. Start Android Studio, and open the 03-leverage-flow-operators/Starter folder. Make sure you don’t continue on the project from the last lesson because the starter project has some new code for this lesson.
In the previous lesson, you saw a few examples of how to use map and transform operators. You’ll look at them first.
Open HomeViewModel.kt. Right now, you expose two flows for your ViewModel: categories and moviesByCategories. Take a closer look, and you’ll notice that you use MovieCategory and Movie types for those flows. Usually, it’s a
good practice to use types specifically related to the UI when communicating with the ViewModel. In this case, we’ll
call those types view states.
Replace those flows with the following:
private val _categories: MutableStateFlow<List<MovieCategoryViewState>> = MutableStateFlow(emptyList())
val categories: Flow<List<MovieCategoryViewState>> = _categories
private val _moviesByCategories: MutableStateFlow<Map<String, List<MovieViewState>>> = MutableStateFlow(emptyMap())
val moviesByCategories: Flow<Map<String, List<MovieViewState>>> = _moviesByCategories
If you do that, the fetchMoviesByCategories() and fetchFavoriteCategories() will show errors because types don’t
match. But here come the map and transform operators.
Update fetchFavoriteCategories() like this:
private fun fetchFavoriteCategories() {
viewModelScope.launch {
movieRepository.fetchFavoriteCategories()
.map { favoriteCategories ->
favoriteCategories.map { MovieCategoryViewState(it.id, it.name) }
}
.collect { _categories.emit(it) }
}
}
As stated in the previous lesson, you use map to transform items. Here, you used it to transform MovieCategory
objects to MovieCategoryViewState objects.
Next, suppose you want to transform your movies to view states, but in that process, you want to fetch a
description and an image for a movie. Update fetchMoviesByCategories() like this:
private fun fetchMoviesByCategories() {
viewModelScope.launch {
movieRepository.fetchMoviesByCategory()
.transform { moviesByCategories ->
val movieViewStates: Map<String, List<MovieViewState>> = moviesByCategories.mapValues { (category, movies) ->
movies.map { movie ->
val description = movieRepository.fetchMovieDescriptionsSuspending(movieId = movie.id)
val imageRes = movieRepository.fetchMovieImage(movieId = movie.id)
MovieViewState(
id = movie.id,
title = movie.title,
description = description,
imageRes = imageRes
)
}
}
emit(movieViewStates)
}
.collect {
_moviesByCategories.emit(it)
}
}
}
The transform operator gives you full control over manipulating the data in the stream. In this case, you
transformed a movie map to the list of MovieViewState objects. During that transformation, you used
fetchMovieDescriptionsSuspending() and fetchMovieImage() from the MovieRepository to get the additional data.
Next, open MovieRepository.kt and add a missing method:
fun fetchMovieDescriptionsSuspending(movieId: String): String {
return movieDatabase.getMovieDescription(movieId = movieId)
}
The UI layer has already been updated for you to work with the view states. But, to complete the demo, open
HomeScreen.kt, and edit the RootContainer() invocation like this:
RootContainer(
categories = categories, // here
moviesByCategories = moviesByCategories, // here
onRatingsClicked = onRatingsClicked,
onCategoryClicked = onCategoryClicked,
onMovieClicked = onMovieClicked
)
Excellent! Run the app again; you’ll see it behaves the same way as before, but your ViewModel and UI are now communicating through view state objects.
That ends this demo. Continue with the next lesson, where you’ll learn more about filtering operators.