Movies UI - Lists

Better Movie Icon

Jetpack Compose comes with some useful Icons, but it's a rather short list. You can see available icons at Google Fonts.

Some are in the androidx.compose.material:material-icons-core (pulled in when you include androidx.compose.material3:material3 as a dependency), and the rest are available in androidx.compose.material:material-icons-extended, which you need to pull in yourself.

Warning

The extended icons dependency is rather large, and you'll likely only need a few icons from it. You can shrink your app size (see Shrink your app) or copy the source for the icons into your application. I recommend copying the source for the icons you need, as it reduces the build time.

Note that source appears to be missing or difficult to locate for some versions. At this point I'm using extended icons version 1.6.0-alpha06, which has source.

To include the movie icon:

  1. Set the version of material-icons-extended you want in the version catalog.
  2. Define the library in the version catalog.
  3. Include the dependency in your app module's build script. (Note that it's commented-out here, as the end result of this step has already copied the icon)
  4. Sync your build scripts (press the elephant icon on the Android Studio tool bar)
  5. Change the icon in MovieListUi.
  6. Change the icon in the tab in ListScaffold.

At this point you can run the application you'll now see movie icons.

Movie icons in app

Now we need to either shrink the app to remove all of the extra icons (this slows down the build!) or copy the source for the icons we need. We'll copy the source.

  1. Open MovieListUi in the editor
  2. Find Icons.Default.Movie
  3. Control-click on Movie to go to its source code. (If you don't see the source you'll need to try a different version of material-icons-extended)
  4. Copy the package name from the source
  5. Right-click on java under app/src/main
  6. Choose New > Package
  7. Paste the package name
  8. Copy the entire source (including the copyright header at the top) for the Movie icon
  9. Right-click the package you just created
  10. Choose New > Kotlin Class/File
  11. Type "Movie" as the name and choose File
  12. Delete the package statement and paste the source code

We now have a copy of the icon.

Next, remove the huge dependency by deleting or commenting out the implementation line from app/build.gradle.kts. Be sure to re-sync the Gradle info by clicking the elephant on the tool bar.

Note

You do not need to delete the dependency information from the version catalog. This will allow you to more easily copy in other icons if you need to at a later date. I recommend leaving it and commenting out the dependency.

Code Changes

CHANGED: /app/build.gradle.kts
@Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed
plugins {
    alias(libs.plugins.androidApplication)
    alias(libs.plugins.kotlinAndroid)
}

kotlin {
    jvmToolchain(17)
}

