From 1582916cb9dfd3c4041d0c0f06c8d9fa2c47ac51 Mon Sep 17 00:00:00 2001
From: dkhawk <107309+dkhawk@users.noreply.github.com>
Date: Fri, 23 Jan 2026 17:00:10 -0700
Subject: [PATCH 01/14] feat!: update Maps SDK to v20.0.0 and add internal
usage attribution
Updates the Maps SDK dependencies and implements internal usage attribution using androidx.startup.
Changes:
- Replaced manual initialization with AttributionIdInitializer using androidx.startup.
- Removed GoogleMapsInitializer and MapsComposeApplication (Breaking Change).
- Updated README.md with attribution opt-out instructions.
- Added installAndLaunch Gradle task for easier demo app testing.
- Fixed ScaleBarTests density calculation.
- Updated minSdk overrides for test dependencies.
Breaking Changes:
- GoogleMapsInitializer and MapsComposeApplication have been removed. Initialization is now handled automatically by androidx.startup.
- minSdk is now 23.
---
README.md | 19 +-
build.gradle.kts | 7 +
gradle/libs.versions.toml | 22 +-
maps-app/src/androidTest/AndroidManifest.xml | 6 +
.../android/compose/GoogleMapViewTests.kt | 8 -
.../maps/android/compose/ScaleBarTests.kt | 8 +-
.../internal/GoogleMapsInitializerTest.kt | 235 ------------------
maps-app/src/main/AndroidManifest.xml | 1 -
.../android/compose/MapsComposeApplication.kt | 40 ---
.../internal/GoogleMapsInitializerTest.kt | 79 ------
maps-compose/build.gradle.kts | 3 +-
maps-compose/src/main/AndroidManifest.xml | 17 +-
.../google/maps/android/compose/GoogleMap.kt | 37 +--
.../compose/internal/GoogleMapsInitializer.kt | 195 ---------------
.../attribution/AttributionIdInitializer.kt | 40 +++
15 files changed, 116 insertions(+), 601 deletions(-)
create mode 100644 maps-app/src/androidTest/AndroidManifest.xml
delete mode 100644 maps-app/src/androidTest/java/com/google/maps/android/compose/internal/GoogleMapsInitializerTest.kt
delete mode 100644 maps-app/src/main/java/com/google/maps/android/compose/MapsComposeApplication.kt
delete mode 100644 maps-app/src/test/java/com/google/maps/android/compose/internal/GoogleMapsInitializerTest.kt
delete mode 100644 maps-compose/src/main/java/com/google/maps/android/compose/internal/GoogleMapsInitializer.kt
create mode 100644 maps-compose/src/main/java/com/google/maps/android/compose/utils/attribution/AttributionIdInitializer.kt
diff --git a/README.md b/README.md
index c42ee1e6..ad75ab4d 100644
--- a/README.md
+++ b/README.md
@@ -497,10 +497,21 @@ The colors of the text, line, and shadow are also all configurable (e.g., based
## Internal usage attribution ID
-This library calls the MapsApiSettings.addInternalUsageAttributionId method, which helps Google
-understand which libraries and samples are helpful to developers and is optional. Instructions for
-opting out of the identifier are provided in
-[reference documentation](maps-compose/src/main/java/com/google/maps/android/compose/internal/GoogleMapsInitializer.kt#L77-L82).
+This library calls the `addInternalUsageAttributionId` method, which helps Google understand which libraries and samples are helpful to developers and is optional. Instructions for opting out of the identifier are provided below.
+
+If you wish to disable this, you can do so by removing the initializer in your `AndroidManifest.xml` using the `tools:node="remove"` attribute:
+
+```xml
+
+
+
+```
## Contributing
diff --git a/build.gradle.kts b/build.gradle.kts
index e9c36c88..7e21ee67 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -35,4 +35,11 @@ allprojects {
// {x-release-please-start-version}
version = "7.0.0"
// {x-release-please-end}
+}
+
+tasks.register("installAndLaunch") {
+ description = "Installs and launches the demo app."
+ group = "install"
+ dependsOn(":maps-app:installDebug")
+ commandLine("adb", "shell", "am", "start", "-n", "com.google.maps.android.compose/.MainActivity")
}
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 0b3f816b..2ae6ee2c 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,30 +1,31 @@
[versions]
-activitycompose = "1.12.1"
-agp = "8.13.1"
+activitycompose = "1.12.2"
+agp = "8.13.2"
androidCore = "1.7.0"
androidx-core = "1.17.0"
androidxtest = "1.7.0"
-compose-bom = "2025.12.00"
+androidx-startup = "1.2.0"
+compose-bom = "2026.01.00"
dokka = "2.1.0"
espresso = "3.7.0"
-gradleMavenPublishPlugin = "0.35.0"
+gradleMavenPublishPlugin = "0.36.0"
jacoco-plugin = "0.2.1"
junit = "4.13.2"
junitktx = "1.3.0"
-kotlin = "2.2.21"
+kotlin = "2.3.0"
kotlinxCoroutines = "1.10.2"
leakcanaryAndroid = "2.14"
mapsecrets = "2.0.1"
-mapsktx = "5.2.1"
+mapsktx = "5.2.2"
material3 = "1.4.0"
materialIconsExtendedAndroid = "1.7.8"
-mockk = "1.14.6"
-mockkAndroid = "1.14.6"
+mockk = "1.14.7"
+mockkAndroid = "1.14.7"
org-jacoco-core = "0.8.14"
-screenshot = "0.0.1-alpha12"
+screenshot = "0.0.1-alpha13"
constraintlayout = "2.2.1"
material = "1.13.0"
-robolectric = "4.16"
+robolectric = "4.16.1"
truth = "1.4.5"
[libraries]
@@ -40,6 +41,7 @@ androidx-compose-ui-preview-tooling = { module = "androidx.compose.ui:ui-tooling
androidx-compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling" }
androidx-core = { module = "androidx.core:core-ktx", version.ref = "androidx-core" }
androidx-test-compose-ui = { module = "androidx.compose.ui:ui-test-junit4" }
+androidx-startup-runtime = { module = "androidx.startup:startup-runtime", version.ref = "androidx-startup" }
androidx-test-core = { module = "androidx.test:core", version.ref = "androidCore" }
androidx-test-espresso = { module = "androidx.test.espresso:espresso-core", version.ref = "espresso" }
androidx-test-junit-ktx = { module = "androidx.test.ext:junit-ktx", version.ref = "junitktx" }
diff --git a/maps-app/src/androidTest/AndroidManifest.xml b/maps-app/src/androidTest/AndroidManifest.xml
new file mode 100644
index 00000000..6fae52d7
--- /dev/null
+++ b/maps-app/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
diff --git a/maps-app/src/androidTest/java/com/google/maps/android/compose/GoogleMapViewTests.kt b/maps-app/src/androidTest/java/com/google/maps/android/compose/GoogleMapViewTests.kt
index 318d270a..abdd2a46 100644
--- a/maps-app/src/androidTest/java/com/google/maps/android/compose/GoogleMapViewTests.kt
+++ b/maps-app/src/androidTest/java/com/google/maps/android/compose/GoogleMapViewTests.kt
@@ -31,8 +31,6 @@ import com.google.android.gms.maps.model.CameraPosition
import com.google.android.gms.maps.model.LatLng
import com.google.common.truth.Truth.assertThat
import com.google.maps.android.compose.LatLngSubject.Companion.assertThat
-import com.google.maps.android.compose.internal.DefaultGoogleMapsInitializer
-import com.google.maps.android.compose.internal.InitializationState
import kotlinx.coroutines.runBlocking
import org.junit.Before
import org.junit.Rule
@@ -54,13 +52,7 @@ class GoogleMapViewTests {
val countDownLatch = CountDownLatch(1)
val appContext: Context = InstrumentationRegistry.getInstrumentation().targetContext
- val googleMapsInitializer = DefaultGoogleMapsInitializer()
- runBlocking {
- googleMapsInitializer.initialize(appContext)
- }
-
- assertThat(googleMapsInitializer.state.value).isEqualTo(InitializationState.SUCCESS)
composeTestRule.setContent {
GoogleMapView(
diff --git a/maps-app/src/androidTest/java/com/google/maps/android/compose/ScaleBarTests.kt b/maps-app/src/androidTest/java/com/google/maps/android/compose/ScaleBarTests.kt
index efe2d22e..fe55c3a5 100644
--- a/maps-app/src/androidTest/java/com/google/maps/android/compose/ScaleBarTests.kt
+++ b/maps-app/src/androidTest/java/com/google/maps/android/compose/ScaleBarTests.kt
@@ -16,8 +16,10 @@ package com.google.maps.android.compose
import android.graphics.Point
import androidx.compose.foundation.layout.Box
+import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.dp
import com.google.android.gms.maps.model.CameraPosition
import com.google.android.gms.maps.model.LatLng
@@ -44,6 +46,7 @@ class ScaleBarTests {
val composeTestRule = createComposeRule()
private lateinit var cameraPositionState: CameraPositionState
+ private lateinit var density: Density
private fun initScaleBar(initialZoom: Float, initialPosition: LatLng) {
check(hasValidApiKey) { "Maps API key not specified" }
@@ -55,6 +58,7 @@ class ScaleBarTests {
)
composeTestRule.setContent {
+ density = LocalDensity.current
Box {
GoogleMap(
cameraPositionState = cameraPositionState,
@@ -87,7 +91,9 @@ class ScaleBarTests {
val projection = cameraPositionState.projection
projection?.let { proj ->
val widthInDp = 65.dp
- val widthInPixels = widthInDp.value.toInt()
+ val widthInPixels = with(density) {
+ widthInDp.toPx().toInt()
+ }
val upperLeftLatLng = proj.fromScreenLocation(Point(0, 0))
val upperRightLatLng = proj.fromScreenLocation(Point(0, widthInPixels))
diff --git a/maps-app/src/androidTest/java/com/google/maps/android/compose/internal/GoogleMapsInitializerTest.kt b/maps-app/src/androidTest/java/com/google/maps/android/compose/internal/GoogleMapsInitializerTest.kt
deleted file mode 100644
index 5408badf..00000000
--- a/maps-app/src/androidTest/java/com/google/maps/android/compose/internal/GoogleMapsInitializerTest.kt
+++ /dev/null
@@ -1,235 +0,0 @@
-// Copyright 2025 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.
-package com.google.maps.android.compose.internal
-
-import android.content.Context
-import android.os.StrictMode
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.platform.app.InstrumentationRegistry
-import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.test.runTest
-import org.junit.After
-import org.junit.Test
-import org.junit.runner.RunWith
-import kotlin.time.Duration.Companion.milliseconds
-import com.google.android.gms.common.ConnectionResult
-import com.google.android.gms.common.GooglePlayServicesMissingManifestValueException
-import com.google.android.gms.maps.MapsInitializer
-import com.google.android.gms.maps.MapsApiSettings
-import io.mockk.coEvery
-import io.mockk.coVerify
-import io.mockk.mockkStatic
-import io.mockk.Runs
-import io.mockk.just
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@RunWith(AndroidJUnit4::class)
-class GoogleMapsInitializerTest {
-
- private val googleMapsInitializer = DefaultGoogleMapsInitializer()
-
- @After
- fun tearDown() = runTest {
- googleMapsInitializer.reset()
- }
-
- @Test
- fun testInitializationSuccess() = runTest {
- // In an instrumentation test environment, Google Play services are available.
- // Therefore, we expect the initialization to succeed.
-
- val context: Context = InstrumentationRegistry.getInstrumentation().targetContext
-
- // Note: we need to establish the Strict Mode settings here as there are violations outside
- // of our control if we try to set them in setUp
- val threadPolicy = StrictMode.getThreadPolicy()
- val vmPolicy = StrictMode.getVmPolicy()
-
- StrictMode.setThreadPolicy(
- StrictMode.ThreadPolicy.Builder()
- .detectDiskReads()
- .detectAll()
- .penaltyLog()
- .penaltyDeath()
- .build()
- )
- StrictMode.setVmPolicy(
- StrictMode.VmPolicy.Builder()
- .detectAll()
- .detectLeakedClosableObjects()
- .penaltyLog()
- .penaltyDeath()
- .build()
- )
-
- googleMapsInitializer.initialize(context)
-
- StrictMode.setThreadPolicy(threadPolicy)
- StrictMode.setVmPolicy(vmPolicy)
-
- assertThat(googleMapsInitializer.state.value).isEqualTo(InitializationState.SUCCESS)
- }
-
- @Test
- fun testInitializationCancellationLeavesStateUninitialized() = runTest {
- // In an instrumentation test environment, Google Play services are available.
- // Therefore, we expect the initialization to succeed.
-
- val context: Context = InstrumentationRegistry.getInstrumentation().targetContext
-
- // Note: we need to establish the Strict Mode settings here as there are violations outside
- // of our control if we try to set them in setUp
- val threadPolicy = StrictMode.getThreadPolicy()
- val vmPolicy = StrictMode.getVmPolicy()
-
- StrictMode.setThreadPolicy(
- StrictMode.ThreadPolicy.Builder()
- .detectDiskReads()
- .detectAll()
- .penaltyLog()
- .penaltyDeath()
- .build()
- )
- StrictMode.setVmPolicy(
- StrictMode.VmPolicy.Builder()
- .detectAll()
- .detectLeakedClosableObjects()
- .penaltyLog()
- .penaltyDeath()
- .build()
- )
-
- val job = launch {
- googleMapsInitializer.reset()
- googleMapsInitializer.initialize(context)
- }
-
- // Allow the initialization coroutine to start before we cancel it.
- delay(1.milliseconds)
- job.cancel()
- job.join()
-
- StrictMode.setThreadPolicy(threadPolicy)
- StrictMode.setVmPolicy(vmPolicy)
-
- assertThat(googleMapsInitializer.state.value).isEqualTo(InitializationState.UNINITIALIZED)
- }
-
- @Test
- fun testInitializeSuccessState() = runTest {
- // Arrange
- mockkStatic(MapsInitializer::class)
- assertThat(googleMapsInitializer.state.value).isEqualTo(InitializationState.UNINITIALIZED)
-
- coEvery { MapsInitializer.initialize(any()) } returns ConnectionResult.SUCCESS
-
- val context: Context = InstrumentationRegistry.getInstrumentation().targetContext
- // Act
- // Direct call pattern matching original successful test structure
- googleMapsInitializer.initialize(context)
-
- // Assert
- assertThat(googleMapsInitializer.state.value).isEqualTo(InitializationState.SUCCESS)
- coVerify(exactly = 1) { MapsInitializer.initialize(
- eq(context),
- any(),
- any(),
- )}
- }
-
- @Test
- fun testInitializeConcurrentCallsOnlyRunOnce() = runTest {
- mockkStatic(MapsInitializer::class)
- coEvery { MapsInitializer.initialize(any()) } returns ConnectionResult.SUCCESS
-
- val context: Context = InstrumentationRegistry.getInstrumentation().targetContext
- val job1 = launch { googleMapsInitializer.initialize(context) }
- val job2 = launch { googleMapsInitializer.initialize(context) }
-
- job1.join()
- job2.join()
-
- // Assert: The actual initialization method should only have been called once
- coVerify(exactly = 1) { MapsInitializer.initialize(
- eq(context),
- any(),
- any(),
- )}
- assertThat(googleMapsInitializer.state.value).isEqualTo(InitializationState.SUCCESS)
- }
-
- @Test
- fun testInitializeUnrecoverableFailureSetsFailureState() = runTest {
- // Arrange
- mockkStatic(MapsInitializer::class)
- val error = GooglePlayServicesMissingManifestValueException()
-
- val context: Context = InstrumentationRegistry.getInstrumentation().targetContext
- var caughtException: Throwable? = null
-
- coEvery {
- MapsInitializer.initialize(
- eq(context),
- isNull(),
- any()
- )
- } throws error
-
- // Act
- val job = launch {
- try {
- googleMapsInitializer.initialize(context)
- } catch (e: GooglePlayServicesMissingManifestValueException) {
- caughtException = e
- }
- }
- job.join()
-
- // Assert: The exception was caught, and the state became FAILURE
- assertThat(caughtException).isInstanceOf(GooglePlayServicesMissingManifestValueException::class.java)
- assertThat(caughtException).isEqualTo(error)
-
- // 2. Assert the state was set to FAILURE
- assertThat(googleMapsInitializer.state.value).isEqualTo(InitializationState.FAILURE)
- }
-
- @Test
- fun testInitializeSuccessAlsoSetsAttributionId() = runTest {
- // Arrange: Mock MapsApiSettings locally
- mockkStatic(MapsInitializer::class, MapsApiSettings::class)
-
- coEvery { MapsInitializer.initialize(any()) } returns ConnectionResult.SUCCESS
- coEvery { MapsApiSettings.addInternalUsageAttributionId(any(), any()) } just Runs
-
- val context: Context = InstrumentationRegistry.getInstrumentation().targetContext
-
- // Act
- // Direct call pattern matching original successful test structure
- googleMapsInitializer.initialize(context)
-
- // Assert: Verify both the primary initialization and the attribution call occurred
- coVerify(exactly = 1) {
- MapsInitializer.initialize(
- eq(context),
- any(),
- any(),
- )
- }
- coVerify(exactly = 1) { MapsApiSettings.addInternalUsageAttributionId(any(), any()) }
- assertThat(googleMapsInitializer.state.value).isEqualTo(InitializationState.SUCCESS)
- }
-}
\ No newline at end of file
diff --git a/maps-app/src/main/AndroidManifest.xml b/maps-app/src/main/AndroidManifest.xml
index e7af6d8e..e6904543 100644
--- a/maps-app/src/main/AndroidManifest.xml
+++ b/maps-app/src/main/AndroidManifest.xml
@@ -19,7 +19,6 @@
{
- googleMapsInitializer.initialize(mockContext)
- }
- assertEquals(InitializationState.UNINITIALIZED, googleMapsInitializer.state.value)
- }
-}
diff --git a/maps-compose/build.gradle.kts b/maps-compose/build.gradle.kts
index b3872e13..8d601503 100644
--- a/maps-compose/build.gradle.kts
+++ b/maps-compose/build.gradle.kts
@@ -60,6 +60,7 @@ dependencies {
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.core)
implementation(libs.androidx.compose.foundation)
+ implementation(libs.androidx.startup.runtime)
implementation(libs.kotlin)
implementation(libs.kotlinx.coroutines.android)
api(libs.maps.ktx.std)
@@ -76,7 +77,7 @@ val attributionId = "gmp_git_androidmapscompose_v$version"
val generateArtifactIdFile = tasks.register("generateArtifactIdFile") {
val outputDir = layout.buildDirectory.dir("generated/source/artifactId")
- val packageName = "com.google.maps.android.compose.meta"
+ val packageName = "com.google.maps.android.compose.utils.meta"
val packagePath = packageName.replace('.', '/')
val outputFile = outputDir.get().file("$packagePath/ArtifactId.kt").asFile
diff --git a/maps-compose/src/main/AndroidManifest.xml b/maps-compose/src/main/AndroidManifest.xml
index e67f88c1..d503585c 100644
--- a/maps-compose/src/main/AndroidManifest.xml
+++ b/maps-compose/src/main/AndroidManifest.xml
@@ -15,4 +15,19 @@
limitations under the License.
-->
-
+
+
+
+
+
+
+
+
+
diff --git a/maps-compose/src/main/java/com/google/maps/android/compose/GoogleMap.kt b/maps-compose/src/main/java/com/google/maps/android/compose/GoogleMap.kt
index 5c2be9d3..d0266903 100644
--- a/maps-compose/src/main/java/com/google/maps/android/compose/GoogleMap.kt
+++ b/maps-compose/src/main/java/com/google/maps/android/compose/GoogleMap.kt
@@ -50,8 +50,7 @@ import com.google.android.gms.maps.MapView
import com.google.android.gms.maps.model.LatLng
import com.google.android.gms.maps.model.MapColorScheme
import com.google.android.gms.maps.model.PointOfInterest
-import com.google.maps.android.compose.internal.InitializationState
-import com.google.maps.android.compose.internal.LocalGoogleMapsInitializer
+
import com.google.maps.android.ktx.awaitMap
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart
@@ -113,31 +112,18 @@ public fun GoogleMap(
return
}
- val googleMapsInitializer = LocalGoogleMapsInitializer.current
- val initializationState by googleMapsInitializer.state
-
- if (initializationState != InitializationState.SUCCESS) {
- val context = LocalContext.current
- LaunchedEffect(Unit) {
- // Coroutine to initialize Google Maps SDK.
- // This will run once when the composable is first displayed.
- googleMapsInitializer.initialize(context)
- }
+ // rememberUpdatedState and friends are used here to make these values observable to
+ // the subcomposition without providing a new content function each recomposition
+ val mapClickListeners = remember { MapClickListeners() }.also {
+ it.indoorStateChangeListener = indoorStateChangeListener
+ it.onMapClick = onMapClick
+ it.onMapLongClick = onMapLongClick
+ it.onMapLoaded = onMapLoaded
+ it.onMyLocationButtonClick = onMyLocationButtonClick
+ it.onMyLocationClick = onMyLocationClick
+ it.onPOIClick = onPOIClick
}
- if (initializationState == InitializationState.SUCCESS) {
- // rememberUpdatedState and friends are used here to make these values observable to
- // the subcomposition without providing a new content function each recomposition
- val mapClickListeners = remember { MapClickListeners() }.also {
- it.indoorStateChangeListener = indoorStateChangeListener
- it.onMapClick = onMapClick
- it.onMapLongClick = onMapLongClick
- it.onMapLoaded = onMapLoaded
- it.onMyLocationButtonClick = onMyLocationButtonClick
- it.onMyLocationClick = onMyLocationClick
- it.onPOIClick = onPOIClick
- }
-
val mapUpdaterState = remember {
MapUpdaterState(
mergeDescendants,
@@ -228,7 +214,6 @@ public fun GoogleMap(
)
}
})
- }
}
/**
diff --git a/maps-compose/src/main/java/com/google/maps/android/compose/internal/GoogleMapsInitializer.kt b/maps-compose/src/main/java/com/google/maps/android/compose/internal/GoogleMapsInitializer.kt
deleted file mode 100644
index 6107f68c..00000000
--- a/maps-compose/src/main/java/com/google/maps/android/compose/internal/GoogleMapsInitializer.kt
+++ /dev/null
@@ -1,195 +0,0 @@
-// Copyright 2025 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.
-
-package com.google.maps.android.compose.internal
-
-import android.content.Context
-import android.os.StrictMode
-import androidx.compose.runtime.ProvidableCompositionLocal
-import androidx.compose.runtime.State
-import androidx.compose.runtime.compositionLocalOf
-import androidx.compose.runtime.mutableStateOf
-import com.google.android.gms.common.ConnectionResult
-import com.google.android.gms.maps.MapsInitializer
-import com.google.android.gms.maps.MapsApiSettings
-import com.google.maps.android.compose.meta.AttributionId
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.sync.Mutex
-import kotlinx.coroutines.sync.withLock
-import kotlinx.coroutines.withContext
-
-/**
- * Enum representing the initialization state of the Google Maps SDK.
- */
-public enum class InitializationState {
- /**
- * The SDK has not been initialized.
- */
- UNINITIALIZED,
-
- /**
- * The SDK is currently being initialized.
- */
- INITIALIZING,
-
- /**
- * The SDK has been successfully initialized.
- */
- SUCCESS,
-
- /**
- * The SDK initialization failed.
- */
- FAILURE
-}
-
-/**
- * A singleton object to manage the initialization of the Google Maps SDK.
- *
- * This object provides a state machine to track the initialization process and ensures that
- * the initialization is performed only once. It also provides a mechanism to reset the
- * initialization state, which can be useful in test environments.
- *
- * The initialization process consists of two main steps:
- * 1. Calling `MapsInitializer.initialize(context)` to initialize the Google Maps SDK.
- * 2. Calling `MapsApiSettings.addInternalUsageAttributionId(context, attributionId)` to add
- * the library's attribution ID to the Maps API settings.
- *
- * The state of the initialization is exposed via the `state` property, which is a [State] object
- * that can be observed for changes.
- */
-public interface GoogleMapsInitializer {
- public val state: State
-
- /**
- * The value of the attribution ID. Set this to the empty string to opt out of attribution.
- *
- * This must be set before calling the `initialize` function.
- */
- public var attributionId: String
-
- /**
- * Initializes Google Maps. This function must be called before using any other
- * functions in this library.
- *
- * If initialization fails with a recoverable error (e.g., a network issue),
- * the state will be reset to [InitializationState.UNINITIALIZED], allowing for a
- * subsequent retry. In the case of an unrecoverable error (e.g., a missing
- * manifest value), the state will be set to [InitializationState.FAILURE] and the
- * original exception will be re-thrown.
- *
- * @param context The context to use for initialization.
- * @param forceInitialization When true, initialization will be attempted even if it
- * has already succeeded or is in progress. This is useful for retrying a
- * previously failed initialization.
- */
- public suspend fun initialize(
- context: Context,
- forceInitialization: Boolean = false,
- )
-
- /**
- * Resets the initialization state.
- *
- * This function cancels any ongoing initialization and resets the state to `UNINITIALIZED`.
- * This is primarily useful in test environments where the SDK might need to be
- * re-initialized multiple times.
- */
- public suspend fun reset()
-}
-
-/**
- * The default implementation of [GoogleMapsInitializer].
- *
- * @param ioDispatcher The dispatcher to use for IO operations.
- */
-public class DefaultGoogleMapsInitializer(
- private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO,
- private val mainDispatcher: CoroutineDispatcher = Dispatchers.Main,
-) : GoogleMapsInitializer {
- private val _state = mutableStateOf(InitializationState.UNINITIALIZED)
- override val state: State = _state
-
- private val mutex = Mutex()
-
- override var attributionId: String = AttributionId.VALUE
-
- override suspend fun initialize(
- context: Context,
- forceInitialization: Boolean,
- ) {
- try {
- if (!forceInitialization &&
- (_state.value == InitializationState.INITIALIZING || _state.value == InitializationState.SUCCESS)
- ) {
- return
- }
-
- mutex.withLock {
- if (_state.value != InitializationState.UNINITIALIZED) {
- return
- }
- _state.value = InitializationState.INITIALIZING
- }
-
- withContext(mainDispatcher) {
- val scope = this
-
- val policy = StrictMode.getThreadPolicy()
- try {
- StrictMode.allowThreadDiskReads()
- val result = MapsInitializer.initialize(context, null) {
- scope.launch(ioDispatcher) {
- MapsApiSettings.addInternalUsageAttributionId(context, attributionId)
- _state.value = InitializationState.SUCCESS
- }
- }
-
- if (result != ConnectionResult.SUCCESS) {
- _state.value = InitializationState.FAILURE
- }
- } finally {
- StrictMode.setThreadPolicy(policy)
- }
- }
- } catch (e: com.google.android.gms.common.GooglePlayServicesMissingManifestValueException) {
- // This is an unrecoverable error. Play Services is not available (could be a test?)
- // Set the state to FAILURE to prevent further attempts.
- _state.value = InitializationState.FAILURE
- throw e
- } catch (e: Exception) {
- // This could be a transient error.
- // Reset to UNINITIALIZED to allow for a retry.
- _state.value = InitializationState.UNINITIALIZED
- throw e
- }
- }
-
- override suspend fun reset() {
- mutex.withLock {
- _state.value = InitializationState.UNINITIALIZED
- }
- }
-}
-
-/**
- * CompositionLocal that provides a [GoogleMapsInitializer].
- */
-public val LocalGoogleMapsInitializer: ProvidableCompositionLocal =
- compositionLocalOf {
- // Default implementation of the initializer
- DefaultGoogleMapsInitializer()
- }
diff --git a/maps-compose/src/main/java/com/google/maps/android/compose/utils/attribution/AttributionIdInitializer.kt b/maps-compose/src/main/java/com/google/maps/android/compose/utils/attribution/AttributionIdInitializer.kt
new file mode 100644
index 00000000..d4638371
--- /dev/null
+++ b/maps-compose/src/main/java/com/google/maps/android/compose/utils/attribution/AttributionIdInitializer.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+
+package com.google.maps.android.compose.utils.attribution
+
+import android.content.Context
+import androidx.startup.Initializer
+import com.google.android.gms.maps.MapsApiSettings
+import com.google.maps.android.compose.utils.meta.AttributionId
+
+/**
+ * Adds a usage attribution ID to the initializer, which helps Google understand which libraries
+ * and samples are helpful to developers, such as usage of this library.
+ * To opt out of sending the usage attribution ID, please remove this initializer from your manifest.
+ */
+internal class AttributionIdInitializer : Initializer {
+ override fun create(context: Context) {
+ MapsApiSettings.addInternalUsageAttributionId(
+ /* context = */ context,
+ /* internalUsageAttributionId = */ AttributionId.VALUE
+ )
+ }
+
+ override fun dependencies(): List>> {
+ return emptyList()
+ }
+}
From 30408a4f865aa498ee82be609f62fea29ab71251 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Enrique=20L=C3=B3pez-Ma=C3=B1as?=
Date: Mon, 26 Jan 2026 17:37:57 +0100
Subject: [PATCH 02/14] fix: downgraded the screenshot library
---
gradle/libs.versions.toml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 2ae6ee2c..fe7f0cd3 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -22,7 +22,7 @@ materialIconsExtendedAndroid = "1.7.8"
mockk = "1.14.7"
mockkAndroid = "1.14.7"
org-jacoco-core = "0.8.14"
-screenshot = "0.0.1-alpha13"
+screenshot = "0.0.1-alpha12"
constraintlayout = "2.2.1"
material = "1.13.0"
robolectric = "4.16.1"
@@ -73,4 +73,4 @@ android-application = { id = "com.android.application", version.ref = "agp" }
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
-screenshot = { id = "com.android.compose.screenshot", version.ref = "screenshot"}
\ No newline at end of file
+screenshot = { id = "com.android.compose.screenshot", version.ref = "screenshot"}
From 7ab1b12e90c7e7288ff6edad30cea140c46c7b05 Mon Sep 17 00:00:00 2001
From: dkhawk <107309+dkhawk@users.noreply.github.com>
Date: Mon, 26 Jan 2026 14:28:28 -0700
Subject: [PATCH 03/14] build: pull the Android SDK versions into the version
catalog
---
.gitignore | 3 +-
gradle/libs.versions.toml | 93 +++++++++++++++++----------
maps-app/build.gradle.kts | 8 +--
maps-compose-utils/build.gradle.kts | 4 +-
maps-compose-widgets/build.gradle.kts | 4 +-
maps-compose/build.gradle.kts | 4 +-
6 files changed, 70 insertions(+), 46 deletions(-)
diff --git a/.gitignore b/.gitignore
index 1590fc53..7376cd81 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,4 +21,5 @@ secrets.properties
# This covers new IDEs, like Antigravity
.vscode/
-**/bin/
\ No newline at end of file
+**/bin/BOB_DATA/
+build-logic/**/bin/
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index fe7f0cd3..4b4a2f68 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,71 +1,94 @@
+# This file is organized into functional groups: Base, Android, Material, Google Maps Platform (GMP), and Testing.
+# Please maintain this structure when adding new dependencies or versions.
[versions]
+# Base
+agp = "8.13.2" # Do not upgrade to 9.0.0 yet
+dokka = "2.1.0"
+gradleMavenPublishPlugin = "0.36.0"
+jacoco-plugin = "0.2.1"
+kotlin = "2.3.0"
+kotlinxCoroutines = "1.10.2"
+
+# Android
activitycompose = "1.12.2"
-agp = "8.13.2"
-androidCore = "1.7.0"
+androidCompileSdk = "36"
+androidMinSdk = "23"
+androidTargetSdk = "36"
androidx-core = "1.17.0"
-androidxtest = "1.7.0"
androidx-startup = "1.2.0"
compose-bom = "2026.01.00"
-dokka = "2.1.0"
+constraintlayout = "2.2.1"
+
+# Material
+material = "1.13.0"
+material3 = "1.4.0"
+materialIconsExtendedAndroid = "1.7.8"
+
+# Google Maps Platform
+mapsecrets = "2.0.1"
+mapsktx = "6.0.0"
+
+# Testing
+androidCore = "1.7.0"
+androidxtest = "1.7.0"
espresso = "3.7.0"
-gradleMavenPublishPlugin = "0.36.0"
-jacoco-plugin = "0.2.1"
junit = "4.13.2"
junitktx = "1.3.0"
-kotlin = "2.3.0"
-kotlinxCoroutines = "1.10.2"
leakcanaryAndroid = "2.14"
-mapsecrets = "2.0.1"
-mapsktx = "5.2.2"
-material3 = "1.4.0"
-materialIconsExtendedAndroid = "1.7.8"
-mockk = "1.14.7"
-mockkAndroid = "1.14.7"
+mockk = "1.14.9"
+mockkAndroid = "1.14.9"
org-jacoco-core = "0.8.14"
-screenshot = "0.0.1-alpha12"
-constraintlayout = "2.2.1"
-material = "1.13.0"
robolectric = "4.16.1"
+screenshot = "0.0.1-alpha12" # Do not upgrade to alpha13 (broken)
truth = "1.4.5"
[libraries]
+# Base
android-gradle-plugin = { module = "com.android.tools.build:gradle", version.ref = "agp" }
-androidx-compose-activity = { module = "androidx.activity:activity-compose", version.ref = "activitycompose" }
+dokka-plugin = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "dokka" }
+gradle-maven-publish-plugin = { module = "com.vanniktech:gradle-maven-publish-plugin", version.ref = "gradleMavenPublishPlugin" }
+jacoco-android-plugin = { module = "com.mxalbert.gradle:jacoco-android", version.ref = "jacoco-plugin", version.require = "0.2.1" }
+kotlin = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk7", version.ref = "kotlin" }
+kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
+kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinxCoroutines" }
+
+# Android
+androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "activitycompose" }
androidx-compose-bom = { module = "androidx.compose:compose-bom", version.ref = "compose-bom" }
androidx-compose-foundation = { module = "androidx.compose.foundation:foundation" }
-androidx-compose-material = { module = "androidx.compose.material:material" }
-androidx-compose-material-icons-extended-android = { module = "androidx.compose.material:material-icons-extended-android", version.ref = "materialIconsExtendedAndroid" }
-androidx-compose-material3 = { module = "androidx.compose.material3:material3", version.ref = "material3" }
androidx-compose-ui = { module = "androidx.compose.ui:ui" }
androidx-compose-ui-preview-tooling = { module = "androidx.compose.ui:ui-tooling-preview" }
androidx-compose-ui-tooling = { module = "androidx.compose.ui:ui-tooling" }
+androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
androidx-core = { module = "androidx.core:core-ktx", version.ref = "androidx-core" }
-androidx-test-compose-ui = { module = "androidx.compose.ui:ui-test-junit4" }
androidx-startup-runtime = { module = "androidx.startup:startup-runtime", version.ref = "androidx-startup" }
+
+# Material
+androidx-compose-material = { module = "androidx.compose.material:material" }
+androidx-compose-material-icons-extended-android = { module = "androidx.compose.material:material-icons-extended-android", version.ref = "materialIconsExtendedAndroid" }
+androidx-compose-material3 = { module = "androidx.compose.material3:material3", version.ref = "material3" }
+material = { group = "com.google.android.material", name = "material", version.ref = "material" }
+
+# Google Maps Platform
+maps-ktx-std = { module = "com.google.maps.android:maps-ktx", version.ref = "mapsktx" }
+maps-ktx-utils = { module = "com.google.maps.android:maps-utils-ktx", version.ref = "mapsktx" }
+maps-secrets-plugin = { module = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin", version.ref = "mapsecrets" }
+
+# Testing
+androidx-test-compose-ui = { module = "androidx.compose.ui:ui-test-junit4" }
androidx-test-core = { module = "androidx.test:core", version.ref = "androidCore" }
androidx-test-espresso = { module = "androidx.test.espresso:espresso-core", version.ref = "espresso" }
androidx-test-junit-ktx = { module = "androidx.test.ext:junit-ktx", version.ref = "junitktx" }
androidx-test-rules = { module = "androidx.test:rules", version.ref = "androidCore" }
androidx-test-runner = { module = "androidx.test:runner", version.ref = "androidxtest" }
-dokka-plugin = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "dokka" }
-gradle-maven-publish-plugin = { module = "com.vanniktech:gradle-maven-publish-plugin", version.ref = "gradleMavenPublishPlugin" }
-jacoco-android-plugin = { module = "com.mxalbert.gradle:jacoco-android", version.ref = "jacoco-plugin", version.require = "0.2.1" }
-kotlin = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk7", version.ref = "kotlin" }
-kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
-kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinxCoroutines" }
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinxCoroutines" }
leakcanary-android = { module = "com.squareup.leakcanary:leakcanary-android", version.ref = "leakcanaryAndroid" }
-maps-ktx-std = { module = "com.google.maps.android:maps-ktx", version.ref = "mapsktx" }
-maps-ktx-utils = { module = "com.google.maps.android:maps-utils-ktx", version.ref = "mapsktx" }
-maps-secrets-plugin = { module = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin:secrets-gradle-plugin", version.ref = "mapsecrets" }
mockk = { module = "io.mockk:mockk", version.ref = "mockk" }
mockk-android = { module = "io.mockk:mockk-android", version.ref = "mockkAndroid" }
org-jacoco-core = { module = "org.jacoco:org.jacoco.core", version.ref = "org-jacoco-core" }
-test-junit = { module = "junit:junit", version.ref = "junit" }
-androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
-material = { group = "com.google.android.material", name = "material", version.ref = "material" }
-screenshot-validation-api = { group = "com.android.tools.screenshot", name = "screenshot-validation-api", version.ref = "screenshot" }
robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" }
+screenshot-validation-api = { group = "com.android.tools.screenshot", name = "screenshot-validation-api", version.ref = "screenshot" }
+test-junit = { module = "junit:junit", version.ref = "junit" }
truth = { module = "com.google.truth:truth", version.ref = "truth" }
[plugins]
diff --git a/maps-app/build.gradle.kts b/maps-app/build.gradle.kts
index e437f95a..713346b6 100644
--- a/maps-app/build.gradle.kts
+++ b/maps-app/build.gradle.kts
@@ -25,11 +25,11 @@ android {
}
namespace = "com.google.maps.android.compose"
- compileSdk = 36
+ compileSdk = libs.versions.androidCompileSdk.get().toInt()
defaultConfig {
- minSdk = 23
- targetSdk = 36
+ minSdk = libs.versions.androidMinSdk.get().toInt()
+ targetSdk = libs.versions.androidTargetSdk.get().toInt()
versionCode = 1
versionName = "1.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
@@ -73,7 +73,7 @@ android {
dependencies {
implementation(platform(libs.androidx.compose.bom))
- implementation(libs.androidx.compose.activity)
+ implementation(libs.androidx.activity.compose)
implementation(libs.androidx.compose.foundation)
implementation(libs.androidx.compose.material)
implementation(libs.androidx.compose.material3)
diff --git a/maps-compose-utils/build.gradle.kts b/maps-compose-utils/build.gradle.kts
index 9cbf3ae9..8b5ab8ca 100644
--- a/maps-compose-utils/build.gradle.kts
+++ b/maps-compose-utils/build.gradle.kts
@@ -13,10 +13,10 @@ android {
}
namespace = "com.google.maps.android.compose.utils"
- compileSdk = 36
+ compileSdk = libs.versions.androidCompileSdk.get().toInt()
defaultConfig {
- minSdk = 23
+ minSdk = libs.versions.androidMinSdk.get().toInt()
}
compileOptions {
diff --git a/maps-compose-widgets/build.gradle.kts b/maps-compose-widgets/build.gradle.kts
index 876bb3bd..23eae787 100644
--- a/maps-compose-widgets/build.gradle.kts
+++ b/maps-compose-widgets/build.gradle.kts
@@ -20,10 +20,10 @@ android {
}
namespace = "com.google.maps.android.compose.widgets"
- compileSdk = 36
+ compileSdk = libs.versions.androidCompileSdk.get().toInt()
defaultConfig {
- minSdk = 23
+ minSdk = libs.versions.androidMinSdk.get().toInt()
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
diff --git a/maps-compose/build.gradle.kts b/maps-compose/build.gradle.kts
index 8d601503..db4ecf19 100644
--- a/maps-compose/build.gradle.kts
+++ b/maps-compose/build.gradle.kts
@@ -13,10 +13,10 @@ android {
}
namespace = "com.google.maps.android.compose"
- compileSdk = 36
+ compileSdk = libs.versions.androidCompileSdk.get().toInt()
defaultConfig {
- minSdk = 23
+ minSdk = libs.versions.androidMinSdk.get().toInt()
}
compileOptions {
From d343b98ae6d237b70bf038a765c4fe289eed6e50 Mon Sep 17 00:00:00 2001
From: dkhawk <107309+dkhawk@users.noreply.github.com>
Date: Mon, 26 Jan 2026 14:36:44 -0700
Subject: [PATCH 04/14] build: remove unnecessary directory reference from
.gitignore
---
.gitignore | 1 -
1 file changed, 1 deletion(-)
diff --git a/.gitignore b/.gitignore
index 7376cd81..a77c626a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -21,5 +21,4 @@ secrets.properties
# This covers new IDEs, like Antigravity
.vscode/
-**/bin/BOB_DATA/
build-logic/**/bin/
From 1292de1d607b9cbc2b0d6dae1f1c1fe7e1fb5c72 Mon Sep 17 00:00:00 2001
From: dkhawk <107309+dkhawk@users.noreply.github.com>
Date: Mon, 26 Jan 2026 15:22:04 -0700
Subject: [PATCH 05/14] build: upgrade AGP to 9.0.0 and Gradle to 9.2.1
Upgrades the Android Gradle Plugin to 9.0.0, the Gradle Wrapper to 9.2.1, and the Compose Screenshot plugin to 0.0.1-alpha13. This resolves configuration issues with the `GenerateTestConfig` task where `mergedManifest` was not properly configured.
Also adds necessary AGP 9.0 compatibility flags to gradle.properties.
---
gradle.properties | 10 ++++++++++
gradle/libs.versions.toml | 14 ++++++++------
gradle/wrapper/gradle-wrapper.properties | 2 +-
3 files changed, 19 insertions(+), 7 deletions(-)
diff --git a/gradle.properties b/gradle.properties
index 2c6f44b5..2caeae0c 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -34,3 +34,13 @@ android.nonTransitiveRClass=false
android.nonFinalResIds=false
android.experimental.enableScreenshotTest=true
+android.defaults.buildfeatures.resvalues=true
+android.sdk.defaultTargetSdkToCompileSdkIfUnset=false
+android.enableAppCompileTimeRClass=false
+android.usesSdkInManifest.disallowed=false
+android.uniquePackageNames=false
+android.dependency.useConstraints=true
+android.r8.strictFullModeForKeepRules=false
+android.r8.optimizedResourceShrinking=false
+android.builtInKotlin=false
+android.newDsl=false
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 4b4a2f68..b604549f 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,8 +1,13 @@
# This file is organized into functional groups: Base, Android, Material, Google Maps Platform (GMP), and Testing.
# Please maintain this structure when adding new dependencies or versions.
+
[versions]
# Base
-agp = "8.13.2" # Do not upgrade to 9.0.0 yet
+androidCompileSdk = "36"
+androidMinSdk = "23"
+androidTargetSdk = "36"
+
+agp = "9.0.0"
dokka = "2.1.0"
gradleMavenPublishPlugin = "0.36.0"
jacoco-plugin = "0.2.1"
@@ -11,9 +16,6 @@ kotlinxCoroutines = "1.10.2"
# Android
activitycompose = "1.12.2"
-androidCompileSdk = "36"
-androidMinSdk = "23"
-androidTargetSdk = "36"
androidx-core = "1.17.0"
androidx-startup = "1.2.0"
compose-bom = "2026.01.00"
@@ -39,7 +41,7 @@ mockk = "1.14.9"
mockkAndroid = "1.14.9"
org-jacoco-core = "0.8.14"
robolectric = "4.16.1"
-screenshot = "0.0.1-alpha12" # Do not upgrade to alpha13 (broken)
+screenshot = "0.0.1-alpha13"
truth = "1.4.5"
[libraries]
@@ -96,4 +98,4 @@ android-application = { id = "com.android.application", version.ref = "agp" }
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
-screenshot = { id = "com.android.compose.screenshot", version.ref = "screenshot"}
+screenshot = { id = "com.android.compose.screenshot", version.ref = "screenshot"}
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 2a84e188..23449a2b 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
From cadfb329e5e96782510aa3b5d19ab29bc723f8f4 Mon Sep 17 00:00:00 2001
From: dkhawk <107309+dkhawk@users.noreply.github.com>
Date: Mon, 26 Jan 2026 15:34:52 -0700
Subject: [PATCH 06/14] build: upgrade AGP to 9.0.0 and Gradle to 9.2.1
Upgrades the Android Gradle Plugin to 9.0.0, the Gradle Wrapper to 9.2.1, and the Compose Screenshot plugin to 0.0.1-alpha13. This resolves configuration issues with the 'GenerateTestConfig' task where 'mergedManifest' was not properly configured.
Also:
- Adopts AGP 9.0 compatibility flags in gradle.properties.
- Removes obsolete 'testDebugScreenshotTest' and 'testReleaseScreenshotTest' exclusions from CI workflows.
---
.github/workflows/test.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 012a13e9..4c1c0a26 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -42,7 +42,7 @@ jobs:
distribution: 'temurin'
- name: Build modules
- run: ./gradlew build jacocoTestReport -x :maps-app:generateDebugScreenshotTestConfig -x :maps-app:testDebugScreenshotTest -x :maps-app:generateReleaseScreenshotTestConfig -x :maps-app:testReleaseScreenshotTest --stacktrace
+ run: ./gradlew build jacocoTestReport -x :maps-app:generateDebugScreenshotTestConfig -x :maps-app:generateReleaseScreenshotTestConfig --stacktrace
- name: Run Screenshot Tests
run: ./gradlew :maps-app:validateDebugScreenshotTest
From 1ebbb216702dc9dd26df5c09ad6a49517cb27400 Mon Sep 17 00:00:00 2001
From: dkhawk <107309+dkhawk@users.noreply.github.com>
Date: Mon, 26 Jan 2026 15:49:40 -0700
Subject: [PATCH 07/14] build: remove broken jacoco-android plugin
Removes com.mxalbert.gradle.jacoco-android plugin which is incompatible with AGP 9.0.
Removed related configuration from convention plugin and test workflow.
---
.github/workflows/test.yml | 2 +-
.../main/kotlin/PublishingConventionPlugin.kt | 18 ------------------
gradle/libs.versions.toml | 2 --
3 files changed, 1 insertion(+), 21 deletions(-)
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 4c1c0a26..f31d41cc 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -42,7 +42,7 @@ jobs:
distribution: 'temurin'
- name: Build modules
- run: ./gradlew build jacocoTestReport -x :maps-app:generateDebugScreenshotTestConfig -x :maps-app:generateReleaseScreenshotTestConfig --stacktrace
+ run: ./gradlew build -x :maps-app:generateDebugScreenshotTestConfig -x :maps-app:generateReleaseScreenshotTestConfig --stacktrace
- name: Run Screenshot Tests
run: ./gradlew :maps-app:validateDebugScreenshotTest
diff --git a/build-logic/convention/src/main/kotlin/PublishingConventionPlugin.kt b/build-logic/convention/src/main/kotlin/PublishingConventionPlugin.kt
index 0ea4f542..155850cd 100644
--- a/build-logic/convention/src/main/kotlin/PublishingConventionPlugin.kt
+++ b/build-logic/convention/src/main/kotlin/PublishingConventionPlugin.kt
@@ -4,41 +4,23 @@ import com.vanniktech.maven.publish.MavenPublishBaseExtension
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.*
-import org.gradle.testing.jacoco.plugins.JacocoPluginExtension
import org.gradle.api.tasks.testing.Test
-import org.gradle.testing.jacoco.plugins.JacocoTaskExtension
class PublishingConventionPlugin : Plugin {
override fun apply(project: Project) {
project.run {
applyPlugins()
- configureJacoco()
configureVanniktechPublishing()
}
}
private fun Project.applyPlugins() {
apply(plugin = "com.android.library")
- apply(plugin = "com.mxalbert.gradle.jacoco-android")
apply(plugin = "org.jetbrains.dokka")
apply(plugin = "com.vanniktech.maven.publish")
}
- private fun Project.configureJacoco() {
- configure {
- toolVersion = "0.8.7"
-
- }
-
- tasks.withType().configureEach {
- extensions.configure(JacocoTaskExtension::class.java) {
- isIncludeNoLocationClasses = true
- excludes = listOf("jdk.internal.*")
- }
- }
- }
-
private fun Project.configureVanniktechPublishing() {
extensions.configure {
configure(
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index b604549f..2bf98875 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -10,7 +10,6 @@ androidTargetSdk = "36"
agp = "9.0.0"
dokka = "2.1.0"
gradleMavenPublishPlugin = "0.36.0"
-jacoco-plugin = "0.2.1"
kotlin = "2.3.0"
kotlinxCoroutines = "1.10.2"
@@ -49,7 +48,6 @@ truth = "1.4.5"
android-gradle-plugin = { module = "com.android.tools.build:gradle", version.ref = "agp" }
dokka-plugin = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "dokka" }
gradle-maven-publish-plugin = { module = "com.vanniktech:gradle-maven-publish-plugin", version.ref = "gradleMavenPublishPlugin" }
-jacoco-android-plugin = { module = "com.mxalbert.gradle:jacoco-android", version.ref = "jacoco-plugin", version.require = "0.2.1" }
kotlin = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk7", version.ref = "kotlin" }
kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinxCoroutines" }
From a4b95a1588a053fde2eccf2882a93e3ddd0f73fb Mon Sep 17 00:00:00 2001
From: dkhawk <107309+dkhawk@users.noreply.github.com>
Date: Mon, 26 Jan 2026 16:33:10 -0700
Subject: [PATCH 08/14] build: revert some deps to fix jacoco
---
.github/workflows/test.yml | 2 +-
.../main/kotlin/PublishingConventionPlugin.kt | 18 ++++++++++++++++++
gradle/libs.versions.toml | 8 +++++---
gradle/wrapper/gradle-wrapper.properties | 2 +-
4 files changed, 25 insertions(+), 5 deletions(-)
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index f31d41cc..012a13e9 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -42,7 +42,7 @@ jobs:
distribution: 'temurin'
- name: Build modules
- run: ./gradlew build -x :maps-app:generateDebugScreenshotTestConfig -x :maps-app:generateReleaseScreenshotTestConfig --stacktrace
+ run: ./gradlew build jacocoTestReport -x :maps-app:generateDebugScreenshotTestConfig -x :maps-app:testDebugScreenshotTest -x :maps-app:generateReleaseScreenshotTestConfig -x :maps-app:testReleaseScreenshotTest --stacktrace
- name: Run Screenshot Tests
run: ./gradlew :maps-app:validateDebugScreenshotTest
diff --git a/build-logic/convention/src/main/kotlin/PublishingConventionPlugin.kt b/build-logic/convention/src/main/kotlin/PublishingConventionPlugin.kt
index 155850cd..0ea4f542 100644
--- a/build-logic/convention/src/main/kotlin/PublishingConventionPlugin.kt
+++ b/build-logic/convention/src/main/kotlin/PublishingConventionPlugin.kt
@@ -4,23 +4,41 @@ import com.vanniktech.maven.publish.MavenPublishBaseExtension
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.kotlin.dsl.*
+import org.gradle.testing.jacoco.plugins.JacocoPluginExtension
import org.gradle.api.tasks.testing.Test
+import org.gradle.testing.jacoco.plugins.JacocoTaskExtension
class PublishingConventionPlugin : Plugin {
override fun apply(project: Project) {
project.run {
applyPlugins()
+ configureJacoco()
configureVanniktechPublishing()
}
}
private fun Project.applyPlugins() {
apply(plugin = "com.android.library")
+ apply(plugin = "com.mxalbert.gradle.jacoco-android")
apply(plugin = "org.jetbrains.dokka")
apply(plugin = "com.vanniktech.maven.publish")
}
+ private fun Project.configureJacoco() {
+ configure {
+ toolVersion = "0.8.7"
+
+ }
+
+ tasks.withType().configureEach {
+ extensions.configure(JacocoTaskExtension::class.java) {
+ isIncludeNoLocationClasses = true
+ excludes = listOf("jdk.internal.*")
+ }
+ }
+ }
+
private fun Project.configureVanniktechPublishing() {
extensions.configure {
configure(
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 2bf98875..0a260c46 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -7,10 +7,10 @@ androidCompileSdk = "36"
androidMinSdk = "23"
androidTargetSdk = "36"
-agp = "9.0.0"
+agp = "8.13.1"
dokka = "2.1.0"
gradleMavenPublishPlugin = "0.36.0"
-kotlin = "2.3.0"
+kotlin = "2.2.21"
kotlinxCoroutines = "1.10.2"
# Android
@@ -39,8 +39,9 @@ leakcanaryAndroid = "2.14"
mockk = "1.14.9"
mockkAndroid = "1.14.9"
org-jacoco-core = "0.8.14"
+jacoco-plugin = "0.2.1"
robolectric = "4.16.1"
-screenshot = "0.0.1-alpha13"
+screenshot = "0.0.1-alpha12"
truth = "1.4.5"
[libraries]
@@ -81,6 +82,7 @@ androidx-test-espresso = { module = "androidx.test.espresso:espresso-core", vers
androidx-test-junit-ktx = { module = "androidx.test.ext:junit-ktx", version.ref = "junitktx" }
androidx-test-rules = { module = "androidx.test:rules", version.ref = "androidCore" }
androidx-test-runner = { module = "androidx.test:runner", version.ref = "androidxtest" }
+jacoco-android-plugin = { module = "com.mxalbert.gradle:jacoco-android", version.ref = "jacoco-plugin" }
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinxCoroutines" }
leakcanary-android = { module = "com.squareup.leakcanary:leakcanary-android", version.ref = "leakcanaryAndroid" }
mockk = { module = "io.mockk:mockk", version.ref = "mockk" }
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 23449a2b..2a84e188 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
From 02686b1f51d18535f4fd8a3aa8e57f2696573988 Mon Sep 17 00:00:00 2001
From: dkhawk <107309+dkhawk@users.noreply.github.com>
Date: Mon, 26 Jan 2026 17:11:35 -0700
Subject: [PATCH 09/14] build: restore jacoco coverage and fix D8 error
Upgrades AGP to 9.0.0 and Screenshot plugin to 0.0.1-alpha13 to resolve D8 Kotlin metadata errors with Kotlin 2.3.0.
Restores Jacoco coverage reporting using the native Gradle Jacoco plugin (compatible with AGP 9.0), replacing the incompatible `jacoco-android` plugin.
Switches coverage collection to the `debug` build type as `release` unit test tasks are not created by default for libraries.
---
.github/workflows/test.yml | 2 +-
.../main/kotlin/PublishingConventionPlugin.kt | 35 ++++++++++++++-----
gradle/libs.versions.toml | 6 ++--
gradle/wrapper/gradle-wrapper.properties | 2 +-
maps-compose-utils/build.gradle.kts | 7 ++++
maps-compose-widgets/build.gradle.kts | 7 ++++
maps-compose/build.gradle.kts | 7 ++++
7 files changed, 53 insertions(+), 13 deletions(-)
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 012a13e9..499f62fd 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -42,7 +42,7 @@ jobs:
distribution: 'temurin'
- name: Build modules
- run: ./gradlew build jacocoTestReport -x :maps-app:generateDebugScreenshotTestConfig -x :maps-app:testDebugScreenshotTest -x :maps-app:generateReleaseScreenshotTestConfig -x :maps-app:testReleaseScreenshotTest --stacktrace
+ run: ./gradlew build jacocoTestReport -x :maps-app:generateDebugScreenshotTestConfig -x :maps-app:testDebugSc run: ./gradlew build jacocoTestReport -x :maps-app:generateDebugScreenshotTestConfig -x :maps-app:generateReleaseScreenshotTestConfig --stacktrace
- name: Run Screenshot Tests
run: ./gradlew :maps-app:validateDebugScreenshotTest
diff --git a/build-logic/convention/src/main/kotlin/PublishingConventionPlugin.kt b/build-logic/convention/src/main/kotlin/PublishingConventionPlugin.kt
index 0ea4f542..8af5773a 100644
--- a/build-logic/convention/src/main/kotlin/PublishingConventionPlugin.kt
+++ b/build-logic/convention/src/main/kotlin/PublishingConventionPlugin.kt
@@ -7,6 +7,7 @@ import org.gradle.kotlin.dsl.*
import org.gradle.testing.jacoco.plugins.JacocoPluginExtension
import org.gradle.api.tasks.testing.Test
import org.gradle.testing.jacoco.plugins.JacocoTaskExtension
+import org.gradle.testing.jacoco.tasks.JacocoReport
class PublishingConventionPlugin : Plugin {
override fun apply(project: Project) {
@@ -20,22 +21,40 @@ class PublishingConventionPlugin : Plugin {
private fun Project.applyPlugins() {
apply(plugin = "com.android.library")
- apply(plugin = "com.mxalbert.gradle.jacoco-android")
apply(plugin = "org.jetbrains.dokka")
+ apply(plugin = "org.gradle.jacoco")
apply(plugin = "com.vanniktech.maven.publish")
}
private fun Project.configureJacoco() {
configure {
- toolVersion = "0.8.7"
-
+ toolVersion = "0.8.11" // Compatible with newer JDKs
}
- tasks.withType().configureEach {
- extensions.configure(JacocoTaskExtension::class.java) {
- isIncludeNoLocationClasses = true
- excludes = listOf("jdk.internal.*")
- }
+ // AGP 9.0+ built-in Jacoco support or manual configuration.
+ // We create a "jacocoTestReport" task to match the CI workflow.
+
+ tasks.register("jacocoTestReport") {
+ // Dependencies
+ dependsOn("testDebugUnitTest")
+
+ reports {
+ xml.required.set(true)
+ html.required.set(true)
+ }
+
+ // Source directories
+ val mainSrc = "${layout.projectDirectory}/src/main/java"
+ sourceDirectories.setFrom(files(mainSrc))
+
+ // Class directories - we need to point to where Kotlin compiles to
+ val debugTree = fileTree("${layout.buildDirectory.get()}/tmp/kotlin-classes/debug")
+ classDirectories.setFrom(files(debugTree))
+
+ // Execution data from the unit test task
+ executionData.setFrom(fileTree(layout.buildDirectory.get()) {
+ include("outputs/unit_test_code_coverage/debugUnitTest/testDebugUnitTest.exec")
+ })
}
}
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 0a260c46..73904ddd 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -7,10 +7,10 @@ androidCompileSdk = "36"
androidMinSdk = "23"
androidTargetSdk = "36"
-agp = "8.13.1"
+agp = "9.0.0"
dokka = "2.1.0"
gradleMavenPublishPlugin = "0.36.0"
-kotlin = "2.2.21"
+kotlin = "2.3.0"
kotlinxCoroutines = "1.10.2"
# Android
@@ -41,7 +41,7 @@ mockkAndroid = "1.14.9"
org-jacoco-core = "0.8.14"
jacoco-plugin = "0.2.1"
robolectric = "4.16.1"
-screenshot = "0.0.1-alpha12"
+screenshot = "0.0.1-alpha13"
truth = "1.4.5"
[libraries]
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 2a84e188..23449a2b 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-9.2.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
diff --git a/maps-compose-utils/build.gradle.kts b/maps-compose-utils/build.gradle.kts
index 8b5ab8ca..86ce8933 100644
--- a/maps-compose-utils/build.gradle.kts
+++ b/maps-compose-utils/build.gradle.kts
@@ -38,6 +38,13 @@ android {
)
}
}
+
+ buildTypes {
+ getByName("debug") {
+ enableUnitTestCoverage = true
+ enableAndroidTestCoverage = true
+ }
+ }
}
dependencies {
diff --git a/maps-compose-widgets/build.gradle.kts b/maps-compose-widgets/build.gradle.kts
index 23eae787..f8e96ccc 100644
--- a/maps-compose-widgets/build.gradle.kts
+++ b/maps-compose-widgets/build.gradle.kts
@@ -46,6 +46,13 @@ android {
)
}
}
+
+ buildTypes {
+ getByName("debug") {
+ enableUnitTestCoverage = true
+ enableAndroidTestCoverage = true
+ }
+ }
}
dependencies {
diff --git a/maps-compose/build.gradle.kts b/maps-compose/build.gradle.kts
index db4ecf19..eeff7c42 100644
--- a/maps-compose/build.gradle.kts
+++ b/maps-compose/build.gradle.kts
@@ -40,6 +40,13 @@ android {
}
sourceSets["main"].java.srcDir("build/generated/source/artifactId")
+
+ buildTypes {
+ getByName("debug") {
+ enableUnitTestCoverage = true
+ enableAndroidTestCoverage = true
+ }
+ }
}
composeCompiler {
From 865b9d2e4b13c439acfdc5b39688dd423bbd4b5a Mon Sep 17 00:00:00 2001
From: dkhawk <107309+dkhawk@users.noreply.github.com>
Date: Mon, 26 Jan 2026 17:21:30 -0700
Subject: [PATCH 10/14] build: fix malformed CI command in test.yml
---
.github/workflows/test.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 499f62fd..4c1c0a26 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -42,7 +42,7 @@ jobs:
distribution: 'temurin'
- name: Build modules
- run: ./gradlew build jacocoTestReport -x :maps-app:generateDebugScreenshotTestConfig -x :maps-app:testDebugSc run: ./gradlew build jacocoTestReport -x :maps-app:generateDebugScreenshotTestConfig -x :maps-app:generateReleaseScreenshotTestConfig --stacktrace
+ run: ./gradlew build jacocoTestReport -x :maps-app:generateDebugScreenshotTestConfig -x :maps-app:generateReleaseScreenshotTestConfig --stacktrace
- name: Run Screenshot Tests
run: ./gradlew :maps-app:validateDebugScreenshotTest
From 0c64a49ecc911db34db097c68611672b650958cc Mon Sep 17 00:00:00 2001
From: dkhawk <107309+dkhawk@users.noreply.github.com>
Date: Mon, 26 Jan 2026 17:25:33 -0700
Subject: [PATCH 11/14] build: fix deprecated Kotlin compiler argument
Replaces deprecated -Xopt-in with -opt-in in build files to resolve Gradle warnings.
---
maps-compose-utils/build.gradle.kts | 2 +-
maps-compose-widgets/build.gradle.kts | 2 +-
maps-compose/build.gradle.kts | 2 +-
3 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/maps-compose-utils/build.gradle.kts b/maps-compose-utils/build.gradle.kts
index 86ce8933..41f9ccb8 100644
--- a/maps-compose-utils/build.gradle.kts
+++ b/maps-compose-utils/build.gradle.kts
@@ -34,7 +34,7 @@ android {
jvmTarget.set(JvmTarget.JVM_1_8)
freeCompilerArgs.addAll(
"-Xexplicit-api=strict",
- "-Xopt-in=kotlin.RequiresOptIn"
+ "-opt-in=kotlin.RequiresOptIn"
)
}
}
diff --git a/maps-compose-widgets/build.gradle.kts b/maps-compose-widgets/build.gradle.kts
index f8e96ccc..8fa3cd9f 100644
--- a/maps-compose-widgets/build.gradle.kts
+++ b/maps-compose-widgets/build.gradle.kts
@@ -42,7 +42,7 @@ android {
jvmTarget.set(JvmTarget.JVM_1_8)
freeCompilerArgs.addAll(
"-Xexplicit-api=strict",
- "-Xopt-in=kotlin.RequiresOptIn"
+ "-opt-in=kotlin.RequiresOptIn"
)
}
}
diff --git a/maps-compose/build.gradle.kts b/maps-compose/build.gradle.kts
index eeff7c42..6afbede7 100644
--- a/maps-compose/build.gradle.kts
+++ b/maps-compose/build.gradle.kts
@@ -34,7 +34,7 @@ android {
jvmTarget.set(JvmTarget.JVM_1_8)
freeCompilerArgs.addAll(
"-Xexplicit-api=strict",
- "-Xopt-in=kotlin.RequiresOptIn"
+ "-opt-in=kotlin.RequiresOptIn"
)
}
}
From f044d95286711fa71616326dab0cd01a8bd884e8 Mon Sep 17 00:00:00 2001
From: dkhawk <107309+dkhawk@users.noreply.github.com>
Date: Mon, 26 Jan 2026 17:53:43 -0700
Subject: [PATCH 12/14] build: add org.apache.http.legacy to maps-app
Required for instrumentation tests running on Android 9+ to avoid NoClassDefFoundError: org/apache/http/ProtocolVersion.
---
maps-app/build.gradle.kts | 2 ++
1 file changed, 2 insertions(+)
diff --git a/maps-app/build.gradle.kts b/maps-app/build.gradle.kts
index 713346b6..ab1e4f54 100644
--- a/maps-app/build.gradle.kts
+++ b/maps-app/build.gradle.kts
@@ -27,6 +27,8 @@ android {
namespace = "com.google.maps.android.compose"
compileSdk = libs.versions.androidCompileSdk.get().toInt()
+ useLibrary("org.apache.http.legacy")
+
defaultConfig {
minSdk = libs.versions.androidMinSdk.get().toInt()
targetSdk = libs.versions.androidTargetSdk.get().toInt()
From c806e00626ef8f0952de554baa56e0be995e0774 Mon Sep 17 00:00:00 2001
From: dkhawk <107309+dkhawk@users.noreply.github.com>
Date: Mon, 26 Jan 2026 18:04:21 -0700
Subject: [PATCH 13/14] fix: add org.apache.http.legacy to manifest
Required for runtime access on Android 9+ devices during instrumentation tests.
Missed by useLibrary in build.gradle which only handles compile classpath.
---
maps-app/src/main/AndroidManifest.xml | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/maps-app/src/main/AndroidManifest.xml b/maps-app/src/main/AndroidManifest.xml
index e6904543..5292fb8c 100644
--- a/maps-app/src/main/AndroidManifest.xml
+++ b/maps-app/src/main/AndroidManifest.xml
@@ -26,6 +26,10 @@
android:supportsRtl="true"
android:theme="@style/Theme.AndroidMapsCompose">
+
+
From e7c3819dc6989279fbffff5b2e81d5155c1d39f8 Mon Sep 17 00:00:00 2001
From: dkhawk <107309+dkhawk@users.noreply.github.com>
Date: Mon, 26 Jan 2026 18:51:41 -0700
Subject: [PATCH 14/14] test(widgets): use mockk-android for instrumentation
tests
Fixes ClassNotFoundException: io.mockk.proxy.android.AndroidMockKAgentFactory
during ScaleBarUnitTest execution on Android devices.
---
gradle/libs.versions.toml | 4 ++--
maps-app/build.gradle.kts | 16 ++++++++--------
maps-app/src/screenshotTest/AndroidManifest.xml | 11 ++++++++++-
maps-compose-widgets/build.gradle.kts | 2 +-
4 files changed, 21 insertions(+), 12 deletions(-)
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 73904ddd..66df61a8 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -37,7 +37,6 @@ junit = "4.13.2"
junitktx = "1.3.0"
leakcanaryAndroid = "2.14"
mockk = "1.14.9"
-mockkAndroid = "1.14.9"
org-jacoco-core = "0.8.14"
jacoco-plugin = "0.2.1"
robolectric = "4.16.1"
@@ -86,7 +85,8 @@ jacoco-android-plugin = { module = "com.mxalbert.gradle:jacoco-android", version
kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinxCoroutines" }
leakcanary-android = { module = "com.squareup.leakcanary:leakcanary-android", version.ref = "leakcanaryAndroid" }
mockk = { module = "io.mockk:mockk", version.ref = "mockk" }
-mockk-android = { module = "io.mockk:mockk-android", version.ref = "mockkAndroid" }
+#mockk-android = { module = "io.mockk:mockk-android", version.ref = "mockkAndroid" }
+mockk-android = { group = "io.mockk", name = "mockk-android", version.ref = "mockk" }
org-jacoco-core = { module = "org.jacoco:org.jacoco.core", version.ref = "org-jacoco-core" }
robolectric = { module = "org.robolectric:robolectric", version.ref = "robolectric" }
screenshot-validation-api = { group = "com.android.tools.screenshot", name = "screenshot-validation-api", version.ref = "screenshot" }
diff --git a/maps-app/build.gradle.kts b/maps-app/build.gradle.kts
index ab1e4f54..0654b784 100644
--- a/maps-app/build.gradle.kts
+++ b/maps-app/build.gradle.kts
@@ -86,7 +86,6 @@ dependencies {
implementation(libs.material)
implementation(libs.androidx.compose.material.icons.extended.android)
- screenshotTestImplementation(libs.screenshot.validation.api)
debugImplementation(libs.androidx.compose.ui.tooling)
debugImplementation(libs.leakcanary.android)
@@ -102,23 +101,24 @@ dependencies {
androidTestImplementation(libs.truth)
androidTestImplementation(libs.mockk.android)
- testImplementation(libs.test.junit)
- testImplementation(libs.robolectric)
+ testImplementation(kotlin("test"))
testImplementation(libs.androidx.test.core)
- testImplementation(libs.truth)
testImplementation(libs.kotlinx.coroutines.test)
+ testImplementation(libs.mockk)
+ testImplementation(libs.robolectric)
+ testImplementation(libs.test.junit)
+ testImplementation(libs.truth)
+ screenshotTestImplementation(kotlin("test"))
screenshotTestImplementation(libs.androidx.compose.ui.tooling)
+ screenshotTestImplementation(libs.mockk.android)
+ screenshotTestImplementation(libs.screenshot.validation.api)
// Instead of the lines below, regular apps would load these libraries from Maven according to
// the README installation instructions
implementation(project(":maps-compose"))
implementation(project(":maps-compose-widgets"))
implementation(project(":maps-compose-utils"))
-
- testImplementation(libs.mockk)
- testImplementation(libs.mockk.android)
- testImplementation(kotlin("test"))
}
secrets {
diff --git a/maps-app/src/screenshotTest/AndroidManifest.xml b/maps-app/src/screenshotTest/AndroidManifest.xml
index db93b49b..e366e537 100644
--- a/maps-app/src/screenshotTest/AndroidManifest.xml
+++ b/maps-app/src/screenshotTest/AndroidManifest.xml
@@ -1 +1,10 @@
-
\ No newline at end of file
+
+
+
+
+
+
diff --git a/maps-compose-widgets/build.gradle.kts b/maps-compose-widgets/build.gradle.kts
index 8fa3cd9f..4330e2da 100644
--- a/maps-compose-widgets/build.gradle.kts
+++ b/maps-compose-widgets/build.gradle.kts
@@ -71,6 +71,6 @@ dependencies {
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.test.espresso)
androidTestImplementation(libs.androidx.test.junit.ktx)
- androidTestImplementation(libs.mockk)
+ androidTestImplementation(libs.mockk.android)
androidTestImplementation(libs.truth)
}