Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
6dd161d
feat: Implement Maps 3D Compose wrapper, sample catalog, and visual t…
dkhawk Apr 13, 2026
403066e
feat: Implement Hello Map sample as a separate activity centered on F…
dkhawk Apr 13, 2026
2a2fc84
feat: Switch Hello Map location to Delicate Arch and fix visual test
dkhawk Apr 13, 2026
d6560b0
feat: Calibrate camera pose for Delicate Arch in HelloMapActivity
dkhawk Apr 13, 2026
3830272
chore: Add visual test examples and gradle daemon properties
dkhawk Apr 13, 2026
80b110f
feat: Add Camera Controls activity and calibrate for Seattle
dkhawk Apr 13, 2026
11e1d5e
feat: Add Map Interactions sample calibrated for Denver Capitol
dkhawk Apr 13, 2026
3cf36b8
refactor: Reorder parameters in GoogleMap3D to follow Compose convention
dkhawk Apr 13, 2026
f5c9160
test: Use conventional UiAutomator testing for Map Interactions fallback
dkhawk Apr 13, 2026
8a1c759
test: Verify Markers sample with visual test
dkhawk Apr 13, 2026
365d86f
test: Verify Markers sample with popover and extruded marker
dkhawk Apr 13, 2026
c527626
feat: Export all sample activities in manifest
dkhawk Apr 13, 2026
4bf0664
feat: Remove unnecessary Back buttons from sample activities
dkhawk Apr 13, 2026
1c06045
feat: Support onClick callback in MarkerConfig and use it in MarkersA…
dkhawk Apr 13, 2026
29699c1
refactor: Clean up fully qualified class names in DataModels and Mark…
dkhawk Apr 14, 2026
667fb6e
feat: Migrate Sanitas Loop polyline to maps3d-compose
dkhawk Apr 14, 2026
0c91270
fix: Remove extra brace in build.gradle.kts files
dkhawk Apr 14, 2026
e570288
build: Add installAndLaunch task to maps3d-compose-demo
dkhawk Apr 14, 2026
1f35ae9
feat: Add extruded museum and click support to Polygons example
dkhawk Apr 14, 2026
0962524
feat(compose): Add Map Options and Popovers samples, and fix CameraRe…
dkhawk Apr 14, 2026
7c9054d
feat(maps3d-compose): Implement Models sample and refactor demo archi…
dkhawk Apr 14, 2026
82305b8
Refactor Basic Map sample to launch as a separate activity
dkhawk Apr 14, 2026
b7a499d
feat(maps3d-compose): Add declarative Popover support and update docu…
dkhawk Apr 14, 2026
8b33e37
feat: Implement OnCameraChangedListener and add Whiskey Compass demo …
dkhawk Apr 15, 2026
82e618b
feat: Implement PinConfiguration support and add visual test (#41)
dkhawk Apr 15, 2026
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
4 changes: 0 additions & 4 deletions Maps3DSamples/ApiDemos/java-app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,6 @@ if (!isCI) {
if (apiKey.isNullOrBlank() || !apiKey.matches(Regex("^AIza[a-zA-Z0-9_-]{35}$"))) {
throw GradleException("Invalid or missing MAPS3D_API_KEY in secrets.properties. Please provide a valid Google Maps API key (starts with 'AIza').")
}

if (secrets.getProperty("MAPS_API_KEY") != null) {
println("Warning: Found MAPS_API_KEY in secrets.properties. This project relies exclusively on MAPS3D_API_KEY.")
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -495,7 +495,7 @@ private void advanceTour(GoogleMap3D map) {
.thenComposeAsync(isSteady -> {
// If the user tapped "Stop" while we were waiting, gracefully exit the chain.
if (!isTourActive)
return CompletableFuture.completedFuture((Void) null);
return CompletableFuture.completedFuture(null);

FlyAroundOptions orbitOptions = new FlyAroundOptions(camera, 5000L, 1.0);
return com.example.maps3djava.common.MapUtils.awaitCameraAnimation(map, orbitOptions);
Expand Down
4 changes: 0 additions & 4 deletions Maps3DSamples/ApiDemos/kotlin-app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -65,10 +65,6 @@ if (!isCI) {
if (apiKey.isNullOrBlank() || !apiKey.matches(Regex("^AIza[a-zA-Z0-9_-]{35}$"))) {
throw GradleException("Invalid or missing MAPS3D_API_KEY in secrets.properties. Please provide a valid Google Maps API key (starts with 'AIza').")
}

if (secrets.getProperty("MAPS_API_KEY") != null) {
println("Warning: Found MAPS_API_KEY in secrets.properties. This project relies exclusively on MAPS3D_API_KEY.")
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import android.widget.Toast
import androidx.lifecycle.lifecycleScope
import com.example.maps3d.common.awaitCameraUpdate
import com.example.maps3d.common.toCameraUpdate
import com.example.maps3d.common.toValidCamera
import com.example.maps3dcommon.R
import com.example.maps3dkotlin.sampleactivity.SampleBaseActivity
import com.google.android.gms.maps3d.GoogleMap3D
Expand All @@ -35,10 +34,9 @@ import com.google.android.gms.maps3d.model.orientation
import com.google.android.gms.maps3d.model.vector3D
import com.google.android.material.button.MaterialButton
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.sample
import kotlinx.coroutines.launch
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
package com.example.maps3dkotlin.polygons

import android.graphics.Color
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.lifecycle.lifecycleScope
Expand All @@ -36,8 +35,6 @@ import com.google.android.gms.maps3d.model.latLngAltitude
import com.google.android.gms.maps3d.model.polygonOptions
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.seconds

/**
* This activity demonstrates how to create and display polygons on a 3D map. It showcases
Expand Down Expand Up @@ -163,7 +160,6 @@ class PolygonsActivity : SampleBaseActivity() {
}

companion object {
private val TAG = PolygonsActivity::class.java.simpleName
private const val DENVER_LATITUDE = 39.748477
private const val DENVER_LONGITUDE = -104.947575

Expand Down
4 changes: 0 additions & 4 deletions Maps3DSamples/advanced/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,6 @@ if (!isCI) {
if (apiKey.isNullOrBlank() || !apiKey.matches(Regex("^AIza[a-zA-Z0-9_-]{35}$"))) {
throw GradleException("Invalid or missing MAPS3D_API_KEY in secrets.properties. Please provide a valid Google Maps API key (starts with 'AIza').")
}

if (secrets.getProperty("MAPS_API_KEY") != null) {
println("Warning: Found MAPS_API_KEY in secrets.properties. This project relies exclusively on MAPS3D_API_KEY.")
}
}
}
}
Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,16 @@ The [Maps3DSamples/ApiDemos/common](Maps3DSamples/ApiDemos/common) module contai
It also has [Map3dViewModel](Maps3DSamples/advanced/app/src/main/java/com/example/advancedmaps3dsamples/common/Map3dViewModel.kt) which demonstrates how to
create an abstract base class for view models needing to interact with the Maps3DView via a GoogleMap3D.

## Experimental Compose Wrapper

In addition to the advanced sample, this repository contains an experimental, declarative Compose wrapper for the Maps 3D SDK:

* **[maps3d-compose](maps3d-compose)**: Provides the `GoogleMap3D` composable and supports declarative state management for markers, polylines, polygons, models, and popovers.
* **[maps3d-compose-demo](maps3d-compose-demo)**: Contains sample activities demonstrating how to use the wrapper.

> [!WARNING]
> These modules are a **Work In Progress (WIP) experiment** and serve as a **reference implementation**. They are not intended for production use.

## Requirements

To run the samples, you will need:
Expand Down
22 changes: 22 additions & 0 deletions maps3d-compose-demo/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Maps 3D Compose Demo

This module is a sample application that demonstrates how to use the `maps3d-compose` wrapper library.

> [!WARNING]
> **Status**: This application serves as a **reference implementation** for using the experimental Compose wrapper. It is a WIP experiment.

## What's Inside

You can find several sample activities demonstrating different features of the 3D Map in Compose:
- **Basic Map**: A simple map with camera controls.
- **Map Options**: Demonstrates changing map modes (Satellite/Hybrid) and applying camera restrictions.
- **Models**: Shows how to add and interact with 3D models (glTF) on the map, including click listeners.
- **Popovers**: Demonstrates the declarative Popover API, rendering Compose content inside native map popovers.

## How to Run

You can install and run this demo on a connected device or emulator using:
```bash
./gradlew :maps3d-compose-demo:installDebug
```
Then launch any of the activities via the launcher or ADB.
117 changes: 117 additions & 0 deletions maps3d-compose-demo/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*
* 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.
*/

plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
alias(libs.plugins.secrets.gradle.plugin)
id("com.diffplug.spotless") version "6.25.0"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be moved to the catalog?

}

configure<com.diffplug.gradle.spotless.SpotlessExtension> {
kotlin {
target("**/*.kt")
ktlint().editorConfigOverride(mapOf("indent_size" to "4", "ktlint_function_naming_ignore_when_annotated_with" to "Composable"))
trimTrailingWhitespace()
endWithNewline()
}
}

android {
namespace = "com.example.maps3dcomposedemo"
compileSdk = libs.versions.compileSdk.get().toInt()

defaultConfig {
applicationId = "com.example.maps3dcomposedemo"
minSdk = libs.versions.minSdk.get().toInt()
targetSdk = libs.versions.targetSdk.get().toInt()
versionCode = 1
versionName = "1.0"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary = true
}
}

buildTypes {
release {
isMinifyEnabled = false
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlin {
compilerOptions {
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11)
}
}
buildFeatures {
compose = true
buildConfig = true
}
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
}
}
}

dependencies {
implementation(project(":maps3d-compose"))

implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose)

implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.ui)
implementation(libs.androidx.ui.graphics)
implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3)

// Maps 3D SDK
implementation(libs.play.services.maps3d)

testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.ui.test.junit4)
androidTestImplementation(libs.androidx.uiautomator)
androidTestImplementation(libs.kotlinx.serialization.json)
androidTestImplementation(project(":visual-testing"))
debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest)
}

