Concurrency with Kotlin Flow

Jun 5 2024 · Kotlin 1.9.20, Android 14, Android Studio Iguana

Lesson 03: Leverage Flow Operators

Transforming Operators Demo

Episode complete

Play next episode

Next
Transcript

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.

See forum comments
Cinema mode Download course materials from Github
Previous: Transforming Operators Next: Filtering Operators