Movies UI - Lists

Contextual Top Bar

Now that we have selections, we can set up a contextual Top Bar to display the number of selections and an action to delete the selected items.

Currently, we have static top bar that displays the page title and an action to reset the database. We can make this contextual based on whether we have selections.

We need a new parameter to delete the selected movies when the trash can icon on the new top bar is clicked. When called, we'll pass the set of selected ids.

show in full file app/src/main/java/com/androidbyexample/movies/screens/MovieListUi.kt
// ...

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MovieListUi(
    // ...
    modifier: Modifier = Modifier,
    onMovieClicked: (MovieDto) -> Unit,
    onDeleteSelectedMovies: (Set<String>) -> Unit,
    onResetDatabase: () -> Unit,
) {
    // ...
    Scaffold(
        topBar = {
            // ...
                actionIconContentColor = MaterialTheme.colorScheme.onSecondary
            )
            if (selectedIds.isEmpty()) {
                TopAppBar(
                    colors = colors,
                    title = {
                        Text(text = stringResource(R.string.movies))
                    },
                    actions = {
                        IconButton(onClick = onResetDatabase) {
                            Icon(
                                painter = painterResource(R.drawable.refresh_24),
                                contentDescription = stringResource(R.string.reset_database),
                            )
                        }
                    }
                )
            } else {
                TopAppBar(
                    colors = colors,
                    navigationIcon = {
                        Icon(
                            painter = painterResource(R.drawable.arrow_back_24),
                            contentDescription = stringResource(R.string.clear_selections),
                            modifier = Modifier.clickable(onClick = ::clearSelectedIds),
                        )
                    },
                    title = {
                        Text(
                            text = selectedIds.size.toString(),
                            modifier = Modifier.padding(8.dp)
                        )
                    },
                    actions = {
                        IconButton(
                            onClick = {
                                onDeleteSelectedMovies(selectedIds.toSet())
                                // NOTE - this is a mutable set, and we need to be sure we just
                                //        pass a read-only copy out of here. If we don't, the
                                //        caller could modify the contents AND we set up a race
                                //        condition between the clear() call below and the read
                                //        of the data in the caller.
                                selectedIds.clear()
                            }
                        ) {
                            Icon(
                                painter = painterResource(R.drawable.delete_24),
                                contentDescription = stringResource(R.string.delete_selected_items)
                            )
                        }
                    },
                )
            }
        },
        modifier = modifier,
    ) { innerPadding ->
        // ...
    }
}

This requires some additional strings

show in full file app/src/main/res/values/strings.xml
<resources>
    // ...
    <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>
</resources>

When there are items selected, the contextual top bar displays the number of selected items, a back arrow to clear the selections (returning to the normal top bar), and a trash can icon to delete selected items.

The icons come from Google Fonts - Icons as before.

Contextual top bar

We added code to clear selections when the back arrow is pressed, but what if the user performs a back gesture (or presses the back button)? We can handle back using a BackHandler (surprise, surprise), calling clearSelectedIds().

show in full file app/src/main/java/com/androidbyexample/movies/screens/MovieListUi.kt
// ...

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MovieListUi(
    // ...
) {
    // ...
    }

    if (selectedIds.isNotEmpty()) {
        BackHandler {
            clearSelectedIds()
        }
    }

    Scaffold(
        // ...
}

This back handler is only set up if there are any selected ids, and will override the back handler set up in Ui.

Now we need to add "delete by ids" support.

Add delete by id support to the Dao

show in full file data/src/main/java/com/androidbyexample/movies/data/MovieDao.kt
// ...

@Dao
abstract class MovieDao {
    // ...
    abstract suspend fun insert(vararg roles: RoleEntity)

    @Query("DELETE FROM MovieEntity WHERE id IN (:ids)")
    abstract suspend fun deleteMoviesById(ids: Set<String>)
    @Query("DELETE FROM ActorEntity WHERE id IN (:ids)")
    abstract suspend fun deleteActorsById(ids: Set<String>)
    @Query("DELETE FROM RatingEntity WHERE id IN (:ids)")
    abstract suspend fun deleteRatingsById(ids: Set<String>)

    @Query("DELETE FROM MovieEntity")
    // ...
}

Expose these in MovieRepository

show in full file repository/src/main/java/com/androidbyexample/movies/repository/MovieRepository.kt
// ...
interface MovieRepository {
    // ...
    suspend fun insert(rating: RatingDto)

    suspend fun deleteMoviesById(ids: Set<String>)
    suspend fun deleteActorsById(ids: Set<String>)
    suspend fun deleteRatingsById(ids: Set<String>)

