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 highlight
  • onSelectionToggle - allows the function to request to toggle a selection
  • onClearSelections - 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 selection
  • Card
    • 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!

Selectable 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) } }, // modifier = Modifier.padding(8.dp) onLongClick = { onSelectionToggle(movie.id) }, )
) { Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(8.dp) ) { Icon( imageVector = Icons.Default.Star, // 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() } } ) } } }