android {
    namespace = "com.androidbyexample.movie"
    compileSdk = 34

    defaultConfig {
        applicationId = "com.androidbyexample.movieui2"
        minSdk = 24
        targetSdk = 34
        versionCode = 1
        versionName = "1.0"

        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
        vectorDrawables {
            useSupportLibrary = true
        }
    }

    buildTypes {
        release {
            isMinifyEnabled = false
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
    buildFeatures {
        compose = true
    }
    composeOptions {
        kotlinCompilerExtensionVersion = "1.5.3"
    }
    packaging {
        resources {
            excludes += "/META-INF/{AL2.0,LGPL2.1}"
        }
    }
}

dependencies {
    implementation(project(":repository"))

// implementation(libs.icons.extended)
implementation(libs.lifecycle.compose) implementation(libs.core.ktx) implementation(libs.lifecycle.runtime.ktx) implementation(libs.activity.compose) implementation(platform(libs.compose.bom)) implementation(libs.ui) implementation(libs.ui.graphics) implementation(libs.ui.tooling.preview) implementation(libs.material3) testImplementation(libs.junit) androidTestImplementation(libs.androidx.test.ext.junit) androidTestImplementation(libs.espresso.core) androidTestImplementation(platform(libs.compose.bom)) androidTestImplementation(libs.ui.test.junit4) debugImplementation(libs.ui.tooling) debugImplementation(libs.ui.test.manifest) }
ADDED: /app/src/main/java/androidx/compose/material/icons/filled/Movie.kt
/* * Copyright 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * *      http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package androidx.compose.material.icons.filledimport androidx.compose.material.icons.Iconsimport androidx.compose.material.icons.materialIconimport androidx.compose.material.icons.materialPathimport androidx.compose.ui.graphics.vector.ImageVectorpublic val Icons.Filled.Movie: ImageVector    get() {        if (_movie != null) {            return _movie!!        }        _movie = materialIcon(name = "Filled.Movie") {            materialPath {                moveTo(18.0f, 4.0f)                lineToRelative(2.0f, 4.0f)                horizontalLineToRelative(-3.0f)                lineToRelative(-2.0f, -4.0f)                horizontalLineToRelative(-2.0f)                lineToRelative(2.0f, 4.0f)                horizontalLineToRelative(-3.0f)                lineToRelative(-2.0f, -4.0f)                horizontalLineTo(8.0f)                lineToRelative(2.0f, 4.0f)                horizontalLineTo(7.0f)                lineTo(5.0f, 4.0f)                horizontalLineTo(4.0f)                curveToRelative(-1.1f, 0.0f, -1.99f, 0.9f, -1.99f, 2.0f)                lineTo(2.0f, 18.0f)                curveToRelative(0.0f, 1.1f, 0.9f, 2.0f, 2.0f, 2.0f)                horizontalLineToRelative(16.0f)                curveToRelative(1.1f, 0.0f, 2.0f, -0.9f, 2.0f, -2.0f)                verticalLineTo(4.0f)                horizontalLineToRelative(-4.0f)                close()            }        }        return _movie!!    }private var _movie: ImageVector? = null
CHANGED: /app/src/main/java/com/androidbyexample/movie/screens/ListScaffold.kt
package com.androidbyexample.movie.screens

import androidx.annotation.StringRes
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.fillMaxSize
import 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.Movieimport androidx.compose.material.icons.filled.Person
import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material.icons.filled.Star
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
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.ScreenSelectButton
import com.androidbyexample.movie.repository.HasId

@OptIn(ExperimentalMaterial3Api::class) @Composable fun <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, imageVector = Icons.Default.Movie,
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.padding
import androidx.compose.material.icons.Icons
//import androidx.compose.material.icons.filled.Starimport androidx.compose.material.icons.filled.Movieimport androidx.compose.material3.Icon
import 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

@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,
) {
ListScaffold( titleId = R.string.movies, items = movies, onItemClicked = onMovieClicked, selectedIds = selectedIds, onSelectionToggle = onSelectionToggle, onClearSelections = onClearSelections, onDeleteSelectedItems = onDeleteSelectedMovies, currentScreen = currentScreen, onSelectListScreen = onSelectListScreen, onResetDatabase = onResetDatabase ) { movie -> Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(8.dp) ) { Icon(
// imageVector = Icons.Default.Star, imageVector = Icons.Default.Movie,
contentDescription = stringResource(id = R.string.movie), modifier = Modifier.clickable { onSelectionToggle(movie.id) } ) Display(text = movie.title) } }
}
CHANGED: /gradle/libs.versions.toml
[versions]
agp = "8.2.0-beta05"
kotlin = "1.9.10"
core-ktx = "1.12.0"
junit = "4.13.2"
androidx-test-ext-junit = "1.1.5"
espresso-core = "3.5.1"
lifecycle-runtime-ktx = "2.6.2"
activity-compose = "1.7.2"
lifecycle-compose = "2.6.2"
compose-bom = "2023.09.01"
appcompat = "1.6.1"
material = "1.9.0"
room = "2.5.2"
ksp = "1.9.10-1.0.13"
icons-extended = "1.6.0-alpha06"
[libraries] core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "core-ktx" } junit = { group = "junit", name = "junit", version.ref = "junit" } androidx-test-ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidx-test-ext-junit" } espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espresso-core" } lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle-runtime-ktx" } activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activity-compose" } lifecycle-compose = { group = "androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "lifecycle-compose" } compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "compose-bom" } ui = { group = "androidx.compose.ui", name = "ui" } ui-graphics = { group = "androidx.compose.ui", name = "ui-graphics" } ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling" } ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview" } ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } material3 = { group = "androidx.compose.material3", name = "material3" } appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } material = { group = "com.google.android.material", name = "material", version.ref = "material" } room-runtime = { group = "androidx.room", name = "room-runtime", version.ref = "room" } room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = "room" } room-compiler = { group = "androidx.room", name = "room-compiler", version.ref = "room" }
icons-extended = { group = "androidx.compose.material", name = "material-icons-extended-android", version.ref = "icons-extended"}
[plugins] androidApplication = { id = "com.android.application", version.ref = "agp" } kotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } androidLibrary = { id = "com.android.library", version.ref = "agp" } ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" }