diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b39367fc9..72277fea2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -111,7 +111,6 @@ jobs: ./gradlew :snippets:app-utils-ktx:assembleDebug ./gradlew :snippets:app-compose:assembleDebug ./gradlew :snippets:app-places-ktx:assembleDebug - ./gradlew :snippets:app-rx:assembleDebug ./gradlew :snippets:app-utils:assembleDebug build-tutorials: @@ -134,13 +133,25 @@ jobs: run: | ./gradlew :tutorials:kotlin:Polygons:assembleDebug - test: # used as required status check + test: runs-on: ubuntu-latest + timeout-minutes: 60 needs: - build-ApiDemos - build-WearOS + - build-FireMarkers - build-Snippets - build-tutorials - - build-FireMarkers steps: - - run: echo "Fail if all other steps are not successful" + - uses: actions/checkout@v4 + - name: set up Java 21 + uses: actions/setup-java@v4 + with: + distribution: 'adopt' + java-version: '21' + + - name: Run Unit Tests + run: ./gradlew testDebugUnitTest + + - name: Run Lint + run: ./gradlew lintDebug diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index bd016d71e..000442411 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -31,7 +31,15 @@ jobs: uses: actions/setup-java@v4 with: distribution: 'adopt' - java-version: '17' + java-version: '21' + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.x' + + - name: Check documentation versions + run: python3 scripts/update_docs_versions.py --check - name: Setup Gradle uses: gradle/actions/setup-gradle@v5 @@ -44,7 +52,6 @@ jobs: ./gradlew :snippets:app:lintGmsDebug ./gradlew :snippets:app-utils:lintDebug ./gradlew :snippets:app-utils-ktx:lintDebug - ./gradlew :snippets:app-rx:lintDebug ./gradlew :snippets:app-places-ktx:lintDebug ./gradlew :snippets:app-ktx:lintDebug ./gradlew :snippets:app-compose:lintDebug @@ -87,12 +94,6 @@ jobs: sarif_file: snippets/app-utils-ktx/build/reports/lint-results-debug.sarif category: snippets-app-utils-ktx - - name: Upload SARIF for snippets:app-rx - uses: github/codeql-action/upload-sarif@v3 - with: - sarif_file: snippets/app-rx/build/reports/lint-results-debug.sarif - category: snippets-app-rx - - name: Upload SARIF for snippets:app-places-ktx uses: github/codeql-action/upload-sarif@v3 with: diff --git a/FireMarkers/app/build.gradle.kts b/FireMarkers/app/build.gradle.kts index 55e16e9e7..77f155daa 100644 --- a/FireMarkers/app/build.gradle.kts +++ b/FireMarkers/app/build.gradle.kts @@ -45,13 +45,11 @@ plugins { alias(libs.plugins.kotlin.serialization) // Provides Kotlin serialization capabilities. } -gradle.projectsEvaluated { - if (rootProject.file("app/google-services.json").exists()) { - project(":app").pluginManager.apply("com.google.gms.google-services") - println("Applied Google Services plugin.") - } else { - println("google-services.json not found — skipping plugin application") - } +if (file("google-services.json").exists()) { + apply(plugin = "com.google.gms.google-services") + println("Applied Google Services plugin.") +} else { + println("google-services.json not found — skipping Google Services plugin") } android { diff --git a/FireMarkers/app/src/main/java/com/example/firemarkers/FireMarkersApplication.kt b/FireMarkers/app/src/main/java/com/example/firemarkers/FireMarkersApplication.kt index 2e8dd5bbb..9f0c82491 100644 --- a/FireMarkers/app/src/main/java/com/example/firemarkers/FireMarkersApplication.kt +++ b/FireMarkers/app/src/main/java/com/example/firemarkers/FireMarkersApplication.kt @@ -59,7 +59,7 @@ class FireMarkersApplication : Application() { val mapsApiKey = bundle.getString("com.google.android.geo.API_KEY") // Key name is important! - if (mapsApiKey == null || mapsApiKey.isBlank() || mapsApiKey == "DEFAULT_API_KEY") { + if (mapsApiKey.isNullOrBlank() || mapsApiKey == "DEFAULT_API_KEY") { Toast.makeText( this, "Maps API Key was not set in secrets.properties", diff --git a/FireMarkers/app/src/main/java/com/example/firemarkers/viewmodel/MarkersViewModel.kt b/FireMarkers/app/src/main/java/com/example/firemarkers/viewmodel/MarkersViewModel.kt index b6d5e0f98..8a415d263 100644 --- a/FireMarkers/app/src/main/java/com/example/firemarkers/viewmodel/MarkersViewModel.kt +++ b/FireMarkers/app/src/main/java/com/example/firemarkers/viewmodel/MarkersViewModel.kt @@ -154,10 +154,7 @@ class MarkersViewModel @Inject constructor( } override fun onCancelled(error: DatabaseError) { - Log.e(TAG, "[$viewModelId] Database error on markers: ${error.message}") - viewModelScope.launch { - _errorEvents.emit("Database error on markers: ${error.message}") - } + handleDatabaseError(error, "markers") } }) } @@ -187,14 +184,23 @@ class MarkersViewModel @Inject constructor( } override fun onCancelled(error: DatabaseError) { - Log.e(TAG, "[$viewModelId] DB error on animation: ${error.message}") - viewModelScope.launch { - _errorEvents.emit("DB error on animation: ${error.message}") - } + handleDatabaseError(error, "animation") } }) } + private fun handleDatabaseError(error: DatabaseError, context: String) { + val msg = if (error.code == DatabaseError.PERMISSION_DENIED) { + "Permission Denied ($context): Check your Firebase Database Rules." + } else { + "Database error ($context): ${error.message}" + } + Log.e(TAG, "[$viewModelId] $msg") + viewModelScope.launch { + _errorEvents.emit(msg) + } + } + /** * Toggles the animation state (running/paused) in Firebase. * diff --git a/FireMarkers/app/src/test/java/com/example/firemarkers/viewmodel/MarkersViewModelTest.kt b/FireMarkers/app/src/test/java/com/example/firemarkers/viewmodel/MarkersViewModelTest.kt index f0df895ab..188e63575 100644 --- a/FireMarkers/app/src/test/java/com/example/firemarkers/viewmodel/MarkersViewModelTest.kt +++ b/FireMarkers/app/src/test/java/com/example/firemarkers/viewmodel/MarkersViewModelTest.kt @@ -156,7 +156,7 @@ class MarkersViewModelTest { listenerCaptor.firstValue.onCancelled(error) testDispatcher.scheduler.advanceUntilIdle() - assertThat(errorMessage).isEqualTo("DB error on animation: Test Error") + assertThat(errorMessage).isEqualTo("Database error (animation): Test Error") job.cancel() } } diff --git a/FireMarkers/app/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/FireMarkers/app/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker new file mode 100644 index 000000000..1f0955d45 --- /dev/null +++ b/FireMarkers/app/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker @@ -0,0 +1 @@ +mock-maker-inline diff --git a/README.md b/README.md index eae2ae1c3..72d660fca 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,14 @@ To run the samples, you will need: 1. In the welcome screen of Android Studio, select "Open an Existing project" 1. Select one of the sample directories from this repository +## Verifying the build + +To verify that all samples build and pass tests, run: + +```bash +./scripts/verify_all.sh +``` + Alternatively, use the `gradlew build` command to build the project directly or download an APK under [releases](https://github.com/googlemaps/android-samples/releases). diff --git a/WearOS/Wearable/build.gradle.kts b/WearOS/Wearable/build.gradle.kts index cf10d36db..229d6bcb8 100644 --- a/WearOS/Wearable/build.gradle.kts +++ b/WearOS/Wearable/build.gradle.kts @@ -1,5 +1,5 @@ /* - * Copyright 2024 Google LLC + * Copyright 2026 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,12 +21,12 @@ plugins { } android { - compileSdk = 35 + compileSdk = libs.versions.compileSdk.get().toInt() defaultConfig { applicationId = "com.example.wearos" - minSdk = 23 - targetSdk = 31 + minSdk = libs.versions.minSdk.get().toInt() + targetSdk = libs.versions.targetSdk.get().toInt() versionCode = 1 versionName = libs.versions.versionName.get() } @@ -34,7 +34,7 @@ android { buildTypes { getByName("release") { isMinifyEnabled = false - proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro") + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") } } @@ -45,10 +45,6 @@ android { sarifOutput = layout.buildDirectory.file("reports/lint-results-debug.sarif").get().asFile } - kotlinOptions { - jvmTarget = "21" - } - kotlin { jvmToolchain(21) } @@ -57,15 +53,37 @@ android { // [START maps_wear_os_dependencies] dependencies { // [START_EXCLUDE] - implementation("androidx.core:core-ktx:1.15.0") - implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk7:2.0.21") + implementation(libs.core.ktx) + implementation(platform(libs.kotlin.bom)) + implementation(libs.kotlin.stdlib) // [END_EXCLUDE] - compileOnly("com.google.android.wearable:wearable:2.9.0") - implementation("com.google.android.support:wearable:2.9.0") - implementation("com.google.android.gms:play-services-maps:19.0.0") + // Modern Android projects use version catalogs to manage dependencies. To include the necessary dependencies, + // first add the following to your libs.versions.toml file: + // + // [versions] + // playServicesMaps = "20.0.0" + // wear = "1.3.0" + // wearable = "2.9.0" + // + // [libraries] + // play-services-maps = { group = "com.google.android.gms", name = "play-services-maps", version.ref = "playServicesMaps" } + // wear = { group = "androidx.wear", name = "wear", version.ref = "wear" } + // wearable-compile = { group = "com.google.android.wearable", name = "wearable", version.ref = "wearable" } + // wearable-support = { group = "com.google.android.support", name = "wearable", version.ref = "wearable" } + + compileOnly(libs.wearable.compile) + implementation(libs.wearable.support) + implementation(libs.play.services.maps) // This dependency is necessary for ambient mode - implementation("androidx.wear:wear:1.3.0") + implementation(libs.wear) + + // If your project does not use a version catalog, you can use the following dependencies instead: + // + // compileOnly("com.google.android.wearable:wearable:2.9.0") + // implementation("com.google.android.support:wearable:2.9.0") + // implementation("com.google.android.gms:play-services-maps:20.0.0") + // implementation("androidx.wear:wear:1.3.0") } // [END maps_wear_os_dependencies] diff --git a/gradle/gradle-daemon-jvm.properties b/gradle/gradle-daemon-jvm.properties new file mode 100644 index 000000000..7741c8a06 --- /dev/null +++ b/gradle/gradle-daemon-jvm.properties @@ -0,0 +1,13 @@ +#This file is generated by updateDaemonJvm +toolchainUrl.FREE_BSD.AARCH64=https\://api.foojay.io/disco/v3.0/ids/c5760d82d08e6c26884debb23736ea57/redirect +toolchainUrl.FREE_BSD.X86_64=https\://api.foojay.io/disco/v3.0/ids/879378f84c64b2c76003b97a32968399/redirect +toolchainUrl.LINUX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/c5760d82d08e6c26884debb23736ea57/redirect +toolchainUrl.LINUX.X86_64=https\://api.foojay.io/disco/v3.0/ids/879378f84c64b2c76003b97a32968399/redirect +toolchainUrl.MAC_OS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/021e528cbed860c875a9016f29ee13c1/redirect +toolchainUrl.MAC_OS.X86_64=https\://api.foojay.io/disco/v3.0/ids/6141bf023dcc7a96c47cad75c59b054e/redirect +toolchainUrl.UNIX.AARCH64=https\://api.foojay.io/disco/v3.0/ids/c5760d82d08e6c26884debb23736ea57/redirect +toolchainUrl.UNIX.X86_64=https\://api.foojay.io/disco/v3.0/ids/879378f84c64b2c76003b97a32968399/redirect +toolchainUrl.WINDOWS.AARCH64=https\://api.foojay.io/disco/v3.0/ids/9b6bab41c3ef2acea6116b7821b8dc11/redirect +toolchainUrl.WINDOWS.X86_64=https\://api.foojay.io/disco/v3.0/ids/a6eb06d81d82a782734ef3b616ba2684/redirect +toolchainVendor=JETBRAINS +toolchainVersion=21 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index fe043860e..0fde98189 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,114 +1,151 @@ [versions] +# Project Configuration +androidGradlePlugin = "8.13.2" minSdk = "24" compileSdk = "36" targetSdk = "36" +versionCode = "1" +versionName = "1.20.1" # {x-release-please-version} +javaVersion = "17" + +# Kotlin & Coroutines +kotlin = "2.2.0" +kotlinxCoroutinesTest = "1.10.2" +kotlinxDatetime = "0.7.1" +ksp = "2.2.20-2.0.4" -activity = "1.12.1" -activityKtx = "1.11.0" -androidxJunit = "1.3.0" +# AndroidX Core & Jetpack +activity = "1.12.2" +activityKtx = "1.12.2" +androidxJunit = "1.3.0" # Test ext appcompat = "1.7.1" cardview = "1.0.0" +constraintlayout = "2.2.1" coreKtx = "1.17.0" -easypermissions = "3.0.0" -espresso = "3.7.0" -gradle = "8.13.2" -hilt = "2.57.2" -junit = "4.13.2" -kotlin = "2.2.21" lifecycle = "2.10.0" -mapsKtx = "5.2.1" -mapsCompose = "6.12.1" -material = "1.13.0" multidex = "2.0.1" navigation = "2.9.6" -playServicesMaps = "19.2.0" -places = "5.1.1" recyclerview = "1.4.0" + +# Jetpack Compose +compose = "1.10.1" +composeBom = "2026.01.00" +hiltNavigationCompose = "1.3.0" +material = "1.13.0" # View-based Material +material3 = "1.4.0" +materialIconsExtended = "1.7.8" + +# Google Maps & Places +mapsCompose = "7.0.0" +mapsKtx = "6.0.0" +places = "5.1.1" +playServicesMaps = "20.0.0" secretsGradlePlugin = "2.0.1" -volley = "1.2.1" -truth = "1.4.5" -uiautomator = "2.3.0" -compose = "1.7.6" -composeBom = "2024.12.01" -hiltNavigationCompose = "1.2.0" + +# Wear OS +wear = "1.3.0" +wearable = "2.9.0" + +# Dependency Injection dagger = "2.57.2" -firebaseBom = "33.10.0" -kotlinxDatetime = "0.7.1" -kotlinxCoroutinesTest = "1.10.2" -robolectric = "4.16" +hilt = "2.57.2" + +# Testing +espresso = "3.7.0" +junit = "4.13.2" +mockito = "6.2.2" +robolectric = "4.16.1" +truth = "1.4.5" turbine = "1.2.1" -mockito = "6.1.0" -versionCode = "1" -# {x-release-please-start-version} -versionName = "1.20.1" -# {x-release-please-end} -javaVersion = "17" +uiautomator = "2.3.0" + +# Firebase +firebaseBom = "34.8.0" +firebaseDatabase = "22.0.1" + +# Third Party +easypermissions = "3.0.0" +volley = "1.2.1" [libraries] +# Kotlin +kotlin-bom = { module = "org.jetbrains.kotlin:kotlin-bom", version.ref = "kotlin" } +kotlin-stdlib = { group = "org.jetbrains.kotlin", name = "kotlin-stdlib", version.ref = "kotlin" } +kotlin-stdlib-jdk8 = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" } +kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinxDatetime" } + +# AndroidX activity = { module = "androidx.activity:activity", version.ref = "activity" } activity-compose = { module = "androidx.activity:activity-compose", version.ref = "activityKtx" } -androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidxJunit" } -ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidxJunit" } appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" } cardview = { group = "androidx.cardview", name = "cardview", version.ref = "cardview" } -compose-bom = { module = "androidx.compose:compose-bom", version.ref = "composeBom" } +constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" } core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } -espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "espresso" } -espresso-idling-resource = { module = "androidx.test.espresso:espresso-idling-resource", version.ref = "espresso" } -hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hilt" } -hilt-android-compiler = { module = "com.google.dagger:hilt-android-compiler", version.ref = "hilt" } -junit = { group = "junit", name = "junit", version.ref = "junit" } -kotlin-stdlib = { group = "org.jetbrains.kotlin", name = "kotlin-stdlib", version.ref = "kotlin" } lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle" } -maps-ktx = { group = "com.google.maps.android", name = "maps-ktx", version.ref = "mapsKtx" } -maps-utils-ktx = { group = "com.google.maps.android", name = "maps-utils-ktx", version.ref = "mapsKtx" } -maps-compose = { module = "com.google.maps.android:maps-compose", version.ref = "mapsCompose" } -material = { group = "com.google.android.material", name = "material", version.ref = "material" } -material3 = { module = "androidx.compose.material3:material3", version = "1.3.1" } multidex = { group = "androidx.multidex", name = "multidex", version.ref = "multidex" } navigation-fragment-ktx = { group = "androidx.navigation", name = "navigation-fragment-ktx", version.ref = "navigation" } navigation-ui-ktx = { group = "androidx.navigation", name = "navigation-ui-ktx", version.ref = "navigation" } -play-services-maps = { group = "com.google.android.gms", name = "play-services-maps", version.ref = "playServicesMaps" } -places = { group = "com.google.android.libraries.places", name = "places", version.ref = "places" } recyclerview = { group = "androidx.recyclerview", name = "recyclerview", version.ref = "recyclerview" } -constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version = "2.2.0" } + +# Compose +compose-bom = { module = "androidx.compose:compose-bom", version.ref = "composeBom" } compose-foundation = { module = "androidx.compose.foundation:foundation", version.ref = "compose" } compose-material = { module = "androidx.compose.material:material", version.ref = "compose" } -volley = { group = "com.android.volley", name = "volley", version.ref = "volley" } -truth = { group = "com.google.truth", name = "truth", version.ref = "truth" } -uiautomator = { group = "androidx.test.uiautomator", name = "uiautomator", version.ref = "uiautomator" } -easypermissions = { group = "pub.devrel", name = "easypermissions", version.ref = "easypermissions" } - -hilt-navigation-compose = { module = "androidx.hilt:hilt-navigation-compose", version.ref = "hiltNavigationCompose" } -material-icons-extended = { module = "androidx.compose.material:material-icons-extended", version.ref = "compose" } +material = { group = "com.google.android.material", name = "material", version.ref = "material" } +material-icons-extended = { module = "androidx.compose.material:material-icons-extended", version.ref = "materialIconsExtended" } +material3 = { module = "androidx.compose.material3:material3", version.ref = "material3" } ui = { module = "androidx.compose.ui:ui", version.ref = "compose" } ui-graphics = { module = "androidx.compose.ui:ui-graphics", version.ref = "compose" } ui-tooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "compose" } ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview", version.ref = "compose" } -ui-test-junit4 = { module = "androidx.compose.ui:ui-test-junit4", version.ref = "compose" } -ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest", version.ref = "compose" } -kotlin-stdlib-jdk8 = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" } -maps-rx = { module = "com.google.maps.android:maps-rx", version = "1.0.0" } -places-rx = { module = "com.google.maps.android:places-rx", version = "1.0.0" } -rxlifecycle-android-lifecycle-kotlin = { module = "com.trello.rxlifecycle4:rxlifecycle-android-lifecycle-kotlin", version = "4.0.2" } +# Maps & Places +maps-compose = { module = "com.google.maps.android:maps-compose", version.ref = "mapsCompose" } +maps-ktx = { group = "com.google.maps.android", name = "maps-ktx", version.ref = "mapsKtx" } +maps-utils-ktx = { group = "com.google.maps.android", name = "maps-utils-ktx", version.ref = "mapsKtx" } +places = { group = "com.google.android.libraries.places", name = "places", version.ref = "places" } +play-services-maps = { group = "com.google.android.gms", name = "play-services-maps", version.ref = "playServicesMaps" } + +# Wear OS +wear = { group = "androidx.wear", name = "wear", version.ref = "wear" } +wearable-compile = { group = "com.google.android.wearable", name = "wearable", version.ref = "wearable" } +wearable-support = { group = "com.google.android.support", name = "wearable", version.ref = "wearable" } + +# Dependency Injection dagger = { module = "com.google.dagger:dagger", version.ref = "dagger" } -firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebaseBom" } -firebase-database = { module = "com.google.firebase:firebase-database", version = "21.0.0" } -kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinxDatetime" } +hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hilt" } +hilt-android-compiler = { module = "com.google.dagger:hilt-android-compiler", version.ref = "hilt" } +hilt-navigation-compose = { module = "androidx.hilt:hilt-navigation-compose", version.ref = "hiltNavigationCompose" } + +# Testing +espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "espresso" } +espresso-idling-resource = { module = "androidx.test.espresso:espresso-idling-resource", version.ref = "espresso" } +ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "androidxJunit" } +junit = { group = "junit", name = "junit", version.ref = "junit" } kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinxCoroutinesTest" } +mockito-kotlin = { module = "org.mockito.kotlin:mockito-kotlin", version.ref = "mockito" } robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" } +truth = { group = "com.google.truth", name = "truth", version.ref = "truth" } turbine = { module = "app.cash.turbine:turbine", version.ref = "turbine" } -mockito-kotlin = { module = "org.mockito.kotlin:mockito-kotlin", version.ref = "mockito" } +ui-test-junit4 = { module = "androidx.compose.ui:ui-test-junit4", version.ref = "compose" } +ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest", version.ref = "compose" } +uiautomator = { group = "androidx.test.uiautomator", name = "uiautomator", version.ref = "uiautomator" } + +# Firebase +firebase-bom = { module = "com.google.firebase:firebase-bom", version.ref = "firebaseBom" } +firebase-database = { module = "com.google.firebase:firebase-database", version.ref = "firebaseDatabase" } + +# Third Party +easypermissions = { group = "pub.devrel", name = "easypermissions", version.ref = "easypermissions" } +volley = { group = "com.android.volley", name = "volley", version.ref = "volley" } [plugins] -android-application = { id = "com.android.application", version.ref = "gradle" } -android-library = { id = "com.android.library", version.ref = "gradle" } +android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" } +android-library = { id = "com.android.library", version.ref = "androidGradlePlugin" } +hilt-android = { id = "com.google.dagger.hilt.android", version.ref = "hilt" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } -jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } -kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" } -hilt-android = { id = "com.google.dagger.hilt.android", version.ref = "hilt" } +kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } +ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } secrets-gradle-plugin = { id = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin", version.ref = "secretsGradlePlugin" } -ksp = { id = "com.google.devtools.ksp", version = "2.2.20-2.0.4" } diff --git a/scripts/update_docs_versions.py b/scripts/update_docs_versions.py new file mode 100755 index 000000000..f6b0ac5fb --- /dev/null +++ b/scripts/update_docs_versions.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 +# +# Copyright 2026 Google LLC +# +# 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. +# + +import argparse +import re +import sys +from pathlib import Path + +def parse_versions_toml(toml_path): + versions = {} + with open(toml_path, 'r') as f: + for line in f: + # Simple regex to find string versions: name = "version" + match = re.match(r'^\s*(\w+)\s*=\s*"([^"]+)"', line) + if match: + versions[match.group(1)] = match.group(2) + return versions + +def update_file(file_path, versions, check_only=False): + with open(file_path, 'r') as f: + content = f.read() + + # Define replacements: (regex, replacement_template) + # matching: implementation(libs.foo) // com.group:artifact:VERSION + + replacements = [ + ( + r'(compileOnly\(libs\.wearable\.compile\)\s*//\s*com\.google\.android\.wearable:wearable:)([\d\.]+)', + f'\\g<1>{versions.get("wearable", "UNKNOWN")}' + ), + ( + r'(implementation\(libs\.wearable\.support\)\s*//\s*com\.google\.android\.support:wearable:)([\d\.]+)', + f'\\g<1>{versions.get("wearable", "UNKNOWN")}' + ), + ( + r'(implementation\(libs\.play\.services\.maps\)\s*//\s*com\.google\.android\.gms:play-services-maps:)([\d\.]+)', + f'\\g<1>{versions.get("playServicesMaps", "UNKNOWN")}' + ), + # Replacements for the "How-To" comment block + ( + r'(//\s*wearable\s*=\s*")([\d\.]+)', + f'\\g<1>{versions.get("wearable", "UNKNOWN")}' + ), + ( + r'(//\s*playServicesMaps\s*=\s*")([\d\.]+)', + f'\\g<1>{versions.get("playServicesMaps", "UNKNOWN")}' + ), + ( + r'(//\s*wear\s*=\s*")([\d\.]+)', + f'\\g<1>{versions.get("wear", "UNKNOWN")}' + ) + ] + + new_content = content + for pattern, replacement in replacements: + new_content = re.sub(pattern, replacement, new_content) + + if new_content != content: + if check_only: + print(f"ERROR: {file_path} is out of sync with libs.versions.toml.") + print("Run 'python3 scripts/update_docs_versions.py' to update it.") + return False + else: + with open(file_path, 'w') as f: + f.write(new_content) + print(f"Updated {file_path}") + return True + + if check_only: + print(f"SUCCESS: {file_path} is in sync.") + return True + +def main(): + parser = argparse.ArgumentParser(description='Sync documentation versions with Version Catalog') + parser.add_argument('--check', action='store_true', help='Check if files are up to date without modifying them') + args = parser.parse_args() + + root_dir = Path(__file__).resolve().parent.parent + toml_path = root_dir / 'gradle' / 'libs.versions.toml' + target_file = root_dir / 'WearOS' / 'Wearable' / 'build.gradle.kts' + + if not toml_path.exists(): + print(f"Error: {toml_path} not found") + sys.exit(1) + + versions = parse_versions_toml(toml_path) + if 'wearable' not in versions or 'playServicesMaps' not in versions: + print("Error: Could not find required versions in libs.versions.toml") + sys.exit(1) + + success = update_file(target_file, versions, check_only=args.check) + + if args.check and not success: + sys.exit(1) + +if __name__ == '__main__': + main() diff --git a/scripts/verify_all.sh b/scripts/verify_all.sh new file mode 100755 index 000000000..dab8c3df2 --- /dev/null +++ b/scripts/verify_all.sh @@ -0,0 +1,97 @@ +#!/bin/bash + +# Copyright 2026 Google LLC +# +# 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. + +# script: verify_all.sh +# description: Builds and verifies all Android modules in the project. + +set -e # Exit immediately if a command exits with a non-zero status. + +# Colors for output +GREEN='\033[0;32m' +RED='\033[0;31m' +NC='\033[0m' # No Color + + +echo "Checking documentation versions sync..." +python3 scripts/update_docs_versions.py --check || { + echo "FAILURE: Documentation versions are out of sync with Version Catalog." + echo "Run 'python3 scripts/update_docs_versions.py' to fix." + exit 1 +} + +echo "Starting project verification..." + +# List of modules to check +# Note: snippets has submodules, checking root snippets project might be enough if it aggregates them, +# but we'll be explicit or use standard tasks. +MODULES=( + ":ApiDemos:java-app" + ":ApiDemos:kotlin-app" + ":ApiDemos:common-ui" + ":FireMarkers:app" + ":WearOS:Wearable" + ":snippets:app" + ":snippets:app-ktx" + ":snippets:app-utils-ktx" + ":snippets:app-compose" + ":snippets:app-places-ktx" + ":snippets:app-utils" + ":tutorials:kotlin:Polygons" +) + +# Function to run verification for a module +verify_module() { + local module=$1 + echo "------------------------------------------------" + # Determine variant-specific tasks + local assembleTask=":assembleDebug" + local testTask=":testDebugUnitTest" + local lintTask=":lintDebug" + + if [[ "$module" == ":snippets:app" ]]; then + assembleTask=":assembleGmsDebug" + testTask=":testGmsDebugUnitTest" + lintTask=":lintGmsDebug" + fi + + # Run assemble, lint, and test + if ./gradlew "$module$assembleTask" "$module$testTask" "$module$lintTask"; then + echo -e "${GREEN}SUCCESS: $module verified.${NC}" + else + echo -e "${RED}FAILURE: $module failed verification.${NC}" + return 1 + fi +} + +FAILED_MODULES=() + +for module in "${MODULES[@]}"; do + if ! verify_module "$module"; then + FAILED_MODULES+=("$module") + fi +done + +echo "------------------------------------------------" +if [ ${#FAILED_MODULES[@]} -eq 0 ]; then + echo -e "${GREEN}ALL MODULES PASSED VERIFICATION.${NC}" + exit 0 +else + echo -e "${RED}THE FOLLOWING MODULES FAILED:${NC}" + for failed in "${FAILED_MODULES[@]}"; do + echo -e "${RED}- $failed${NC}" + done + exit 1 +fi diff --git a/settings.gradle.kts b/settings.gradle.kts index 378a16b1a..af2968cd4 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -26,7 +26,7 @@ pluginManagement { // [START maps_android_settings_dependency_resolution_management] dependencyResolutionManagement { - repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS) repositories { google() mavenCentral() @@ -63,8 +63,6 @@ include(":snippets:app-compose") project(":snippets:app-compose").projectDir = file("snippets/app-compose") include(":snippets:app-places-ktx") project(":snippets:app-places-ktx").projectDir = file("snippets/app-places-ktx") -include(":snippets:app-rx") -project(":snippets:app-rx").projectDir = file("snippets/app-rx") include(":snippets:app-utils") project(":snippets:app-utils").projectDir = file("snippets/app-utils") diff --git a/snippets/app-compose/build.gradle.kts b/snippets/app-compose/build.gradle.kts index 9325cb42d..5573bf9a7 100644 --- a/snippets/app-compose/build.gradle.kts +++ b/snippets/app-compose/build.gradle.kts @@ -16,7 +16,7 @@ plugins { alias(libs.plugins.android.application) - alias(libs.plugins.jetbrains.kotlin.android) + alias(libs.plugins.kotlin.android) alias(libs.plugins.kotlin.compose) alias(libs.plugins.secrets.gradle.plugin) } diff --git a/snippets/app-ktx/build.gradle.kts b/snippets/app-ktx/build.gradle.kts index 321c26d53..42904cb7c 100644 --- a/snippets/app-ktx/build.gradle.kts +++ b/snippets/app-ktx/build.gradle.kts @@ -16,7 +16,7 @@ plugins { alias(libs.plugins.android.application) - alias(libs.plugins.jetbrains.kotlin.android) + alias(libs.plugins.kotlin.android) alias(libs.plugins.secrets.gradle.plugin) } diff --git a/snippets/app-places-ktx/build.gradle.kts b/snippets/app-places-ktx/build.gradle.kts index 79c975622..d7c137612 100644 --- a/snippets/app-places-ktx/build.gradle.kts +++ b/snippets/app-places-ktx/build.gradle.kts @@ -16,7 +16,7 @@ plugins { alias(libs.plugins.android.application) - alias(libs.plugins.jetbrains.kotlin.android) + alias(libs.plugins.kotlin.android) alias(libs.plugins.secrets.gradle.plugin) } diff --git a/snippets/app-utils-ktx/build.gradle.kts b/snippets/app-utils-ktx/build.gradle.kts index 28ef62beb..489511342 100644 --- a/snippets/app-utils-ktx/build.gradle.kts +++ b/snippets/app-utils-ktx/build.gradle.kts @@ -16,7 +16,7 @@ plugins { alias(libs.plugins.android.application) - alias(libs.plugins.jetbrains.kotlin.android) + alias(libs.plugins.kotlin.android) alias(libs.plugins.secrets.gradle.plugin) } diff --git a/snippets/app-utils/build.gradle.kts b/snippets/app-utils/build.gradle.kts index edfd49c95..482b68e7d 100644 --- a/snippets/app-utils/build.gradle.kts +++ b/snippets/app-utils/build.gradle.kts @@ -16,7 +16,7 @@ plugins { alias(libs.plugins.android.application) - alias(libs.plugins.jetbrains.kotlin.android) + alias(libs.plugins.kotlin.android) alias(libs.plugins.secrets.gradle.plugin) } diff --git a/snippets/app/build.gradle.kts b/snippets/app/build.gradle.kts index c5667a783..dce825fdb 100644 --- a/snippets/app/build.gradle.kts +++ b/snippets/app/build.gradle.kts @@ -18,7 +18,7 @@ plugins { // [START_EXCLUDE] alias(libs.plugins.android.application) - alias(libs.plugins.jetbrains.kotlin.android) + alias(libs.plugins.kotlin.android) // [END_EXCLUDE] alias(libs.plugins.secrets.gradle.plugin) } diff --git a/snippets/build.gradle.kts b/snippets/build.gradle.kts index c0ff6867e..7c6c417cb 100644 --- a/snippets/build.gradle.kts +++ b/snippets/build.gradle.kts @@ -17,7 +17,7 @@ // [START maps_android_secrets_gradle_plugin_project_level_config] plugins { alias(libs.plugins.android.application) apply false - alias(libs.plugins.jetbrains.kotlin.android) apply false + alias(libs.plugins.kotlin.android) apply false alias(libs.plugins.kotlin.compose) apply false alias(libs.plugins.secrets.gradle.plugin) apply false }