Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
# Dependabot: security updates + routine version bumps.
# The gradle ecosystem uses `directories` (glob) so every module's build file
# is watched — root, buildSrc, each app/data/theme/component/template/demo subproject.
# The gradle ecosystem is rooted at "/" only: versions live in
# gradle/libs.versions.toml and Dependabot follows settings.gradle.kts to every
# subproject from there. Scanning each module directory separately ("/**") made
# the updater fail with "No files changed" because the module build files only
# reference catalog aliases.
# The github-actions ecosystem watches workflows; directories key does not apply there.
version: 2
updates:
- package-ecosystem: gradle
directories:
- "/"
- "/**"
directory: /
schedule:
interval: weekly
open-pull-requests-limit: 5
Expand Down
93 changes: 89 additions & 4 deletions .github/workflows/android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,109 @@ on:
branches: [ master ]
pull_request:
branches: [ master ]
workflow_dispatch:

permissions:
contents: read

# A new push to the same PR/branch cancels the previous run.
concurrency:
group: android-ci-${{ github.ref }}
cancel-in-progress: true

jobs:
# Compiles every module, runs JVM unit tests and Roborazzi screenshot
# verification, and publishes the debug APK. This is the required PR check.
build:
runs-on: ubuntu-latest
timeout-minutes: 45

steps:
- uses: actions/checkout@v4

- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: 17
distribution: temurin

# Pinned to a commit SHA (same pin as wrapper-validation.yml).
- name: Set up Gradle
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
with:
cache-read-only: ${{ github.ref != 'refs/heads/master' }}

- name: Grant execute permission for gradlew
run: chmod +x gradlew

- name: Assemble, unit tests, screenshot tests
run: ./gradlew assembleDebug testDebugUnitTest verifyRoborazziDebug --stacktrace

- name: Upload debug APK
uses: actions/upload-artifact@v4
with:
name: ComposeCookBook
path: app/build/outputs/apk/debug/*.apk

- name: Upload Roborazzi diffs
if: failure()
uses: actions/upload-artifact@v4
with:
name: roborazzi-diffs
path: '**/build/outputs/roborazzi/'
if-no-files-found: ignore

- name: Upload unit test reports
if: failure()
uses: actions/upload-artifact@v4
with:
name: unit-test-reports
path: '**/build/reports/tests/'
if-no-files-found: ignore

# Lint runs separately so its memory footprint cannot take the build job down
# with it. Release-variant lint (lintVitalAnalyzeRelease) is deliberately not
# part of the PR gate.
lint:
runs-on: ubuntu-latest
timeout-minutes: 45

steps:
- uses: actions/checkout@v4
- name: set up JDK 17

- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: 17
distribution: temurin


- name: Set up Gradle
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
with:
cache-read-only: true

- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew build

- name: Lint (app)
run: ./gradlew :app:lintDebug --stacktrace

- name: Upload lint report
if: always()
uses: actions/upload-artifact@v4
with:
name: lint-report
path: app/build/reports/lint-results-debug.html
if-no-files-found: ignore

# Branch protection on master requires a status check literally named
# "Building the APK" — that was the job name in the old build.yml, which
# this PR removed as a duplicate of the `build` job above. Keeping this
# thin job (rather than editing the protected-branch setting) makes the
# existing rule keep working without re-running assembleDebug a second time.
building-the-apk:
name: Building the APK
needs: build
runs-on: ubuntu-latest
steps:
- run: echo "Covered by the build job above, which assembles and uploads the debug APK."
49 changes: 0 additions & 49 deletions .github/workflows/build.yml

This file was deleted.

60 changes: 60 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
name: CodeQL

on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
schedule:
# Weekly scan so new CodeQL queries run even without pushes
- cron: '26 7 * * 1'
workflow_dispatch:

permissions:
contents: read

jobs:
analyze:
name: Analyze (java-kotlin)
runs-on: ubuntu-latest
timeout-minutes: 60
permissions:
contents: read
security-events: write

steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: 17
distribution: temurin

# Dependency cache only. The Gradle *build* cache is disabled for the
# analysis build below: CodeQL's tracer has to observe real compilations,
# and a warm build cache makes every compileKotlin task FROM-CACHE, which
# ends in "CodeQL could not process any code written in Java/Kotlin".
- name: Set up Gradle
uses: gradle/actions/setup-gradle@50e97c2cd7a37755bbfafc9c5b7cafaece252f6e # v6.1.0
with:
cache-read-only: true

- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: java-kotlin
build-mode: manual

# Kotlin extraction requires a real compilation; assembleDebug is the
# cheapest target that compiles every module.
- name: Build for analysis
run: |
chmod +x gradlew
./gradlew assembleDebug --no-build-cache --no-configuration-cache

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: '/language:java-kotlin'
36 changes: 36 additions & 0 deletions .github/workflows/dependency-review.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: Dependency review

# Flags PRs that introduce dependencies with known vulnerabilities.
# Requires "Dependency graph" to be enabled under Settings > Code security.
# While it is disabled the job skips with a warning instead of failing.
on:
pull_request:
branches: [ master ]

