Movies UI - Lists

Add Actors and Ratings

To flesh out the app more, let's add in Actors and Ratings.

First, to make things simpler for the other lists, we create a ListScaffold and use it in MovieListUi

Now we add the new screens. These new screens are similar to the existing screens, but be careful of where you get the ids from. The MovieDisplayUi, for example, displays a list of RoleWithActorDtos, and when clicked, we need to call it.actor.id to get the proper id. (If you use just it.id here, the fetch will fail when looking up the actor)

When we run this version of the application, we can now drill deeper into the data. Pick a movie, then pick an actor, then pick movies starring that actor and so forth. Navigating back pops each screen off the stack, returning to the previous screen.

Note

We're still using a Star icon for movies. We'll pick a better icon in the final step.

You'll notice that we currently have no means of navigating to the actor and rating list screens. To do this, we'll add tabs at the bottom of the list screens to jump to other lists.

Navigation-wise, we'll treat each of these jumps as restarting navigation, replacing the stack with the destination. This isn't necessary, but I wanted to demonstrate a navigation alternative.

At the bottom of the list scaffold we'll place three buttons which act like tabs.

We define ScreenSelectButton for these tabs.

Each tab takes a Screen as its target, and calls onSelectListScreen() passing that target screen when clicked. We use NavigationBarItem, provided by the Material 3 library, for the tab-like styling.

We place these buttons in a NavigationBar in the bottomBar slot of the Scaffold. Once again, continue using a star as the icon for movies. We'll fix this in the next step.

These buttons require some extra parameters to track which is selected (currentScreen) and what to do when they're clicked (onSelectListScreen). We pass these up to all callers up through Ui.

We set these extra parameters in Ui; for example when calling MovieListUi. Note that this requires a new function in the view model to explicitly set the screen stack.

