Skip to content

Commit b0160ab

Browse files
dkhawkkikoso
andauthored
feat: implement missing compose samples and fix library bugs (#42)
* feat: create repo-root catalog, basic Compose map sample, and establish UI test baseline - Created a centralized samples catalog at the repository root for better discoverability, integrating the PlacesUIKit3D sample. - Created BasicComposeMapActivity from scratch to serve as a minimal pedagogical reference for Maps 3D Compose integration. - Established a baseline of UI smoke tests across key activities in both advanced and PlacesUIKit3D modules to ensure stability. * feat: initialize ComposeDemos module and reorganize catalog Created ComposeDemos module with skeletons, tests, and automation script. Reorganized catalog into module-specific READMEs. * feat: add Polylines sample data, tests, and screenshot * docs: use HTML img tags for catalog thumbnails * feat: implement Popovers sample and visual test * feat: apply immersive mode and update catalog thumbnails Enabled immersive mode in all implemented activities and updated screenshots in catalog. * feat: implement Polygons, Models, and Markers samples Implemented Polygons, Models, and Markers samples in Compose with visual tests and updated catalog. * fix: correct spelling of Visualization to American English * feat: implement Camera Restrictions sample and visual test Implemented Camera Restrictions sample in Compose with visual test and updated catalog. * style: run spotlessApply to format code * style: clean up fully qualified names and format code * feat: implement missing compose samples and fix library bugs * chore: add default API key placeholders to local.defaults.properties * feat: implement Place Details sample with custom theme and fragment interop * feat: update initial camera for Place Details sample * feat: make Flatirons the first item in Place Details sample * chore: remove old screenshot * feat: add correct place details screenshot for Flatirons * chore: remove old screenshot again * feat: verify Place Details with strict prompt and longer delay * feat: add swipe gesture to Place Details visual test * feat: implement Place Details sample with custom theme and clean MVVM architecture * docs: add description column to Compose catalog README * fix: resolve build failures in CI and spotless - Fix ktlint property naming error in PlaceDetailsActivity - Add missing ProGuard rule files in snippets/common - Fix directory typo in build-advanced CI workflow * fix: add manifest placeholder for MAPS3D_API_KEY in all sample apps This resolves build failures during manifest merger tasks (like processDebugUnitTestManifest) which were missing the MAPS3D_API_KEY placeholder. * fix: make secrets.properties optional in CI to avoid FileNotFoundException --------- Co-authored-by: Enrique López Mañas <eenriquelopez@gmail.com>
1 parent 738aedc commit b0160ab

96 files changed

Lines changed: 7723 additions & 112 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,4 @@ jobs:
5252
distribution: 'adopt'
5353
java-version: '21'
5454
- name: Build advanced
55-
run: cd Maps3DSamples/ApiDemos && ./gradlew buildDebugPreBundle
55+
run: cd Maps3DSamples/advanced && ./gradlew buildDebugPreBundle
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# ☕ Java Samples Catalog
2+
3+
This directory contains the Java samples using traditional Android Views for the Android Maps 3D SDK.
4+
5+
## 📊 Sample Status
6+
7+
| Feature | Status | Source Code |
8+
| :--- | :--- | :--- |
9+
| **Hello Map** | ✅ Done | [HelloMapActivity.java](src/main/java/com/example/maps3djava/hellomap/HelloMapActivity.java) |
10+
| **Polylines** | ✅ Done | [PolylinesActivity.java](src/main/java/com/example/maps3djava/polylines/PolylinesActivity.java) |
11+
| **Map Interactions** | ✅ Done | [MapInteractionsActivity.java](src/main/java/com/example/maps3djava/mapinteractions/MapInteractionsActivity.java) |
12+
| **Popovers** | ✅ Done | [PopoversActivity.java](src/main/java/com/example/maps3djava/popovers/PopoversActivity.java) |
13+
| **Camera Controls** | ✅ Done | [CameraControlsActivity.java](src/main/java/com/example/maps3djava/cameracontrols/CameraControlsActivity.java) |
14+
| **Polygons** | ✅ Done | [PolygonsActivity.java](src/main/java/com/example/maps3djava/polygons/PolygonsActivity.java) |
15+
| **Models** | ✅ Done | [ModelsActivity.java](src/main/java/com/example/maps3djava/models/ModelsActivity.java) |
16+
| **Markers** | ✅ Done | [MarkersActivity.java](src/main/java/com/example/maps3djava/markers/MarkersActivity.java) |
17+
18+
---
19+
> [!NOTE]
20+
> These samples are view-based and serve as a reference for Java developers.

Maps3DSamples/ApiDemos/java-app/build.gradle.kts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ android {
8888
versionName = "1.0"
8989

9090
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
91+
manifestPlaceholders["MAPS3D_API_KEY"] = "DEFAULT_API_KEY"
9192
}
9293

9394
buildTypes {
@@ -147,12 +148,11 @@ dependencies {
147148
}
148149

149150
secrets {
150-
// Optionally specify a different file name containing your secrets.
151-
// The plugin defaults to "local.properties"
152-
propertiesFileName = "secrets.properties"
153-
154-
// A properties file containing default secret values. This file can be
155-
// checked in version control.
151+
// Only set propertiesFileName if the file exists to avoid FileNotFoundException in CI
152+
val secretsFile = rootProject.file("secrets.properties")
153+
if (secretsFile.exists()) {
154+
propertiesFileName = "secrets.properties"
155+
}
156156
defaultPropertiesFileName = "local.defaults.properties"
157157
}
158158

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# 🧱 Kotlin + Views Samples Catalog
2+
3+
This directory contains the Kotlin samples using traditional Android Views for the Android Maps 3D SDK.
4+
5+
## 📊 Sample Status
6+
7+
| Feature | Status | Source Code |
8+
| :--- | :--- | :--- |
9+
| **Hello Map** | ✅ Done | [HelloMapActivity.kt](src/main/java/com/example/maps3dkotlin/hellomap/HelloMapActivity.kt) |
10+
| **Polylines** | ✅ Done | [PolylinesActivity.kt](src/main/java/com/example/maps3dkotlin/polylines/PolylinesActivity.kt) |
11+
| **Map Interactions** | ✅ Done | [MapInteractionsActivity.kt](src/main/java/com/example/maps3dkotlin/mapinteractions/MapInteractionsActivity.kt) |
12+
| **Popovers** | ✅ Done | [PopoversActivity.kt](src/main/java/com/example/maps3dkotlin/popovers/PopoversActivity.kt) |
13+
| **Camera Controls** | ✅ Done | [CameraControlsActivity.kt](src/main/java/com/example/maps3dkotlin/cameracontrols/CameraControlsActivity.kt) |
14+
| **Polygons** | ✅ Done | [PolygonsActivity.kt](src/main/java/com/example/maps3dkotlin/polygons/PolygonsActivity.kt) |
15+
| **Models** | ✅ Done | [ModelsActivity.kt](src/main/java/com/example/maps3dkotlin/models/ModelsActivity.kt) |
16+
| **Markers** | ✅ Done | [MarkersActivity.kt](src/main/java/com/example/maps3dkotlin/markers/MarkersActivity.kt) |
17+
18+
---
19+
> [!NOTE]
20+
> These samples are view-based and serve as a reference for non-Compose applications.

Maps3DSamples/ApiDemos/kotlin-app/build.gradle.kts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,7 @@ android {
9191
versionName = "1.7.0"
9292

9393
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
94+
manifestPlaceholders["MAPS3D_API_KEY"] = "DEFAULT_API_KEY"
9495
}
9596

9697
buildTypes {
@@ -155,12 +156,11 @@ dependencies {
155156
}
156157

157158
secrets {
158-
// Optionally specify a different file name containing your secrets.
159-
// The plugin defaults to "local.properties"
160-
propertiesFileName = "secrets.properties"
161-
162-
// A properties file containing default secret values. This file can be
163-
// checked in version control.
159+
// Only set propertiesFileName if the file exists to avoid FileNotFoundException in CI
160+
val secretsFile = rootProject.file("secrets.properties")
161+
if (secretsFile.exists()) {
162+
propertiesFileName = "secrets.properties"
163+
}
164164
defaultPropertiesFileName = "local.defaults.properties"
165165
}
166166

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# 🚀 Jetpack Compose Samples Catalog
2+
3+
This directory contains the Compose samples for the Android Maps 3D SDK. We use a state-driven approach and lean on the `maps3d-compose` library.
4+
5+
## 📊 Sample Status
6+
7+
| Feature | Status | Source Code | Screenshot | Description |
8+
| :--- | :--- | :--- | :--- | :--- |
9+
| **Basic Map** | ✅ Done | [HelloMapActivity.kt](src/main/java/com/example/composedemos/hellomap/HelloMapActivity.kt) | <img src="src/main/assets/screenshots/hello_map_screenshot.png" alt="Screenshot" width="121"/> | Displays a basic 3D map with standard satellite imagery and initial camera placement. |
10+
| **Polylines** | ✅ Done | [PolylinesActivity.kt](src/main/java/com/example/composedemos/polylines/PolylinesActivity.kt) | <img src="src/main/assets/screenshots/polylines_screenshot.png" alt="Screenshot" width="121"/> | Demonstrates drawing 3D polylines on the map, including custom colors and widths. |
11+
| **Map Interactions** | 🚧 Skeleton | [MapInteractionsActivity.kt](src/main/java/com/example/composedemos/mapinteractions/MapInteractionsActivity.kt) | | Will demonstrate handling click and drag events on map objects. |
12+
| **Popovers** | ✅ Done | [PopoversActivity.kt](src/main/java/com/example/composedemos/popovers/PopoversActivity.kt) | <img src="src/main/assets/screenshots/popovers_screenshot.png" alt="Screenshot" width="121"/> | Shows how to display interactive popover overlays at specific coordinates on the map. |
13+
| **Camera Controls** | ✅ Done | [CameraControlsActivity.kt](src/main/java/com/example/composedemos/cameracontrols/CameraControlsActivity.kt) | <img src="src/main/assets/screenshots/camera_controls_screenshot.png" alt="Screenshot" width="121"/> | Demonstrates manual control of the camera center, heading, tilt, and range using UI controls. |
14+
| **Polygons** | ✅ Done | [PolygonsActivity.kt](src/main/java/com/example/composedemos/polygons/PolygonsActivity.kt) | <img src="src/main/assets/screenshots/polygons_screenshot.png" alt="Screenshot" width="121"/> | Demonstrates drawing 3D polygons with fill colors and outlines on the map. |
15+
| **Models** | ✅ Done | [ModelsActivity.kt](src/main/java/com/example/composedemos/models/ModelsActivity.kt) | <img src="src/main/assets/screenshots/models_screenshot.png" alt="Screenshot" width="121"/> | Shows how to load and place custom 3D models (gLTF) on the map with position, scale, and orientation. |
16+
| **Markers** | ✅ Done | [MarkersActivity.kt](src/main/java/com/example/composedemos/markers/MarkersActivity.kt) | <img src="src/main/assets/screenshots/markers_screenshot.png" alt="Screenshot" width="121"/> | Demonstrates adding 2D markers with custom icons and anchor points to the 3D map. |
17+
| **Camera Restrictions** | ✅ Done | [CameraRestrictionsActivity.kt](src/main/java/com/example/composedemos/camerarestrictions/CameraRestrictionsActivity.kt) | <img src="src/main/assets/screenshots/camera_restrictions_screenshot.png" alt="Screenshot" width="121"/> | Shows how to restrict the camera range and center bounds to a specific area. |
18+
| **Flight Simulator** | 🚧 Skeleton | [FlightSimulatorActivity.kt](src/main/java/com/example/composedemos/flightsimulator/FlightSimulatorActivity.kt) | | Will demonstrate a first-person camera view simulating flight. |
19+
| **Routes API** | ✅ Done | [RoutesActivity.kt](src/main/java/com/example/composedemos/routes/RoutesActivity.kt) | <img src="src/main/assets/screenshots/routes_screenshot.png" alt="Screenshot" width="121"/> | Demonstrates loading a route from file, rendering the polyline, and animating a 3D car model along the route using a flow-based engine. |
20+
| **Path Following** | 🚧 Skeleton | [PathFollowingActivity.kt](src/main/java/com/example/composedemos/pathfollowing/PathFollowingActivity.kt) | | Will demonstrate camera following a path. |
21+
| **Path Styling** | 🚧 Skeleton | [PathStylingActivity.kt](src/main/java/com/example/composedemos/pathstyling/PathStylingActivity.kt) | | Will demonstrate custom styling for paths. |
22+
| **Animating Models** | 🚧 Skeleton | [AnimatingModelsActivity.kt](src/main/java/com/example/composedemos/animatingmodels/AnimatingModelsActivity.kt) | | Will demonstrate animating 3D models. |
23+
| **Place Search** | 🚧 Skeleton | [PlaceSearchActivity.kt](src/main/java/com/example/composedemos/placesearch/PlaceSearchActivity.kt) | | Will demonstrate programmatic place search. |
24+
| **Place Autocomplete** | 🚧 Skeleton | [PlaceAutocompleteActivity.kt](src/main/java/com/example/composedemos/placeautocomplete/PlaceAutocompleteActivity.kt) | | Will demonstrate place autocomplete widget. |
25+
| **Place Details** | ✅ Done | [PlaceDetailsActivity.kt](src/main/java/com/example/composedemos/placedetails/PlaceDetailsActivity.kt) | <img src="src/main/assets/screenshots/place_details_screenshot.png" alt="Screenshot" width="121"/> | Demonstrates loading place details using the modern Places UI Kit fragment (Fragment Interop) with a custom 'Boulder Nature Hippie' theme. |
26+
| **Advanced Camera Animation** | 🚧 Skeleton | [AdvancedCameraAnimationActivity.kt](src/main/java/com/example/composedemos/advancedcameraanimation/AdvancedCameraAnimationActivity.kt) | | Will demonstrate complex camera animations. |
27+
| **Data Visualization** | 🚧 Skeleton | [DataVisualizationActivity.kt](src/main/java/com/example/composedemos/datavisualization/DataVisualizationActivity.kt) | | Will demonstrate visualizing data on 3D map. |
28+
| **Cloud Map Styling** | 🚧 Skeleton | [CloudStylingActivity.kt](src/main/java/com/example/composedemos/cloudstyling/CloudStylingActivity.kt) | | Will demonstrate styling map via Cloud console. |
29+
| **Roadmap Mode** | 🚧 Skeleton | [RoadmapModeActivity.kt](src/main/java/com/example/composedemos/roadmapmode/RoadmapModeActivity.kt) | | Will demonstrate standard roadmap mode. |
30+
| **Field Of View** | 🚧 Skeleton | [FieldOfViewActivity.kt](src/main/java/com/example/composedemos/fieldofview/FieldOfViewActivity.kt) | | Will demonstrate changing field of view. |
31+
32+
---
33+
> [!NOTE]
34+
> Status `🚧 Skeleton` means the activity exists and can be launched from the main list, but contains a TODO placeholder UI. We are actively implementing these following a TDD approach.
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
/*
2+
* Copyright 2026 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
plugins {
18+
alias(libs.plugins.android.application)
19+
alias(libs.plugins.kotlin.android)
20+
alias(libs.plugins.kotlin.compose)
21+
alias(libs.plugins.secrets.gradle.plugin)
22+
alias(libs.plugins.spotless)
23+
}
24+
25+
configure<com.diffplug.gradle.spotless.SpotlessExtension> {
26+
kotlin {
27+
target("**/*.kt")
28+
ktlint().editorConfigOverride(mapOf("indent_size" to "4", "ktlint_function_naming_ignore_when_annotated_with" to "Composable"))
29+
trimTrailingWhitespace()
30+
endWithNewline()
31+
}
32+
}
33+
34+
android {
35+
namespace = "com.example.composedemos"
36+
compileSdk = libs.versions.compileSdk.get().toInt()
37+
38+
defaultConfig {
39+
applicationId = "com.example.composedemos"
40+
minSdk = libs.versions.minSdk.get().toInt()
41+
targetSdk = libs.versions.targetSdk.get().toInt()
42+
versionCode = 1
43+
versionName = "1.0"
44+
45+
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
46+
manifestPlaceholders["MAPS3D_API_KEY"] = "DEFAULT_API_KEY"
47+
vectorDrawables {
48+
useSupportLibrary = true
49+
}
50+
}
51+
52+
buildTypes {
53+
release {
54+
isMinifyEnabled = false
55+
proguardFiles(
56+
getDefaultProguardFile("proguard-android-optimize.txt"),
57+
"proguard-rules.pro"
58+
)
59+
}
60+
}
61+
compileOptions {
62+
sourceCompatibility = JavaVersion.VERSION_11
63+
targetCompatibility = JavaVersion.VERSION_11
64+
}
65+
kotlin {
66+
compilerOptions {
67+
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_11)
68+
}
69+
}
70+
buildFeatures {
71+
compose = true
72+
buildConfig = true
73+
}
74+
packaging {
75+
resources {
76+
excludes += "/META-INF/{AL2.0,LGPL2.1}"
77+
}
78+
}
79+
}
80+
81+
dependencies {
82+
implementation(project(":maps3d-compose"))
83+
84+
implementation(libs.androidx.core.ktx)
85+
implementation(libs.androidx.lifecycle.runtime.ktx)
86+
implementation(libs.androidx.activity.compose)
87+
88+
implementation(platform(libs.androidx.compose.bom))
89+
implementation(libs.androidx.ui)
90+
implementation(libs.androidx.ui.graphics)
91+
implementation(libs.androidx.ui.tooling.preview)
92+
implementation(libs.androidx.material3)
93+
94+
// Maps 3D SDK
95+
implementation(libs.play.services.maps3d)
96+
implementation(libs.places)
97+
implementation(libs.androidx.fragment.ktx)
98+
99+
// Maps Utils
100+
implementation(libs.maps.utils.ktx)
101+
102+
// Material Icons Extended
103+
implementation(libs.androidx.material.icons.extended)
104+
105+
// Lifecycle ViewModel Compose
106+
implementation("androidx.lifecycle:lifecycle-viewmodel-compose")
107+
108+
testImplementation(libs.junit)
109+
testImplementation(libs.robolectric)
110+
androidTestImplementation(libs.androidx.junit)
111+
androidTestImplementation(libs.androidx.espresso.core)
112+
androidTestImplementation(platform(libs.androidx.compose.bom))
113+
androidTestImplementation(libs.androidx.ui.test.junit4)
114+
androidTestImplementation(libs.androidx.uiautomator)
115+
androidTestImplementation(libs.kotlinx.serialization.json)
116+
androidTestImplementation(project(":visual-testing"))
117+
debugImplementation(libs.androidx.ui.tooling)
118+
debugImplementation(libs.androidx.ui.test.manifest)
119+
}
120+
121+
secrets {
122+
// Only set propertiesFileName if the file exists to avoid FileNotFoundException in CI
123+
val secretsFile = rootProject.file("secrets.properties")
124+
if (secretsFile.exists()) {
125+
propertiesFileName = "secrets.properties"
126+
}
127+
defaultPropertiesFileName = "local.defaults.properties"
128+
}
129+
130+
tasks.register<Exec>("installAndLaunch") {
131+
description = "Installs and launches the demo app."
132+
group = "install"
133+
dependsOn("installDebug")
134+
commandLine("adb", "shell", "am", "start", "-n", "com.example.composedemos/.MainActivity")
135+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright 2026 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.example.composedemos
17+
18+
import android.app.Instrumentation
19+
import android.content.Context
20+
import android.graphics.Bitmap
21+
import android.graphics.BitmapFactory
22+
import androidx.test.platform.app.InstrumentationRegistry
23+
import androidx.test.uiautomator.By
24+
import androidx.test.uiautomator.UiDevice
25+
import androidx.test.uiautomator.Until
26+
import com.google.maps.android.visualtesting.GeminiVisualTestHelper
27+
import org.junit.Assert.assertTrue
28+
import java.io.File
29+
30+
abstract class BaseVisualTest {
31+
32+
protected val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
33+
protected val uiDevice = UiDevice.getInstance(instrumentation)
34+
protected val context: Context = instrumentation.targetContext
35+
protected val helper = GeminiVisualTestHelper()
36+
37+
protected val geminiApiKey: String by lazy {
38+
val key = BuildConfig.GEMINI_API_KEY
39+
assertTrue(
40+
"GEMINI_API_KEY is not set in secrets.properties. Please add GEMINI_API_KEY=YOUR_API_KEY to your secrets.properties file.",
41+
key != "YOUR_GEMINI_API_KEY",
42+
)
43+
key
44+
}
45+
46+
protected fun captureScreenshot(filename: String = "screenshot_${System.currentTimeMillis()}.png"): Bitmap {
47+
val screenshotFile = File(context.filesDir, filename)
48+
val screenshotTaken = uiDevice.takeScreenshot(screenshotFile)
49+
assertTrue("Failed to take screenshot: $filename", screenshotTaken)
50+
51+
val bitmap = BitmapFactory.decodeFile(screenshotFile.absolutePath)
52+
assertTrue("Failed to decode screenshot file: $filename", bitmap != null)
53+
54+
android.util.Log.i("BaseVisualTest", "Screenshot saved to device: ${screenshotFile.absolutePath}")
55+
56+
return bitmap
57+
}
58+
59+
/**
60+
* Waits for the map to render.
61+
* Since MapView content (tiles, markers) is rendered on a GL surface and not exposed as
62+
* accessibility nodes, we cannot rely on UiAutomator looking for text/markers.
63+
* We use a stable delay to ensure rendering is complete.
64+
*/
65+
protected fun waitForMapRendering(timeoutSeconds: Long = 30) {
66+
val found = uiDevice.wait(Until.hasObject(By.desc("MapSteady")), timeoutSeconds * 1000)
67+
assertTrue("Map did not become steady within $timeoutSeconds seconds", found)
68+
}
69+
}

0 commit comments

Comments
 (0)