permissions:
contents: read

jobs:
dependency-review:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Check that Dependency graph is enabled
id: graph
env:
GH_TOKEN: ${{ github.token }}
run: |
if gh api "repos/${GITHUB_REPOSITORY}/dependency-graph/sbom" --silent >/dev/null 2>&1; then
echo "enabled=true" >> "$GITHUB_OUTPUT"
else
echo "enabled=false" >> "$GITHUB_OUTPUT"
echo "::warning title=Dependency review skipped::Dependency graph is disabled for this repository. Enable it under Settings > Code security and analysis to activate dependency review."
fi

- name: Dependency review
if: steps.graph.outputs.enabled == 'true'
uses: actions/dependency-review-action@v4
with:
fail-on-severity: high
14 changes: 8 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,14 +82,16 @@ Adding card details | CaseCade Menu
:-------------------------:| :-------------------------:
![](https://user-images.githubusercontent.com/8813304/112016144-1e21ef00-8b35-11eb-8c33-362c8cbe6c0d.gif) | ![](https://media.giphy.com/media/WoFe2OZ7kbW2KBkzFN/giphy.gif)

### Kotlin DSL Templates
You can find them in *buildSrc*
- build.bradle
- dependency
- configurations
### Build setup
- Dependencies and versions live in the Gradle version catalog: `gradle/libs.versions.toml`
- Shared module configuration (convention plugins, SDK levels) lives in *buildSrc*

## How to get started
Please get **Android Studio Bumblebee latest Canary** [from here](https://developer.android.com/studio/preview/) and use **JDK 11** to build this project.
Use **Android Studio Ladybug or newer** and **JDK 17** (the project targets AGP 8.7 / Kotlin 2.1).

The MoviesApp demo needs a [TMDB API key](https://www.themoviedb.org/settings/api). Add it to your
(git-ignored) `local.properties` as `tmdbApiKey=YOUR_KEY`, or export `TMDB_API_KEY`. Without it the
demo builds fine but the movie lists stay empty.

## Features & Where to start
- __Widgets:__ Widgets Screen show case all the available components to build UI.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.LibraryAdd
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
Expand Down Expand Up @@ -67,16 +70,18 @@ fun MovieDetailContent(movie: Movie, imageId: Int) {
item {
val painter =
rememberAsyncImagePainter(model = "https://image.tmdb.org/t/p/w500/${movie.poster_path}")
// Coil 3 exposes the painter state as a StateFlow; collect it and react outside
// of composition instead of comparing the flow object itself.
val painterState by painter.state.collectAsState()
LaunchedEffect(painterState) {
expand.value = painterState is AsyncImagePainter.State.Success
}
Image(
painter = painter,
contentScale = ContentScale.Crop,
contentDescription = null,
modifier = Modifier.height(600.dp).fillMaxWidth(),
)
when (painter.state) {
is AsyncImagePainter.State.Success -> expand.value = true
else -> expand.value = false
}
}
item {
Column(modifier = Modifier.background(MaterialTheme.colors.onSurface)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Modifier
Expand All @@ -22,7 +23,8 @@ import com.guru.composecookbook.theme.typography

@Composable
fun SimilarMoviesSection(currentMovie: Movie?, viewModel: MovieDetailViewModel) {
viewModel.getSimilarMovies(currentMovie?.id.toString())
// Fetch once per movie, not on every recomposition.
LaunchedEffect(currentMovie?.id) { viewModel.getSimilarMovies(currentMovie?.id.toString()) }
val similarMovies by viewModel.similarMoviesLiveData.observeAsState()
similarMovies?.let { movies ->
Text(text = "Similar Movies", style = typography.h5, modifier = Modifier.padding(8.dp))
Expand Down
21 changes: 20 additions & 1 deletion demos/moviesapp/data/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,9 +1,28 @@
import java.util.Properties

plugins {
/** See [common-kotlin-module-configs-script-plugin.gradle.kts] file */
id("common-kotlin-module-configs-script-plugin")
}

android { namespace = "com.guru.composecookbook.moviesapp.data" }
// TMDB API key. Put `tmdbApiKey=<your key>` in local.properties (git-ignored) or
// export TMDB_API_KEY. Never commit the key; get one at https://www.themoviedb.org/settings/api
val tmdbApiKey: String = run {
val props = Properties()
val localProperties = rootProject.file("local.properties")
if (localProperties.exists()) localProperties.inputStream().use { props.load(it) }
props.getProperty("tmdbApiKey")?.trim('"')?.takeIf { it.isNotBlank() }
?: System.getenv("TMDB_API_KEY")
?: ""
}

android {
namespace = "com.guru.composecookbook.moviesapp.data"

buildFeatures { buildConfig = true }

defaultConfig { buildConfigField("String", "TMDB_API_KEY", "\"$tmdbApiKey\"") }
}

dependencies {
implementation(libs.bundles.core.android)
Expand Down
Loading
Loading