secrets {
propertiesFileName = "secrets.properties"
}

tasks.register<Exec>("installAndLaunch") {
description = "Installs and launches the demo app."
group = "install"
dependsOn("installDebug")
commandLine("adb", "shell", "am", "start", "-n", "com.example.maps3dcomposedemo/.MainActivity")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/*
* 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.example.maps3dcomposedemo

import android.app.Instrumentation
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.By
import androidx.test.uiautomator.UiDevice
import androidx.test.uiautomator.Until
import com.google.maps.android.visualtesting.GeminiVisualTestHelper
import org.junit.Assert.assertTrue
import java.io.File

abstract class BaseVisualTest {

protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
protected val uiDevice = UiDevice.getInstance(instrumentation)
protected val context: Context = instrumentation.targetContext
protected val helper = GeminiVisualTestHelper()

protected val geminiApiKey: String by lazy {
val key = BuildConfig.GEMINI_API_KEY
assertTrue(
"GEMINI_API_KEY is not set in secrets.properties. Please add GEMINI_API_KEY=YOUR_API_KEY to your secrets.properties file.",
key != "YOUR_GEMINI_API_KEY",
)
key
}

protected fun captureScreenshot(filename: String = "screenshot_${System.currentTimeMillis()}.png"): Bitmap {
val screenshotFile = File(context.getExternalFilesDir(null), filename)
val screenshotTaken = uiDevice.takeScreenshot(screenshotFile)
assertTrue("Failed to take screenshot: $filename", screenshotTaken)

val bitmap = BitmapFactory.decodeFile(screenshotFile.absolutePath)
assertTrue("Failed to decode screenshot file: $filename", bitmap != null)

println("Screenshot saved to device: ${screenshotFile.absolutePath}")
println("To pull: adb pull ${screenshotFile.absolutePath}")

return bitmap
}

/**
* Waits for the map to render.
* Since MapView content (tiles, markers) is rendered on a GL surface and not exposed as
* accessibility nodes, we cannot rely on UiAutomator looking for text/markers.
* We use a stable delay to ensure rendering is complete.
*/
protected fun waitForMapRendering(timeoutSeconds: Long = 30) {
val found = uiDevice.wait(Until.hasObject(By.desc("MapSteady")), timeoutSeconds * 1000)
assertTrue("Map did not become steady within $timeoutSeconds seconds", found)
}
}
Loading
Loading