    suspend fun resetDatabase()
}

Pass them through in the MovieDatabaseRepository

show in full file repository/src/main/java/com/androidbyexample/movies/repository/MovieDatabaseRepository.kt
// ...
class MovieDatabaseRepository(
    // ...
): MovieRepository {
    // ...
    override suspend fun insert(rating: RatingDto) = dao.insert(rating.toEntity())

    override suspend fun deleteMoviesById(ids: Set<String>) = dao.deleteMoviesById(ids)
    override suspend fun deleteActorsById(ids: Set<String>) = dao.deleteActorsById(ids)
    override suspend fun deleteRatingsById(ids: Set<String>) = dao.deleteRatingsById(ids)

    override suspend fun resetDatabase() = dao.resetDatabase()
    // ...
}

They'll automatically be passed through the view model (because we delegated MovieRepository).

Add deleteSelectedXXX functions for movies, actors and ratings to the view model.

Note

The deleteSelectedXXX functions in the view model launch a coroutine using the viewModelScope. This coroutine scope lives as long as the view model lives. If we used rememberCoroutineScope in MovieListUi, any coroutines launched would be canceled when the user leaves the screen.

show in full file app/src/main/java/com/androidbyexample/movies/MovieViewModel.kt
// ...
class MovieViewModel(
    // ...
): ViewModel(), MovieRepository by repository {
    // ...
    }

    fun deleteSelectedMovies(ids: Set<String>) {
        viewModelScope.launch {
            deleteMoviesById(ids)
        }
    }
    fun deleteSelectedActors(ids: Set<String>) {
        viewModelScope.launch {
            deleteActorsById(ids)
        }
    }
    fun deleteSelectedRatings(ids: Set<String>) {
        viewModelScope.launch {
            deleteRatingsById(ids)
        }
    }

    companion object {
        // ...
}

Pass deleteSelectedMovies() as the function to use for onDeleteSelectedMovies

show in full file app/src/main/java/com/androidbyexample/movies/screens/Ui.kt
// ...

@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Composable
fun Ui(
    // ...
) {
    // ...
    NavDisplay(
        // ...
        entryProvider = entryProvider {
            entry<MovieList>(
                // ...
            ) {
                // ...
                MovieListUi(
                    // ...
                        viewModel.pushScreen(MovieDisplay(movie.id))
                    },
                    onDeleteSelectedMovies = { ids ->
                        viewModel.deleteSelectedMovies(ids)
                    },
                    onResetDatabase = viewModel::doResetDatabase,
                )
            }
            // ...
        }
    )
}

Phew - that's a long chain of deletes coming from the data layer! You shouldn't have to uninstall the app or increase the database version, as we only modified the DAO.

When we run the application, we can now select movies and delete them using the trash can button. The list is automatically updated because we used a Flow as the return type for the getting the movie list. When the database has been changed, the getMoviesFlow query is re-executed, and the results emitted to the same Flow. The Ui collects the Flow into Compose state. Compose detects the state change and recomposes the UI. Poof!


All code changes

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

import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.createSavedStateHandle
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import com.androidbyexample.movies.repository.MovieDatabaseRepository
import com.androidbyexample.movies.repository.MovieRepository
import com.androidbyexample.movies.screens.MovieList
import com.androidbyexample.movies.screens.Screen
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch

class MovieViewModel(
    private val savedStateHandle: SavedStateHandle,
    private val repository: MovieRepository,
): ViewModel(), MovieRepository by repository {

    val backStackFlow = savedStateHandle.getMutableStateFlow<List<Screen>>(
        key = "back_stack",
        initialValue = listOf(MovieList)
    )

    fun pushScreen(screen: Screen) {
        backStackFlow.value += screen
    }

    fun popScreen() {
        backStackFlow.value = backStackFlow.value.dropLast(1)
    }

    fun doResetDatabase() {
        viewModelScope.launch(Dispatchers.IO) {
            repository.resetDatabase()
        }
    }

fun deleteSelectedMovies(ids: Set<String>) { viewModelScope.launch { deleteMoviesById(ids) } } fun deleteSelectedActors(ids: Set<String>) { viewModelScope.launch { deleteActorsById(ids) } } fun deleteSelectedRatings(ids: Set<String>) { viewModelScope.launch { deleteRatingsById(ids) } }
companion object { val Factory = viewModelFactory { initializer { val application = checkNotNull(this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY]) val savedStateHandle = this.createSavedStateHandle() MovieViewModel( savedStateHandle = savedStateHandle, repository = MovieDatabaseRepository.create(application) ) } } } }
CHANGED: app/src/main/java/com/androidbyexample/movies/screens/MovieListUi.kt
package com.androidbyexample.movies.screens

