diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index a6d5269d3..ca5a5bb3b 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -31,8 +31,8 @@ jobs: java-version: '21' distribution: 'temurin' - # lintAll gates :proguard-tests, which applies the google-services plugin and - # will not configure without this file. Mirrors what scripts/build.sh copies. + # lintAll gates :app and :proguard-tests, both of which apply the google-services + # plugin and will not configure without this file. Mirrors what build.sh copies. - name: Copy google-services.json run: | cp library/google-services.json app/google-services.json diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c7870c500..60e26eefe 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -39,6 +39,38 @@ android { } } } + + lint { + // Common lint options across all modules + disable += mutableSetOf( + "IconExpectedSize", + "InvalidPackage", // Firestore uses GRPC which makes lint mad + "NewerVersionAvailable", "GradleDependency", // For reproducible builds + "SelectableText", "SyntheticAccessor" // We almost never care about this + ) + + // Module specific + disable += mutableSetOf( + // Reads the root wrapper, but only the application module analyses it, so it + // cannot sit in the common set above. For reproducible builds. + "AndroidGradlePluginVersion", + // The demos log their auth callbacks unconditionally on purpose — watching + // logcat is how you see one fire. Same call as :auth. + "LogConditional", + // Glide's KSP processor does not generate GlideApp, which the storage demo and + // storage/README.md are both written around. Migration tracked separately. + "KaptUsageInsteadOfKsp", + // A themed icon needs a flat silhouette drawn for the purpose. The only + // candidate here is ic_launcher_foreground, whose opaque region is a solid + // plate, so it tints to a featureless block — worse than no monochrome layer. + "MonochromeLauncherIcon" + ) + + checkAllWarnings = true + warningsAsErrors = true + abortOnError = true + } + compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 77abc5776..9eb4b0fc3 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -14,7 +14,6 @@ android:usesCleartextTraffic="true"> @@ -22,7 +21,13 @@ - + + diff --git a/app/src/main/java/com/firebaseui/android/demo/database/DatabaseDemoActivity.kt b/app/src/main/java/com/firebaseui/android/demo/database/DatabaseDemoActivity.kt index 4e99cbfd3..b756c2276 100644 --- a/app/src/main/java/com/firebaseui/android/demo/database/DatabaseDemoActivity.kt +++ b/app/src/main/java/com/firebaseui/android/demo/database/DatabaseDemoActivity.kt @@ -33,6 +33,7 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.firebase.ui.database.paging.DatabasePagingOptions import com.firebase.ui.database.paging.FirebaseRecyclerPagingAdapter +import com.firebaseui.android.demo.R import com.google.firebase.auth.FirebaseAuth import com.google.firebase.database.DatabaseReference import com.google.firebase.database.FirebaseDatabase @@ -122,7 +123,8 @@ class ScoreAdapter(options: DatabasePagingOptions) : override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ScoreViewHolder(parent) override fun onBindViewHolder(holder: ScoreViewHolder, position: Int, model: ScoreItem) { - (holder.itemView as TextView).text = "${model.name} — score: ${model.score}" + val row = holder.itemView as TextView + row.text = row.context.getString(R.string.demo_score_row, model.name, model.score) } } diff --git a/app/src/main/java/com/firebaseui/android/demo/firestore/FirestoreDemoActivity.kt b/app/src/main/java/com/firebaseui/android/demo/firestore/FirestoreDemoActivity.kt index 91d07abb9..3fb4e44d5 100644 --- a/app/src/main/java/com/firebaseui/android/demo/firestore/FirestoreDemoActivity.kt +++ b/app/src/main/java/com/firebaseui/android/demo/firestore/FirestoreDemoActivity.kt @@ -33,6 +33,7 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.firebase.ui.firestore.paging.FirestorePagingAdapter import com.firebase.ui.firestore.paging.FirestorePagingOptions +import com.firebaseui.android.demo.R import com.google.firebase.auth.FirebaseAuth import com.google.firebase.firestore.CollectionReference import com.google.firebase.firestore.FirebaseFirestore @@ -129,7 +130,8 @@ class ScoreAdapter(options: FirestorePagingOptions) : override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ScoreViewHolder(parent) override fun onBindViewHolder(holder: ScoreViewHolder, position: Int, model: ScoreItem) { - (holder.itemView as TextView).text = "${model.name} — score: ${model.score}" + val row = holder.itemView as TextView + row.text = row.context.getString(R.string.demo_score_row, model.name, model.score) } } diff --git a/app/src/main/res/drawable-hdpi/firebase_auth.png b/app/src/main/res/drawable-hdpi/firebase_auth.png deleted file mode 100644 index fecbcb6dd..000000000 Binary files a/app/src/main/res/drawable-hdpi/firebase_auth.png and /dev/null differ diff --git a/app/src/main/res/drawable-hdpi/firebase_auth.webp b/app/src/main/res/drawable-hdpi/firebase_auth.webp new file mode 100644 index 000000000..ecdf81adb Binary files /dev/null and b/app/src/main/res/drawable-hdpi/firebase_auth.webp differ diff --git a/app/src/main/res/drawable-mdpi/firebase_auth.png b/app/src/main/res/drawable-mdpi/firebase_auth.png deleted file mode 100644 index bc9af3cc0..000000000 Binary files a/app/src/main/res/drawable-mdpi/firebase_auth.png and /dev/null differ diff --git a/app/src/main/res/drawable-mdpi/firebase_auth.webp b/app/src/main/res/drawable-mdpi/firebase_auth.webp new file mode 100644 index 000000000..cb5675df3 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/firebase_auth.webp differ diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml deleted file mode 100644 index fde1368fc..000000000 --- a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/drawable-xhdpi/firebase_auth.png b/app/src/main/res/drawable-xhdpi/firebase_auth.png deleted file mode 100644 index 8a93e39a6..000000000 Binary files a/app/src/main/res/drawable-xhdpi/firebase_auth.png and /dev/null differ diff --git a/app/src/main/res/drawable-xhdpi/firebase_auth.webp b/app/src/main/res/drawable-xhdpi/firebase_auth.webp new file mode 100644 index 000000000..1bfda9b10 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/firebase_auth.webp differ diff --git a/app/src/main/res/drawable-xxhdpi/firebase_auth.png b/app/src/main/res/drawable-xxhdpi/firebase_auth.png deleted file mode 100644 index c01b18b14..000000000 Binary files a/app/src/main/res/drawable-xxhdpi/firebase_auth.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxhdpi/firebase_auth.webp b/app/src/main/res/drawable-xxhdpi/firebase_auth.webp new file mode 100644 index 000000000..600f00a5d Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/firebase_auth.webp differ diff --git a/app/src/main/res/drawable-xxxhdpi/firebase_auth.png b/app/src/main/res/drawable-xxxhdpi/firebase_auth.png deleted file mode 100644 index 221da4d3a..000000000 Binary files a/app/src/main/res/drawable-xxxhdpi/firebase_auth.png and /dev/null differ diff --git a/app/src/main/res/drawable-xxxhdpi/firebase_auth.webp b/app/src/main/res/drawable-xxxhdpi/firebase_auth.webp new file mode 100644 index 000000000..033665959 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/firebase_auth.webp differ diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml deleted file mode 100644 index 1e4408cae..000000000 --- a/app/src/main/res/drawable/ic_launcher_background.xml +++ /dev/null @@ -1,170 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml deleted file mode 100644 index f8c6127d3..000000000 --- a/app/src/main/res/values/colors.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - #FFBB86FC - #FF6200EE - #FF3700B3 - #FF03DAC5 - #FF018786 - #FF000000 - #FFFFFFFF - \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b0b680c7f..3d815a631 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,10 +1,19 @@ - + FirebaseUI Demo CHANGE-HERE - - APP-ID - fbAPP-ID - CHANGE-HERE - \ No newline at end of file + + %1$s — score: %2$d + + + APP-ID + fbAPP-ID + CHANGE-HERE + diff --git a/build.gradle.kts b/build.gradle.kts index e5e280ced..4a970f9ab 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -40,9 +40,11 @@ tasks.register("lintAll") { description = "Runs Android Lint for every module that configures a lint { } block." dependsOn( + ":app:lintDebug", ":auth:lintDebug", ":common:lintDebug", ":database:lintDebug", + ":e2eTest:lintDebug", ":firestore:lintDebug", ":library:lintDebug", ":storage:lintDebug", diff --git a/e2eTest/build.gradle.kts b/e2eTest/build.gradle.kts index e54f6003d..96cc0c199 100644 --- a/e2eTest/build.gradle.kts +++ b/e2eTest/build.gradle.kts @@ -14,6 +14,20 @@ android { minSdk = Config.SdkVersions.min } + lint { + // Common lint options across all modules + disable += mutableSetOf( + "IconExpectedSize", + "InvalidPackage", // Firestore uses GRPC which makes lint mad + "NewerVersionAvailable", "GradleDependency", // For reproducible builds + "SelectableText", "SyntheticAccessor" // We almost never care about this + ) + + checkAllWarnings = true + warningsAsErrors = true + abortOnError = true + } + compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 diff --git a/okf-bundle/ci-workflows/android.md b/okf-bundle/ci-workflows/android.md index 92631d23e..49bf5d895 100644 --- a/okf-bundle/ci-workflows/android.md +++ b/okf-bundle/ci-workflows/android.md @@ -46,7 +46,7 @@ Step 4 is **Java-only** (`include("**/*.java")`), so it inspects zero files in t Separate workflow, `pull_request` only, running `./gradlew --max-workers=2 lintAll`. -`lintAll` is registered in the root `build.gradle.kts` and gates the 8 modules that configure a `lint { }` block; `:app` and `:e2eTest` are not yet among them (CPRN-433). +`lintAll` is registered in the root `build.gradle.kts` and gates all 10 modules that configure a `lint { }` block. It is a separate workflow rather than a step in `build.sh` for two reasons. Lint measured **~4-5 minutes** on this repo — the `build` job went from 3-6 min to 8-11 min when it was inline — so running it in parallel roughly halves PR feedback time at about the same total runner cost, since the extra compile the lint job pays is the one `build.sh` stops paying (`lintAnalyze` depends on `compileDebugKotlin`, and there is no remote build cache: Develocity here is configured for build scans only). And under `set -e` an inline lint failure aborted the run **before** `testDebugUnitTest`, so one new finding cost you every test result for that run. diff --git a/okf-bundle/testing/agent-command-policy.md b/okf-bundle/testing/agent-command-policy.md index 5e052d924..1f7fc5ce1 100644 --- a/okf-bundle/testing/agent-command-policy.md +++ b/okf-bundle/testing/agent-command-policy.md @@ -31,7 +31,7 @@ Single source for **which shell commands agents may run** in this repo. E2e is a | Unit tests (one module with a real `src/test` suite) | `./gradlew ::testDebugUnitTest` (e.g. `:auth:testDebugUnitTest`, `:firestore:…`, `:storage:…`) | `:common:testDebugUnitTest` / `:database:testDebugUnitTest` as “green” evidence (empty suites — [empty unit-suite trap](#empty-unit-suite-trap)); full suite when only one module changed *as a substitute for* the CI path at handoff | | Assemble one module (when no JVM unit suite) | `./gradlew ::assembleDebug` (e.g. `:database`, `:common`) | Treating empty `testDebugUnitTest` as validation | | Checkstyle (**Java only**) | `./gradlew checkstyle` | Invented ktlint/detekt entrypoints; treating a green checkstyle as style coverage for Kotlin sources ([Kotlin blind spot](#checkstyle-kotlin-blind-spot)) | -| Android Lint (all gated modules) — **not** in `build.sh`, own workflow | `./gradlew lintAll` | Bare `./gradlew lint` / `lintDebug` (pulls `:app` and `:e2eTest`, which declare no `lint { }` block yet); assuming a green `build.sh` covered lint | +| Android Lint (all gated modules) — **not** in `build.sh`, own workflow | `./gradlew lintAll` | Bare `./gradlew lint` / `lintDebug` (pulls variants `lintAll` does not gate, e.g. `:proguard-tests` debug); assuming a green `build.sh` covered lint | | Android Lint (one module) | `./gradlew ::lintDebug` (`:proguard-tests` uses `lintRelease`) | Editing Kotlin in a gated module without re-running lint | | Accept new lint debt (**needs a human decision**) | `./gradlew :auth:updateLintBaseline` | Running this to make a red build green — see [lint baseline trap](#lint-baseline-trap) | | Assemble debug | `./gradlew assembleDebug` | Module-scoped assemble as the only CI substitute at handoff | diff --git a/okf-bundle/testing/validation-checklist.md b/okf-bundle/testing/validation-checklist.md index f8764672e..b592f89c0 100644 --- a/okf-bundle/testing/validation-checklist.md +++ b/okf-bundle/testing/validation-checklist.md @@ -62,9 +62,9 @@ Instrumented `androidTest` (database/firestore) is **not** in CI or the agent al ./gradlew lintAll # Android Lint — reads Kotlin and resources ``` -`checkstyle` is scoped `include("**/*.java")` from the root `build.gradle.kts`, so on a Kotlin-only diff it inspects **zero files and exits 0**. A green checkstyle is not evidence for a change in `:auth`, `:app` or `:e2eTest` — [Kotlin blind spot](agent-command-policy.md#checkstyle-kotlin-blind-spot). +`checkstyle` is scoped `include("**/*.java")` from the root `build.gradle.kts`, so on a Kotlin-only diff it inspects **zero files and exits 0**. A green checkstyle is not evidence for a change in `:auth`, `:app` or `:e2eTest`; `lintAll` is what covers those — [Kotlin blind spot](agent-command-policy.md#checkstyle-kotlin-blind-spot). -`lintAll` runs Android Lint for the 8 modules that configure a `lint { }` block, each at `checkAllWarnings = true`, `warningsAsErrors = true` and `abortOnError = true` — so any new finding fails the build. It runs in its own workflow ([lint.yml](../ci-workflows/android.md#lint-workflow)), **not** in `build.sh`, so you must run it separately — a green `build.sh` says nothing about lint. `:app` and `:e2eTest` are not yet gated (CPRN-433). Config: each module's `lint { }` block; `library/quality/checkstyle.xml` for checkstyle. +`lintAll` runs Android Lint for all 10 modules that configure a `lint { }` block, each at `checkAllWarnings = true`, `warningsAsErrors = true` and `abortOnError = true` — so any new finding fails the build. It runs in its own workflow ([lint.yml](../ci-workflows/android.md#lint-workflow)), **not** in `build.sh`, so you must run it separately — a green `build.sh` says nothing about lint. Config: each module's `lint { }` block; `library/quality/checkstyle.xml` for checkstyle. `auth/lint-baseline.xml` suppresses 180 pre-existing findings. **Never** run `updateLintBaseline` to clear a failure your change caused — [baseline trap](agent-command-policy.md#lint-baseline-trap).