Movies UI - Lists
Selections in the UI
Let's integrate selections into the UI.
First, we add parameters to our MovieListUi
composable function. These parameters give the function
selectedIds
- the set of selections so we know what to highlightonSelectionToggle
- allows the function to request to toggle a selectiononClearSelections
- allows the function to request all selections be cleared
We choose the color of each card based on its
selection status. (Note that contentColorFor
will only work if the color passed in is defined in the theme. In this case, we're using secondary
and surface
colors from the theme so it'll work.)
We tell the Card
which
colors to use for its background, containerColor
and forground, contentColor
. The contentColor will be used for any nested text or icons.
We need to change the way clicks are handled to match our strategy. We'll do this for the
Icon
- any clicks toggle the selectionCard
- any long-clicks toggle the selection
- any normal clicks
- toggle the selection (if anything was selected), or
- navigate to the movie (if nothing was selected)
We currently have an onClick
defined on the Card
, but because we want to handle both long and normal clicks, we need to switch to a
combinedClickable
modifier.
We add a clickable
modifier to the Icon
to finish our click handling.
Finally, we
collect the selection from the view model and
wire up
the new parameters in the Ui
function.
This gives us a movie list that allows us to select movies!
Code Changes
CHANGED: /app/src/main/java/com/androidbyexample/movie/screens/MovieListUi.kt
package com.androidbyexample.movie.screens import androidx.compose.foundation.ExperimentalFoundationApiimport androidx.compose.foundation.clickableimport androidx.compose.foundation.combinedClickableimport androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Refresh import androidx.compose.material.icons.filled.Star import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialThemeimport androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.material3.contentColorForimport 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)@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)@Composable fun MovieListUi( movies: List<MovieDto>, onMovieClicked: (MovieDto) -> Unit,selectedIds: Set<String>, onSelectionToggle: (id: String) -> Unit, onClearSelections: () -> Unit,onResetDatabase: () -> Unit, ) { Scaffold( topBar = { TopAppBar( title = { Text(text = stringResource(R.string.movies)) }, actions = { IconButton(onClick = onResetDatabase) { Icon( imageVector = Icons.Default.Refresh, contentDescription = stringResource(R.string.reset_database) ) } } ) }, modifier = Modifier.fillMaxSize() ) { paddingValues ->LazyColumn( modifier = Modifier .padding(paddingValues) .fillMaxSize() ) { items( items = movies, key = { it.id }, ) { movie ->} }val containerColor = if (movie.id in selectedIds) { MaterialTheme.colorScheme.secondary } else { MaterialTheme.colorScheme.surface } val contentColor = MaterialTheme.colorScheme.contentColorFor(containerColor)Card( elevation = CardDefaults.cardElevation( defaultElevation = 8.dp, ),colors = CardDefaults.cardColors( containerColor = containerColor, contentColor = contentColor, ),modifier = Modifier .padding(8.dp).combinedClickable( onClick = { if (selectedIds.isEmpty()) { onMovieClicked(movie) } else { onSelectionToggle(movie.id) } },) { Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(8.dp) ) { Icon( imageVector = Icons.Default.Star,// modifier = Modifier.padding(8.dp)onLongClick = { onSelectionToggle(movie.id) }, )// contentDescription = stringResource(id = R.string.movie)contentDescription = stringResource(id = R.string.movie),modifier = Modifier.clickable { onSelectionToggle(movie.id) }) Display(text = movie.title) } } } }
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, ) } 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,onResetDatabase = { scope.launch(Dispatchers.IO) { viewModel.resetDatabase() } } ) } } }