import androidx.activity.compose.BackHandler
import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.clickable
import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
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.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.contentColorFor
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateSetOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.androidbyexample.movies.R
import com.androidbyexample.movies.helper.Display
import com.androidbyexample.movies.repository.MovieDto

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun MovieListUi(
    movies: List<MovieDto>,
    modifier: Modifier = Modifier,
    onMovieClicked: (MovieDto) -> Unit,
onDeleteSelectedMovies: (Set<String>) -> Unit,
onResetDatabase: () -> Unit, ) {
val selectedIds = rememberSaveable { mutableStateSetOf<String>()} fun onSelectionToggle(id: String) { if (id in selectedIds) { selectedIds -= id } else { selectedIds += id } } fun clearSelectedIds() { selectedIds.clear() }
if (selectedIds.isNotEmpty()) { BackHandler { clearSelectedIds() } }
Scaffold( topBar = { val colors = TopAppBarDefaults.topAppBarColors( containerColor = MaterialTheme.colorScheme.primary, scrolledContainerColor = MaterialTheme.colorScheme.surfaceContainer, titleContentColor = MaterialTheme.colorScheme.onPrimary, navigationIconContentColor = MaterialTheme.colorScheme.onPrimary, actionIconContentColor = MaterialTheme.colorScheme.onSecondary )
if (selectedIds.isEmpty()) { TopAppBar( colors = colors, title = { Text(text = stringResource(R.string.movies)) }, actions = { IconButton(onClick = onResetDatabase) { Icon( painter = painterResource(R.drawable.refresh_24), contentDescription = stringResource(R.string.reset_database), ) } } ) } else { TopAppBar( colors = colors, navigationIcon = { Icon( painter = painterResource(R.drawable.arrow_back_24), contentDescription = stringResource(R.string.clear_selections), modifier = Modifier.clickable(onClick = ::clearSelectedIds), ) }, title = { Text( text = selectedIds.size.toString(), modifier = Modifier.padding(8.dp) ) }, actions = { IconButton( onClick = { onDeleteSelectedMovies(selectedIds.toSet()) // NOTE - this is a mutable set, and we need to be sure we just // pass a read-only copy out of here. If we don't, the // caller could modify the contents AND we set up a race // condition between the clear() call below and the read // of the data in the caller. selectedIds.clear() } ) { Icon( painter = painterResource(R.drawable.delete_24), contentDescription = stringResource(R.string.delete_selected_items) ) } }, ) }
}, modifier = modifier, ) { innerPadding ->
LazyColumn( modifier = modifier .padding(innerPadding) ) { items( items = movies, key = { it.id } ) { movie ->
val containerColor = if (movie.id in selectedIds) { MaterialTheme.colorScheme.primaryContainer } else { MaterialTheme.colorScheme.surface } val contentColor = MaterialTheme .colorScheme .contentColorFor(containerColor)
Card( elevation = CardDefaults.cardElevation( defaultElevation = 8.dp, ),
colors = CardDefaults.cardColors( containerColor = containerColor, contentColor = contentColor, ), border = BorderStroke(1.dp, MaterialTheme.colorScheme.outlineVariant),
modifier = Modifier .padding((8.dp)) .combinedClickable( onClick = { if (selectedIds.isEmpty()) { onMovieClicked(movie) } else { onSelectionToggle(movie.id) } }, onLongClick = { onSelectionToggle(movie.id) }, ) ) {
Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(8.dp), ) { Icon( painter = painterResource(R.drawable.movie_24), contentDescription = stringResource(R.string.movie),
modifier = Modifier.clickable { onSelectionToggle(movie.id) }
) Display(text = movie.title) } } } }
} }
CHANGED: app/src/main/java/com/androidbyexample/movies/screens/Ui.kt
package com.androidbyexample.movies.screens

import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
import androidx.compose.material3.adaptive.navigation3.ListDetailSceneStrategy
import androidx.compose.material3.adaptive.navigation3.rememberListDetailSceneStrategy
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation3.runtime.entryProvider
import androidx.navigation3.ui.NavDisplay
import com.androidbyexample.movies.MovieViewModel

@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Composable
fun Ui(
    viewModel: MovieViewModel,
) {
    val listDetailStrategy = rememberListDetailSceneStrategy<Screen>()

    val backStack by viewModel.backStackFlow.collectAsStateWithLifecycle(listOf(MovieList))

    NavDisplay(
        backStack = backStack,
        onBack = viewModel::popScreen,
        sceneStrategies = listOf(listDetailStrategy),
        entryProvider = entryProvider {
            entry<MovieList>(
                metadata = ListDetailSceneStrategy.listPane()
            ) {
                val movies by viewModel.moviesFlow.collectAsStateWithLifecycle(
                    initialValue = emptyList()
                )

                MovieListUi(
                    movies = movies,
                    onMovieClicked = { movie ->
                        viewModel.pushScreen(MovieDisplay(movie.id))
                    },
onDeleteSelectedMovies = { ids -> viewModel.deleteSelectedMovies(ids) },
onResetDatabase = viewModel::doResetDatabase, ) } entry<MovieDisplay>( metadata = ListDetailSceneStrategy.detailPane() ) { key -> MovieDisplayUi( id = key.id, fetchMovie = viewModel::getMovieWithCast, ) } } ) }
ADDED: app/src/main/res/drawable/arrow_back_24.xml
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportWidth="960"
    android:viewportHeight="960">
  <path
      android:pathData="m313,520 l224,224 -57,56 -320,-320 320,-320 57,56 -224,224h487v80L313,520Z"
      android:fillColor="#e3e3e3"/>
</vector>
ADDED: app/src/main/res/drawable/delete_24.xml
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportWidth="960"
    android:viewportHeight="960">
  <path
      android:pathData="M280,840q-33,0 -56.5,-23.5T200,760v-520h-40v-80h200v-40h240v40h200v80h-40v520q0,33 -23.5,56.5T680,840L280,840ZM680,240L280,240v520h400v-520ZM360,680h80v-360h-80v360ZM520,680h80v-360h-80v360ZM280,240v520,-520Z"
      android:fillColor="#e3e3e3"/>
</vector>
CHANGED: app/src/main/res/values/strings.xml
<resources>
//  <string name="app_name">Movies</string>
    <string name="app_name">movies</string>
    <string name="movies">Movies</string>
//  <string name="title">title</string>
    <string name="title">Title</string>
    <string name="description">Description</string>
    <string name="movie">Movie</string>
    <string name="reset_database">Reset database</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>
</resources>
CHANGED: data/src/main/java/com/androidbyexample/movies/data/MovieDao.kt
package com.androidbyexample.movies.data

import androidx.room.Dao
import androidx.room.Insert
import androidx.room.Query
import androidx.room.Transaction
import kotlinx.coroutines.flow.Flow

@Dao
abstract class MovieDao {
    @Query("SELECT * FROM RatingEntity")
    abstract fun getRatingsFlow(): Flow<List<RatingEntity>>
    @Query("SELECT * FROM MovieEntity")
    abstract fun getMoviesFlow(): Flow<List<MovieEntity>>
    @Query("SELECT * FROM ActorEntity")
    abstract fun getActorsFlow(): Flow<List<ActorEntity>>

    @Transaction
    @Query("SELECT * FROM RatingEntity WHERE id = :id")
    abstract suspend fun getRatingWithMovies(id: String): RatingWithMovies
    @Transaction
    @Query("SELECT * FROM ActorEntity WHERE id = :id")
    abstract suspend fun getActorWithFilmography(id: String): ActorWithFilmography
    @Transaction
    @Query("SELECT * FROM MovieEntity WHERE id = :id")
    abstract suspend fun getMovieWithCast(id: String): MovieWithCast

    @Insert
    abstract suspend fun insert(vararg ratings: RatingEntity)
    @Insert
    abstract suspend fun insert(vararg movies: MovieEntity)
    @Insert
    abstract suspend fun insert(vararg actors: ActorEntity)
    @Insert
    abstract suspend fun insert(vararg roles: RoleEntity)

@Query("DELETE FROM MovieEntity WHERE id IN (:ids)") abstract suspend fun deleteMoviesById(ids: Set<String>) @Query("DELETE FROM ActorEntity WHERE id IN (:ids)") abstract suspend fun deleteActorsById(ids: Set<String>) @Query("DELETE FROM RatingEntity WHERE id IN (:ids)") abstract suspend fun deleteRatingsById(ids: Set<String>)
@Query("DELETE FROM MovieEntity") abstract suspend fun clearMovies() @Query("DELETE FROM ActorEntity") abstract suspend fun clearActors() @Query("DELETE FROM RatingEntity") abstract suspend fun clearRatings() @Query("DELETE FROM RoleEntity") abstract suspend fun clearRoles() @Transaction open suspend fun resetDatabase() { clearMovies() clearActors() clearRoles() clearRatings() insert( RatingEntity(id = "r0", name = "Not Rated", description = "Not yet rated"), RatingEntity(id = "r1", name = "G", description = "General Audiences"), RatingEntity(id = "r2", name = "PG", description = "Parental Guidance Suggested"), RatingEntity(id = "r3", name = "PG-13", description = "Unsuitable for those under 13"), RatingEntity(id = "r4", name = "R", description = "Restricted - 17 and older"), ) insert( MovieEntity("m1", "The Transporter", "Jason Statham kicks a guy in the face", "r3"), MovieEntity("m2", "Transporter 2", "Jason Statham kicks a bunch of guys in the face", "r4"), MovieEntity("m3", "Hobbs and Shaw", "Cars, Explosions and Stuff", "r3"), MovieEntity("m4", "Jumanji - Welcome to the Jungle", "The Rock smolders", "r3"), ) insert( ActorEntity("a1", "Jason Statham"), ActorEntity("a2", "The Rock"), ActorEntity("a3", "Shu Qi"), ActorEntity("a4", "Amber Valletta"), ActorEntity("a5", "Kevin Hart"), ) insert( RoleEntity("m1", "a1", "Frank Martin", 1), RoleEntity("m1", "a3", "Lai", 2), RoleEntity("m2", "a1", "Frank Martin", 1), RoleEntity("m2", "a4", "Audrey Billings", 2), RoleEntity("m3", "a2", "Hobbs", 1), RoleEntity("m3", "a1", "Shaw", 2), RoleEntity("m4", "a2", "Spencer", 1), RoleEntity("m4", "a5", "Fridge", 2), ) } }
CHANGED: repository/src/main/java/com/androidbyexample/movies/repository/MovieDatabaseRepository.kt
package com.androidbyexample.movies.repository

import android.content.Context
import com.androidbyexample.movies.data.MovieDao
import com.androidbyexample.movies.data.createDao
import kotlinx.coroutines.flow.map

class MovieDatabaseRepository(
    private val dao: MovieDao
): MovieRepository {
    override val ratingsFlow =
        dao.getRatingsFlow()
            .map { ratings ->// for each List<RatingEntity> that's emitted
                // create a list of RatingDto
                ratings.map { rating -> rating.toDto() } // map each entity to Dto
            }
    override val moviesFlow =
        dao.getMoviesFlow()
            .map { movies ->
                movies.map { it.toDto() }
            }
    override val actorsFlow =
        dao.getActorsFlow()
            .map { actors ->
                actors.map { it.toDto() }
            }
    override suspend fun getRatingWithMovies(id: String): RatingWithMoviesDto =
        dao.getRatingWithMovies(id).toDto()
    override suspend fun getMovieWithCast(id: String): MovieWithCastDto =
        dao.getMovieWithCast(id).toDto()
    override suspend fun getActorWithFilmography(id: String): ActorWithFilmographyDto =
        dao.getActorWithFilmography(id).toDto()
    override suspend fun insert(movie: MovieDto) = dao.insert(movie.toEntity())
    override suspend fun insert(actor: ActorDto) = dao.insert(actor.toEntity())
    override suspend fun insert(rating: RatingDto) = dao.insert(rating.toEntity())

override suspend fun deleteMoviesById(ids: Set<String>) = dao.deleteMoviesById(ids) override suspend fun deleteActorsById(ids: Set<String>) = dao.deleteActorsById(ids) override suspend fun deleteRatingsById(ids: Set<String>) = dao.deleteRatingsById(ids)
override suspend fun resetDatabase() = dao.resetDatabase() companion object { fun create(context: Context) = MovieDatabaseRepository(createDao(context)) } }
CHANGED: repository/src/main/java/com/androidbyexample/movies/repository/MovieRepository.kt
package com.androidbyexample.movies.repository

import kotlinx.coroutines.flow.Flow

interface MovieRepository {
    val ratingsFlow: Flow<List<RatingDto>>
    val moviesFlow: Flow<List<MovieDto>>
    val actorsFlow: Flow<List<ActorDto>>
    suspend fun getRatingWithMovies(id: String): RatingWithMoviesDto
    suspend fun getMovieWithCast(id: String): MovieWithCastDto
    suspend fun getActorWithFilmography(id: String): ActorWithFilmographyDto
    suspend fun insert(movie: MovieDto)
    suspend fun insert(actor: ActorDto)
    suspend fun insert(rating: RatingDto)

suspend fun deleteMoviesById(ids: Set<String>) suspend fun deleteActorsById(ids: Set<String>) suspend fun deleteRatingsById(ids: Set<String>)
suspend fun resetDatabase() }