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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 31 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,16 +61,26 @@ This repository includes a [sample app](demo) that illustrates the use of this l

To run the demo app, ensure you've met the requirements above then:
1. Clone the repository
1. Add a file `local.properties` in the root project (this file should *NOT* be under version control to protect your API key)
1. Add a single line to `local.properties` that looks like `MAPS_API_KEY=YOUR_API_KEY`, where `YOUR_API_KEY` is the API key you obtained earlier
1. Add a file `secrets.properties` in the root project (this file should *NOT* be under version control to protect your API key)
1. Add the following keys to `secrets.properties`:
- `MAPS_API_KEY`: **Required**. Your Google Maps API Key. Ensure it has the **Maps SDK for Android** enabled.
- `PLACES_API_KEY`: **Optional**. Required for demos using the Places API (e.g., Heatmaps with Places). Ensure the **Places API** is enabled.
- `MAP_ID`: **Optional**. Required for demos using Advanced Markers or Cloud-based Map Styling.

For example:
```properties
MAPS_API_KEY=YOUR_MAPS_API_KEY
PLACES_API_KEY=YOUR_PLACES_API_KEY
MAP_ID=YOUR_MAP_ID
```
1. Build and run the `debug` variant for the Maps SDK for Android version

### Setting up the Map ID

Some of the features in the demo app, such as Advanced Markers, require a Map ID. You can learn more about map IDs in the [official documentation](https://developers.google.com/maps/documentation/android-sdk/map-ids/mapid-over). You can set the Map ID in one of the following ways:

1. **`secrets.properties`:** Add a line to your `secrets.properties` file with your Map ID:
```
```properties
MAP_ID=YOUR_MAP_ID
```

Expand Down Expand Up @@ -144,6 +154,24 @@ By default, the `Source` is set to `Source.DEFAULT`, but you can also specify `S

</details>

## Internal usage attribution ID

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
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data
android:name="com.google.maps.android.utils.attribution.AttributionIdInitializer"
tools:node="remove" />
</provider>
```

## Contributing

Contributions are welcome and encouraged! If you'd like to contribute, send us a [pull request] and refer to our [code of conduct] and [contributing guide].
Expand Down
22 changes: 13 additions & 9 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,29 +1,31 @@
[versions]
compileSdk = "36"
targetSdk = "36"
minimumSdk = "21"
minimumSdk = "23"

appcompat = "1.7.1"
dokka-gradle-plugin = "2.1.0"
gradle = "8.13.1"
gradle = "8.13.2"

Check warning

Code scanning / Android Lint

Obsolete Android Gradle Plugin Version Warning

A newer version of com.android.tools.build:gradle than 8.13.2 is available: 9.0.0
jacoco-android = "0.2.1"
lifecycle-extensions = "2.2.0"
lifecycle-viewmodel-ktx = "2.9.4"
lifecycle-viewmodel-ktx = "2.10.0"
startup-runtime = "1.2.0"
kotlin = "2.2.21"
kotlinx-coroutines = "1.10.2"
junit = "4.13.2"
mockito-core = "5.20.0"
mockito-core = "5.21.0"
secrets-gradle-plugin = "2.0.1"
truth = "1.4.5"
play-services-maps = "19.2.0"
play-services-maps = "20.0.0"
core-ktx = "1.17.0"
robolectric = "4.16"
robolectric = "4.16.1"
kxml2 = "2.3.0"
mockk = "1.14.6"
lint = "31.13.1"
mockk = "1.14.7"
lint = "32.0.0"
org-jacoco-core = "0.8.14"
material = "1.13.0"
gradleMavenPublishPlugin = "0.35.0"
gradleMavenPublishPlugin = "0.36.0"
androidx-test-core = "1.7.0"

[libraries]
appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" }
Expand All @@ -34,6 +36,7 @@
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
lifecycle-extensions = { module = "androidx.lifecycle:lifecycle-extensions", version.ref = "lifecycle-extensions" }
lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycle-viewmodel-ktx" }
startup-runtime = { module = "androidx.startup:startup-runtime", version.ref = "startup-runtime" }
kotlin-stdlib-jdk8 = { module = "org.jetbrains.kotlin:kotlin-stdlib-jdk8", version.ref = "kotlin" }
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" }
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" }
Expand All @@ -55,3 +58,4 @@
org-jacoco-core = { module = "org.jacoco:org.jacoco.core", version.ref = "org-jacoco-core" }
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
gradle-maven-publish-plugin = { module = "com.vanniktech:gradle-maven-publish-plugin", version.ref = "gradleMavenPublishPlugin" }
androidx-test-core = { module = "androidx.test:core", version.ref = "androidx-test-core" }
41 changes: 40 additions & 1 deletion library/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ android {
}
defaultConfig {
compileSdk = libs.versions.compileSdk.get().toInt()
minSdk = 21
minSdk = libs.versions.minimumSdk.get().toInt()
testOptions.targetSdk = libs.versions.targetSdk.get().toInt()
consumerProguardFiles("consumer-rules.pro")
}
Expand Down Expand Up @@ -60,19 +60,22 @@ android {
unitTests.isReturnDefaultValues = true
}
namespace = "com.google.maps.android"
sourceSets["main"].java.srcDir("build/generated/source/artifactId")
}

dependencies {
api(libs.play.services.maps)
implementation(libs.kotlinx.coroutines.android)
implementation(libs.appcompat)
implementation(libs.core.ktx)
implementation(libs.startup.runtime)
lintPublish(project(":lint-checks"))
testImplementation(libs.junit)
testImplementation(libs.robolectric)
testImplementation(libs.kxml2)
testImplementation(libs.mockk)
testImplementation(libs.kotlin.test)
testImplementation(libs.androidx.test.core)
testImplementation(libs.truth)
implementation(libs.kotlin.stdlib.jdk8)

Expand All @@ -89,3 +92,39 @@ tasks.register("instrumentTest") {
if (System.getenv("JITPACK") != null) {
apply(plugin = "maven")
}

// START: Attribution ID Generation Logic
val attributionId = "gmp_git_androidmapsutils_v$version"

val generateArtifactIdFile = tasks.register("generateArtifactIdFile") {
description = "Generates an AttributionId object from the project version."
group = "build"

val outputDir = layout.buildDirectory.dir("generated/source/artifactId")
val packageName = "com.google.maps.android.utils.meta"
val packagePath = packageName.replace('.', '/')
val outputFile = outputDir.get().file("$packagePath/ArtifactId.kt").asFile

outputs.file(outputFile)

doLast {
outputFile.parentFile.mkdirs()
outputFile.writeText(
"""
package $packageName

/**
* Automatically generated object containing the library's attribution ID.
* This is used to track library usage for analytics.
*/
public object AttributionId {
public const val VALUE: String = "$attributionId"
}
""".trimIndent()
)
}
}

tasks.named("preBuild") {
dependsOn(generateArtifactIdFile)
}
15 changes: 13 additions & 2 deletions library/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2024 Google LLC
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.
Expand All @@ -15,12 +15,23 @@
limitations under the License.
-->

<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- https://groups.google.com/group/adt-dev/browse_thread/thread/c059c0380998092b/6720093fb40fdaf9 -->
<application>
<meta-data
android:name="com.google.android.gms.version"
android:value="@integer/google_play_services_version" />

<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">

<meta-data
android:name="com.google.maps.android.utils.attribution.AttributionIdInitializer"
android:value="androidx.startup" />
</provider>
</application>
</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* 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.utils.attribution

import android.content.Context
import androidx.startup.Initializer
import com.google.android.gms.maps.MapsApiSettings
import com.google.maps.android.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<Unit> {
override fun create(context: Context) {
// See [AttributionIdInitializer]
MapsApiSettings.addInternalUsageAttributionId(
/* context = */ context,
/* internalUsageAttributionId = */ AttributionId.VALUE
)
}

override fun dependencies(): List<Class<out Initializer<*>>> {
return emptyList()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* 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.utils.attribution

import android.content.Context
import androidx.test.core.app.ApplicationProvider
import com.google.android.gms.maps.MapsApiSettings
import com.google.maps.android.utils.meta.AttributionId
import io.mockk.every
import io.mockk.just
import io.mockk.mockkStatic
import io.mockk.runs
import io.mockk.unmockkStatic
import io.mockk.verify
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner

@RunWith(RobolectricTestRunner::class)
class AttributionIdInitializerTest {

@Before
fun setUp() {
mockkStatic(MapsApiSettings::class)
every { MapsApiSettings.addInternalUsageAttributionId(any(), any()) } just runs
}

@After
fun tearDown() {
unmockkStatic(MapsApiSettings::class)
}

@Test
fun `create adds internal usage attribution id`() {
val context = ApplicationProvider.getApplicationContext<Context>()
val initializer = AttributionIdInitializer()

initializer.create(context)

verify {
MapsApiSettings.addInternalUsageAttributionId(
context,
AttributionId.VALUE
)
}
}
}
Loading