This creates a nice Ui that allows us to navigate starting from movies, actors or ratings. The screen tabs could be moved into a common base scaffold for all screens to allow instant jumping to a list from any screen (I'll leave that as an "exercise for the interested reader").

Now let's get a better movie icon...

Code Changes

CHANGED: /app/src/main/java/com/androidbyexample/movie/MovieViewModel.kt
package com.androidbyexample.movie

import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.CreationExtras
import com.androidbyexample.movie.repository.MovieDatabaseRepository
import com.androidbyexample.movie.repository.MovieRepository
import com.androidbyexample.movie.screens.MovieList
import com.androidbyexample.movie.screens.Screen
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch

class MovieViewModel(
    private val repository: MovieRepository,
): ViewModel(), MovieRepository by repository {
private val _selectedIdsFlow = MutableStateFlow<Set<String>>(emptySet()) val selectedIdsFlow: Flow<Set<String>> = _selectedIdsFlow.asStateFlow()
fun clearSelectedIds() { _selectedIdsFlow.value = emptySet() } fun toggleSelection(id: String) { if (id in _selectedIdsFlow.value) { _selectedIdsFlow.value -= id } else { _selectedIdsFlow.value += id } }
private var screenStack = listOf<Screen>(MovieList) set(value) { field = value
clearSelectedIds()
currentScreen = value.lastOrNull() }
// NOTE: We're keep this as a Compose State for comparison. // You can use Compose state to expose anything from the view model, // but our example will be using Flow from now on to demonstrate how // the view model can be used without Compose, perhaps for other // platforms such as iOS, desktop, web or command line var currentScreen by mutableStateOf<Screen?>(MovieList) private set
fun pushScreen(screen: Screen) { screenStack = screenStack + screen } fun popScreen() { screenStack = screenStack.dropLast(1) }
fun setScreen(screen: Screen) { screenStack = listOf(screen) }
fun deleteSelectedMovies() { viewModelScope.launch { deleteMoviesById(_selectedIdsFlow.value) _selectedIdsFlow.value = emptySet() } } fun deleteSelectedActors() { viewModelScope.launch { deleteActorsById(_selectedIdsFlow.value) _selectedIdsFlow.value = emptySet() } } fun deleteSelectedRatings() { viewModelScope.launch { deleteRatingsById(_selectedIdsFlow.value) _selectedIdsFlow.value = emptySet() } }
companion object { val Factory: ViewModelProvider.Factory = object: ViewModelProvider.Factory { @Suppress("UNCHECKED_CAST") override fun <T : ViewModel> create( modelClass: Class<T>, extras: CreationExtras ): T { // Get the Application object from extras val application = checkNotNull(extras[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY]) return MovieViewModel( MovieDatabaseRepository.create(application) ) as T } } } }
CHANGED: /app/src/main/java/com/androidbyexample/movie/components/Label.kt
package com.androidbyexample.movie.components

import androidx.annotation.StringRes
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp

@Composable
fun Label(
    @StringRes textId: Int,
) {    Label(text = stringResource(id = textId))}@Composablefun Label(    text: String,) {
    Text(
//      text = stringResource(id = textId),        text = text,        style = MaterialTheme.typography.titleMedium,
        modifier = Modifier
            .padding(8.dp)
            .fillMaxWidth(),
    )
}
ADDED: /app/src/main/java/com/androidbyexample/movie/components/ScreenSelectButton.kt
package com.androidbyexample.movie.componentsimport androidx.annotation.StringResimport androidx.compose.foundation.layout.RowScopeimport androidx.compose.material3.Iconimport androidx.compose.material3.NavigationBarItemimport androidx.compose.material3.Textimport androidx.compose.runtime.Composableimport androidx.compose.ui.graphics.vector.ImageVectorimport androidx.compose.ui.res.stringResourceimport com.androidbyexample.movie.screens.Screen
@Composablefun RowScope.ScreenSelectButton( currentScreen: Screen, targetScreen: Screen, imageVector: ImageVector, @StringRes labelId: Int, onSelectListScreen: (Screen) -> Unit,) = NavigationBarItem( selected = currentScreen == targetScreen, icon = { Icon( imageVector = imageVector, contentDescription = stringResource(id = labelId) ) }, label = { Text(text = stringResource(id = labelId)) }, onClick = { onSelectListScreen(targetScreen) } )
ADDED: /app/src/main/java/com/androidbyexample/movie/screens/ActorDisplayUi.kt
package com.androidbyexample.movie.screensimport androidx.compose.foundation.layout.Columnimport androidx.compose.foundation.layout.paddingimport androidx.compose.material3.ExperimentalMaterial3Apiimport androidx.compose.material3.Scaffoldimport androidx.compose.material3.Textimport androidx.compose.material3.TopAppBarimport androidx.compose.runtime.Composableimport androidx.compose.runtime.LaunchedEffectimport androidx.compose.runtime.getValueimport androidx.compose.runtime.mutableStateOfimport androidx.compose.runtime.rememberimport androidx.compose.runtime.setValueimport androidx.compose.ui.Modifierimport androidx.compose.ui.res.stringResourceimport com.androidbyexample.movie.Rimport com.androidbyexample.movie.components.Displayimport com.androidbyexample.movie.components.Labelimport com.androidbyexample.movie.repository.ActorWithFilmographyDtoimport com.androidbyexample.movie.repository.MovieDtoimport kotlinx.coroutines.Dispatchersimport kotlinx.coroutines.withContext
@OptIn(ExperimentalMaterial3Api::class)@Composablefun ActorDisplayUi( id: String, fetchActor: suspend (String) -> ActorWithFilmographyDto, onMovieClicked: (MovieDto) -> Unit,) { var actorWithFilmography by remember { mutableStateOf<ActorWithFilmographyDto?>(null) } LaunchedEffect(key1 = id) { withContext(Dispatchers.IO) { actorWithFilmography = fetchActor(id) } } Scaffold( topBar = { TopAppBar( title = { Text(text = actorWithFilmography?.actor?.name ?: stringResource(R.string.loading)) } ) } ) { paddingValues -> actorWithFilmography?.let { actorWithFilmography -> Column( modifier = Modifier .padding(paddingValues) ) { Label(textId = R.string.name) Display(text = actorWithFilmography.actor.name) Label( text = stringResource( id = R.string.movies_starring, actorWithFilmography.actor.name ) ) List( items = actorWithFilmography.filmography.sortedBy { it.movie.title }, onItemClicked = { onMovieClicked(it.movie) }, selectedIds = emptySet(), onSelectionToggle = {}, onClearSelections = {}, modifier = Modifier.weight(1f) ) { role -> Display(text = role.movie.title) } } } }}
ADDED: /app/src/main/java/com/androidbyexample/movie/screens/ActorListUi.kt
package com.androidbyexample.movie.screensimport androidx.compose.foundation.clickableimport androidx.compose.foundation.layout.Rowimport androidx.compose.foundation.layout.paddingimport androidx.compose.material.icons.Iconsimport androidx.compose.material.icons.filled.Personimport androidx.compose.material.icons.filled.Starimport androidx.compose.material3.Iconimport androidx.compose.runtime.Composableimport androidx.compose.ui.Alignmentimport androidx.compose.ui.Modifierimport androidx.compose.ui.res.stringResourceimport androidx.compose.ui.unit.dpimport com.androidbyexample.movie.Rimport com.androidbyexample.movie.components.Displayimport com.androidbyexample.movie.repository.ActorDto
@Composablefun ActorListUi( actors: List<ActorDto>, onActorClicked: (ActorDto) -> Unit, selectedIds: Set<String>, onSelectionToggle: (id: String) -> Unit, onClearSelections: () -> Unit, onDeleteSelectedActors: () -> Unit, currentScreen: Screen, onSelectListScreen: (Screen) -> Unit, onResetDatabase: () -> Unit,) { ListScaffold( titleId = R.string.actors, items = actors, onItemClicked = onActorClicked, selectedIds = selectedIds, onSelectionToggle = onSelectionToggle, onClearSelections = onClearSelections, onDeleteSelectedItems = onDeleteSelectedActors, currentScreen = currentScreen, onSelectListScreen = onSelectListScreen, onResetDatabase = onResetDatabase ) { actor -> Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(8.dp) ) { Icon( imageVector = Icons.Default.Person, contentDescription = stringResource(id = R.string.actor), modifier = Modifier.clickable { onSelectionToggle(actor.id) } ) Display(text = actor.name) } }}
ADDED: /app/src/main/java/com/androidbyexample/movie/screens/ListScaffold.kt
package com.androidbyexample.movie.screensimport androidx.annotation.StringResimport androidx.compose.foundation.clickableimport androidx.compose.foundation.layout.ColumnScopeimport androidx.compose.foundation.layout.fillMaxSizeimport androidx.compose.foundation.layout.paddingimport androidx.compose.material.icons.Iconsimport androidx.compose.material.icons.filled.ArrowBackimport androidx.compose.material.icons.filled.Deleteimport androidx.compose.material.icons.filled.Personimport androidx.compose.material.icons.filled.Refreshimport androidx.compose.material.icons.filled.Starimport androidx.compose.material3.ExperimentalMaterial3Apiimport androidx.compose.material3.Iconimport androidx.compose.material3.IconButtonimport androidx.compose.material3.NavigationBarimport androidx.compose.material3.Scaffoldimport androidx.compose.material3.Textimport androidx.compose.material3.TopAppBarimport androidx.compose.runtime.Composableimport androidx.compose.ui.Modifierimport androidx.compose.ui.res.stringResourceimport androidx.compose.ui.unit.dpimport com.androidbyexample.movie.Rimport com.androidbyexample.movie.components.ScreenSelectButtonimport com.androidbyexample.movie.repository.HasId
@OptIn(ExperimentalMaterial3Api::class)@Composablefun <T: HasId> ListScaffold( @StringRes titleId: Int, items: List<T>, onItemClicked: (T) -> Unit, selectedIds: Set<String>, onSelectionToggle: (id: String) -> Unit, onClearSelections: () -> Unit, onDeleteSelectedItems: () -> Unit, currentScreen: Screen, onSelectListScreen: (Screen) -> Unit, onResetDatabase: () -> Unit, itemContent: @Composable ColumnScope.(T) -> Unit,) { Scaffold( topBar = { if (selectedIds.isEmpty()) { TopAppBar( title = { Text(text = stringResource(titleId)) }, actions = { IconButton(onClick = onResetDatabase) { Icon( imageVector = Icons.Default.Refresh, contentDescription = stringResource(R.string.reset_database) ) } } ) } else { TopAppBar( navigationIcon = { Icon( imageVector = Icons.Default.ArrowBack, contentDescription = stringResource(R.string.clear_selections), modifier = Modifier.clickable(onClick = onClearSelections), ) }, title = { Text(text = selectedIds.size.toString(), modifier = Modifier.padding(8.dp)) }, actions = { IconButton(onClick = onDeleteSelectedItems) { Icon( imageVector = Icons.Default.Delete, contentDescription = stringResource(R.string.delete_selected_items) ) } }, ) } },
bottomBar = { NavigationBar { ScreenSelectButton( targetScreen = RatingList, imageVector = Icons.Default.Star, labelId = R.string.ratings, currentScreen = currentScreen, onSelectListScreen = onSelectListScreen ) ScreenSelectButton( targetScreen = MovieList, imageVector = Icons.Default.Star, labelId = R.string.movies, currentScreen = currentScreen, onSelectListScreen = onSelectListScreen ) ScreenSelectButton( targetScreen = ActorList, imageVector = Icons.Default.Person, labelId = R.string.actors, currentScreen = currentScreen, onSelectListScreen = onSelectListScreen ) } },
modifier = Modifier.fillMaxSize() ) { paddingValues -> List( items = items, onItemClicked = onItemClicked, selectedIds = selectedIds, onSelectionToggle = onSelectionToggle, onClearSelections = onClearSelections, modifier = Modifier .padding(paddingValues) .fillMaxSize(), itemContent = itemContent, ) }}
CHANGED: /app/src/main/java/com/androidbyexample/movie/screens/MovieListUi.kt
package com.androidbyexample.movie.screens

import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Row
//import androidx.compose.foundation.layout.fillMaxSizeimport androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
//import androidx.compose.material.icons.filled.ArrowBack//import androidx.compose.material.icons.filled.Delete//import androidx.compose.material.icons.filled.Refreshimport androidx.compose.material.icons.filled.Star
//import androidx.compose.material3.ExperimentalMaterial3Apiimport androidx.compose.material3.Icon
//import androidx.compose.material3.IconButton//import androidx.compose.material3.Scaffold//import androidx.compose.material3.Text//import androidx.compose.material3.TopAppBarimport androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.androidbyexample.movie.R
import com.androidbyexample.movie.components.Display
import com.androidbyexample.movie.repository.MovieDto

//@OptIn(ExperimentalMaterial3Api::class)@Composable
fun MovieListUi(
    movies: List<MovieDto>,
    onMovieClicked: (MovieDto) -> Unit,

    selectedIds: Set<String>,
    onSelectionToggle: (id: String) -> Unit,
    onClearSelections: () -> Unit,
    onDeleteSelectedMovies: () -> Unit,

    currentScreen: Screen,    onSelectListScreen: (Screen) -> Unit,    onResetDatabase: () -> Unit,
) {
// Scaffold(// topBar = {// if (selectedIds.isEmpty()) {// TopAppBar(// title = { Text(text = stringResource(R.string.movies)) },// actions = {// IconButton(onClick = onResetDatabase) {// Icon(// imageVector = Icons.Default.Refresh,// contentDescription = stringResource(R.string.reset_database)// )// }// }// )// } else {// TopAppBar(// navigationIcon = {// Icon(// imageVector = Icons.Default.ArrowBack,// contentDescription = stringResource(R.string.clear_selections),// modifier = Modifier.clickable(onClick = onClearSelections),// )// },// title = { Text(text = selectedIds.size.toString(), modifier = Modifier.padding(8.dp)) },// actions = {// IconButton(onClick = onDeleteSelectedMovies) {// Icon(// imageVector = Icons.Default.Delete,// contentDescription = stringResource(R.string.delete_selected_items)// )// }// },// )// }// },// modifier = Modifier.fillMaxSize()// ) { paddingValues ->// List( ListScaffold( titleId = R.string.movies, items = movies, onItemClicked = onMovieClicked, selectedIds = selectedIds, onSelectionToggle = onSelectionToggle, onClearSelections = onClearSelections, // modifier = Modifier// .padding(paddingValues)// .fillMaxSize() onDeleteSelectedItems = onDeleteSelectedMovies, currentScreen = currentScreen, onSelectListScreen = onSelectListScreen, onResetDatabase = onResetDatabase ) { movie -> Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(8.dp) ) { Icon( imageVector = Icons.Default.Star, contentDescription = stringResource(id = R.string.movie), modifier = Modifier.clickable { onSelectionToggle(movie.id) } ) Display(text = movie.title) } }
// }}
ADDED: /app/src/main/java/com/androidbyexample/movie/screens/RatingDisplayUi.kt
package com.androidbyexample.movie.screensimport androidx.compose.foundation.layout.Columnimport androidx.compose.foundation.layout.paddingimport androidx.compose.material3.ExperimentalMaterial3Apiimport androidx.compose.material3.Scaffoldimport androidx.compose.material3.Textimport androidx.compose.material3.TopAppBarimport androidx.compose.runtime.Composableimport androidx.compose.runtime.LaunchedEffectimport androidx.compose.runtime.getValueimport androidx.compose.runtime.mutableStateOfimport androidx.compose.runtime.rememberimport androidx.compose.runtime.setValueimport androidx.compose.ui.Modifierimport androidx.compose.ui.res.stringResourceimport com.androidbyexample.movie.Rimport com.androidbyexample.movie.components.Displayimport com.androidbyexample.movie.components.Labelimport com.androidbyexample.movie.repository.MovieDtoimport com.androidbyexample.movie.repository.RatingWithMoviesDtoimport kotlinx.coroutines.Dispatchersimport kotlinx.coroutines.withContext
@OptIn(ExperimentalMaterial3Api::class)@Composablefun RatingDisplayUi( id: String, fetchRating: suspend (String) -> RatingWithMoviesDto, onMovieClicked: (MovieDto) -> Unit,) { var ratingWithMovies by remember { mutableStateOf<RatingWithMoviesDto?>(null) } LaunchedEffect(key1 = id) { withContext(Dispatchers.IO) { ratingWithMovies = fetchRating(id) } } Scaffold( topBar = { TopAppBar( title = { Text(text = ratingWithMovies?.rating?.name ?: stringResource(R.string.loading)) } ) } ) { paddingValues -> ratingWithMovies?.let { ratingWithMovies -> Column( modifier = Modifier .padding(paddingValues) ) { Label(textId = R.string.name) Display(text = ratingWithMovies.rating.name) Label(textId = R.string.description) Display(text = ratingWithMovies.rating.description) Label( text = stringResource( id = R.string.movies_rated, ratingWithMovies.rating.name ) ) List( items = ratingWithMovies.movies.sortedBy { it.title }, onItemClicked = onMovieClicked, selectedIds = emptySet(), onSelectionToggle = {}, onClearSelections = {}, modifier = Modifier.weight(1f) ) { movie -> Display( text = movie.title ) } } } }}
ADDED: /app/src/main/java/com/androidbyexample/movie/screens/RatingListUi.kt
package com.androidbyexample.movie.screensimport androidx.compose.foundation.clickableimport androidx.compose.foundation.layout.Rowimport androidx.compose.foundation.layout.paddingimport androidx.compose.material.icons.Iconsimport androidx.compose.material.icons.filled.Starimport androidx.compose.material3.Iconimport androidx.compose.runtime.Composableimport androidx.compose.ui.Alignmentimport androidx.compose.ui.Modifierimport androidx.compose.ui.res.stringResourceimport androidx.compose.ui.unit.dpimport com.androidbyexample.movie.Rimport com.androidbyexample.movie.components.Displayimport com.androidbyexample.movie.repository.RatingDto
@Composablefun RatingListUi( ratings: List<RatingDto>, onRatingClicked: (RatingDto) -> Unit, selectedIds: Set<String>, onSelectionToggle: (id: String) -> Unit, onClearSelections: () -> Unit, onDeleteSelectedRatings: () -> Unit, currentScreen: Screen, onSelectListScreen: (Screen) -> Unit, onResetDatabase: () -> Unit,) { ListScaffold( titleId = R.string.ratings, items = ratings, onItemClicked = onRatingClicked, selectedIds = selectedIds, onSelectionToggle = onSelectionToggle, onClearSelections = onClearSelections, onDeleteSelectedItems = onDeleteSelectedRatings, currentScreen = currentScreen, onSelectListScreen = onSelectListScreen, onResetDatabase = onResetDatabase ) { rating -> Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(8.dp) ) { Icon( imageVector = Icons.Default.Star, contentDescription = stringResource(id = R.string.rating), modifier = Modifier.clickable { onSelectionToggle(rating.id) } ) Display(text = rating.name) } }}
CHANGED: /app/src/main/java/com/androidbyexample/movie/screens/Screens.kt
package com.androidbyexample.movie.screens

sealed interface Screen

object MovieList: Screen object ActorList: Screenobject RatingList: Screendata class MovieDisplay(val id: String): Screen data class ActorDisplay(val id: String): Screendata class RatingDisplay(val id: String): Screen
CHANGED: /app/src/main/java/com/androidbyexample/movie/screens/Ui.kt
package com.androidbyexample.movie.screens

import androidx.activity.compose.BackHandler
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.androidbyexample.movie.MovieViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch

@Composable
fun Ui(
    viewModel: MovieViewModel,
    onExit: () -> Unit,
) {
    BackHandler {
        viewModel.popScreen()
    }

    val scope = rememberCoroutineScope()

    when (val screen = viewModel.currentScreen) {
        null -> onExit()
        is MovieDisplay -> {
            MovieDisplayUi(
                id = screen.id,
                fetchMovie = viewModel::getMovieWithCast,
// onActorClicked = { } onActorClicked = { viewModel.pushScreen(ActorDisplay(it.actor.id)) }
) }
is ActorDisplay -> { ActorDisplayUi( id = screen.id, fetchActor = viewModel::getActorWithFilmography, onMovieClicked = { viewModel.pushScreen(MovieDisplay(it.id)) } ) } is RatingDisplay -> { RatingDisplayUi( id = screen.id, fetchRating = viewModel::getRatingWithMovies, onMovieClicked = { viewModel.pushScreen(MovieDisplay(it.id)) } ) }
MovieList -> { val movies by viewModel.moviesFlow.collectAsStateWithLifecycle(initialValue = emptyList())
val selectedIds by viewModel.selectedIdsFlow.collectAsStateWithLifecycle(initialValue = emptySet())
MovieListUi( movies = movies, onMovieClicked = { movie -> viewModel.pushScreen(MovieDisplay(movie.id)) },
selectedIds = selectedIds, onClearSelections = viewModel::clearSelectedIds, onSelectionToggle = viewModel::toggleSelection,
onDeleteSelectedMovies = viewModel::deleteSelectedMovies,
currentScreen = screen, onSelectListScreen = viewModel::setScreen,
onResetDatabase = { scope.launch(Dispatchers.IO) { viewModel.resetDatabase() } } ) }
ActorList -> { val actors by viewModel.actorsFlow.collectAsStateWithLifecycle(initialValue = emptyList()) val selectedIds by viewModel.selectedIdsFlow.collectAsStateWithLifecycle(initialValue = emptySet()) ActorListUi( actors = actors, onActorClicked = { actor -> viewModel.pushScreen(ActorDisplay(actor.id)) }, selectedIds = selectedIds, onClearSelections = viewModel::clearSelectedIds, onSelectionToggle = viewModel::toggleSelection, onDeleteSelectedActors = viewModel::deleteSelectedActors, currentScreen = screen, onSelectListScreen = viewModel::setScreen, onResetDatabase = { scope.launch(Dispatchers.IO) { viewModel.resetDatabase() } } ) } RatingList -> { val ratings by viewModel.ratingsFlow.collectAsStateWithLifecycle(initialValue = emptyList()) val selectedIds by viewModel.selectedIdsFlow.collectAsStateWithLifecycle(initialValue = emptySet()) RatingListUi( ratings = ratings, onRatingClicked = { rating -> viewModel.pushScreen(RatingDisplay(rating.id)) }, selectedIds = selectedIds, onClearSelections = viewModel::clearSelectedIds, onSelectionToggle = viewModel::toggleSelection, onDeleteSelectedRatings = viewModel::deleteSelectedRatings, currentScreen = screen, onSelectListScreen = viewModel::setScreen, onResetDatabase = { scope.launch(Dispatchers.IO) { viewModel.resetDatabase() } } ) }
} }
CHANGED: /app/src/main/res/values/strings.xml
<resources>
    <string name="app_name">MovieUi2</string>
    <string name="movies">Movies</string>
    <string name="movie">Movie</string>
    <string name="title">Title</string>
    <string name="description">Description</string>
    <string name="reset_database">Reset Database</string>
    <string name="loading">(loading)</string>
    <string name="cast">Cast</string>
    <string name="cast_entry">%1$s: %2$s</string>
<string name="clear_selections">Clear Selections</string> <string name="delete_selected_items">Delete selected items</string>
<string name="ratings">Ratings</string> <string name="rating">Rating</string> <string name="actors">Actors</string> <string name="actor">Actor</string> <string name="name">Name</string> <string name="movies_rated">Movies rated %1$s</string> <string name="movies_starring">Movies starring %1$s</string>
</resources>
CHANGED: /repository/src/main/java/com/androidbyexample/movie/repository/ActorDto.kt
package com.androidbyexample.movie.repository

import com.androidbyexample.movie.data.ActorEntity
import com.androidbyexample.movie.data.ActorWithFilmography
import com.androidbyexample.movie.data.RoleWithMovie

data class ActorDto( override val id: String, val name: String, ): HasId
internal fun ActorEntity.toDto() = ActorDto(id = id, name = name) internal fun ActorDto.toEntity() = ActorEntity(id = id, name = name) data class ActorWithFilmographyDto( val actor: ActorDto, val filmography: List<RoleWithMovieDto>, ) data class RoleWithMovieDto( val movie: MovieDto, val character: String, val orderInCredits: Int, //)): HasId { override val id: String get() = "${movie.id}:$character"} internal fun RoleWithMovie.toDto() = RoleWithMovieDto( movie = movie.toDto(), character = role.character, orderInCredits = role.orderInCredits, ) internal fun ActorWithFilmography.toDto() = ActorWithFilmographyDto( actor = actor.toDto(), filmography = rolesWithMovies.map { it.toDto() } )