Skip to content

Commit dc8f92f

Browse files
dkhawkkikoso
andauthored
feat: Integrates Places UI Kit 3D Sample (#31)
* feat: integrate PlacesUIKit3D sample module * refactor: apply DevRel Code Review recommendations and document fragment interop * refactor: apply DevRel Code Review UI Polish (extract strings, fix brittle layout padding, handle explicit location failure, remove magic numbers) * style: fix foldable layout constraints for BottomSheet and PlaceDetailsOverlay * Limits BottomSheet width to 600dp on large screens * Centers the BottomSheet modifier to look balanced on foldables * Allows PlaceDetailsCompactFragment to properly wrap its internal height * Automatically dismisses PlaceDetailsOverlay when bottom sheet is expanded * test: add MainViewModelTest to satisfy zero-trust testing directive * Verifies initial StateFlow states * Verifies Landmark and PlaceId selection flows * Proves PlaceDetails dismissal behavior when setting placeId to null * chore: remove devrel-code-reviewer workflow * fix(ui-kit): correct Map3dViewModel camera logic and method typos * Corrects a critical logic error where `setCameraTilt` incorrectly assigned its value to the `heading` property. * Resolves spelling errors in camera method signatures (`setCamaraRange` -> `setCameraRange`, `setCamaraRoll` -> `setCameraRoll`). * Adds comprehensive unit testing via `Map3dViewModelTest` to verify the immutability and accuracy of map CameraUpdate emissions. * Introduces the Robolectric test dependency to support the ViewModel coroutine scope in the test suite. * build: update gradle wrapper to 9.3.1 to support AGP 9.1.0 * build: fixed build * build: unify gradle wrapper versions to 9.4.1 * build: remove gradle wrapper distribution sha --------- Co-authored-by: Enrique López Mañas <eenriquelopez@gmail.com>
1 parent fe8aaae commit dc8f92f

57 files changed

Lines changed: 3233 additions & 22 deletions

Some content is hidden

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

Maps3DSamples/ApiDemos/gradle/wrapper/gradle-wrapper.properties

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
distributionBase=GRADLE_USER_HOME
22
distributionPath=wrapper/dists
3-
distributionSha256Sum=a17ddd85a26b6a7f5ddb71ff8b05fc5104c0202c6e64782429790c933686c806
4-
distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip
3+
distributionUrl=https\://services.gradle.org/distributions/gradle-9.4.1-bin.zip
54
networkTimeout=10000
65
validateDistributionUrl=true
76
zipStoreBase=GRADLE_USER_HOME

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ dependencies {
145145
androidTestImplementation(libs.androidx.espresso.core)
146146
androidTestImplementation(platform(libs.androidx.compose.bom))
147147
androidTestImplementation(libs.androidx.ui.test.junit4)
148-
androidTestImplementation(libs.truth)
148+
androidTestImplementation(libs.google.truth)
149149
debugImplementation(libs.androidx.ui.tooling)
150150
debugImplementation(libs.androidx.ui.test.manifest)
151151
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ dependencies {
150150
androidTestImplementation(libs.androidx.espresso.core)
151151
androidTestImplementation(platform(libs.androidx.compose.bom))
152152
androidTestImplementation(libs.androidx.ui.test.junit4)
153-
androidTestImplementation(libs.truth)
153+
androidTestImplementation(libs.google.truth)
154154
debugImplementation(libs.androidx.ui.tooling)
155155
debugImplementation(libs.androidx.ui.test.manifest)
156156

Maps3DSamples/advanced/app/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,3 +180,5 @@ tasks.register<Exec>("installAndLaunch") {
180180
dependsOn("installDebug")
181181
commandLine("adb", "shell", "am", "start", "-n", "com.example.advancedmaps3dsamples/.MainActivity")
182182
}
183+
184+
tasks.register("prepareKotlinBuildScriptModel"){}

Maps3DSamples/advanced/app/src/main/java/com/example/advancedmaps3dsamples/common/Map3dViewModel.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,7 @@ abstract class Map3dViewModel : ViewModel() {
348348

349349
open fun setCameraTilt(tilt: Number) {
350350
updateCameraAndMove {
351-
copy(heading = tilt.toTilt())
351+
copy(tilt = tilt.toTilt())
352352
}
353353
}
354354

Maps3DSamples/advanced/gradle/wrapper/gradle-wrapper.properties

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
distributionBase=GRADLE_USER_HOME
22
distributionPath=wrapper/dists
3-
distributionSha256Sum=a17ddd85a26b6a7f5ddb71ff8b05fc5104c0202c6e64782429790c933686c806
4-
distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip
3+
distributionUrl=https://services.gradle.org/distributions/gradle-9.4.1-bin.zip
54
networkTimeout=10000
65
validateDistributionUrl=true
76
zipStoreBase=GRADLE_USER_HOME

PlacesUIKit3D/build.gradle.kts

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
/*
2+
* Copyright 2025 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+
// The `plugins` block is where we apply Gradle plugins to this module.
18+
// Plugins add new tasks and configurations to our build process.
19+
plugins {
20+
// The core plugin for building an Android application. It provides tasks like `assembleDebug`, `installDebug`, etc.
21+
alias(libs.plugins.android.application)
22+
// This plugin enables Kotlin support in the Android project, allowing us to write code in Kotlin.
23+
alias(libs.plugins.kotlin.android)
24+
// This plugin from Google helps manage API keys and other secrets by reading them from a `secrets.properties`
25+
// file (which should be in .gitignore) and exposing them in the `BuildConfig` file at compile time.
26+
// This is crucial for keeping sensitive data out of version control.
27+
alias(libs.plugins.secrets.gradle.plugin)
28+
// This plugin provides the necessary integration for using Jetpack Compose with the Kotlin compiler.
29+
alias(libs.plugins.kotlin.compose)
30+
// KSP (Kotlin Symbol Processing) is used for annotation processing. Hilt uses it to generate code.
31+
alias(libs.plugins.ksp)
32+
// The Hilt plugin integrates Dagger Hilt for dependency injection.
33+
alias(libs.plugins.hilt.android)
34+
// The parcelize plugin provides a @Parcelize annotation to automatically generate Parcelable implementations.
35+
alias(libs.plugins.jetbrains.kotlin.parcelize)
36+
}
37+
38+
// The `android` block is where we configure all the Android-specific build options.
39+
android {
40+
// The `namespace` is a unique identifier for the app's generated R class. It's also used
41+
// as the default `applicationId` if not specified in `defaultConfig`.
42+
namespace = "com.example.placesuikit3d"
43+
// `compileSdk` specifies the Android API level the app is compiled against.
44+
// Using a recent version allows us to use the latest Android features.
45+
compileSdk = 36
46+
47+
defaultConfig {
48+
// `applicationId` is the unique identifier for the app on the Google Play Store and on the device.
49+
applicationId = "com.example.placesuikit3d"
50+
// `minSdk` is the minimum API level required to run the app. Devices below this level cannot install it.
51+
minSdk = 29
52+
// `targetSdk` indicates the API level the app was tested against. Android may enable
53+
// compatibility behaviors on newer OS versions if the target is lower.
54+
targetSdk = 36
55+
versionCode = 1
56+
versionName = "1.0"
57+
58+
// Specifies the instrumentation runner for running Android tests.
59+
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
60+
}
61+
62+
buildTypes {
63+
// The `release` block configures settings for the release build of the app.
64+
release {
65+
// `isMinifyEnabled` enables code shrinking with R8 to reduce the app's size.
66+
// It's disabled here for simplicity in a sample app, but highly recommended for production.
67+
isMinifyEnabled = false
68+
// `proguardFiles` specifies the files that define the R8 shrinking and obfuscation rules.
69+
proguardFiles(
70+
getDefaultProguardFile("proguard-android-optimize.txt"),
71+
"proguard-rules.pro"
72+
)
73+
}
74+
}
75+
compileOptions {
76+
// Sets the Java language compatibility for the source code and compiled bytecode.
77+
// Using Java 17 is required for modern Android development.
78+
sourceCompatibility = JavaVersion.VERSION_17
79+
targetCompatibility = JavaVersion.VERSION_17
80+
}
81+
82+
kotlin {
83+
jvmToolchain(17)
84+
}
85+
86+
buildFeatures {
87+
// `viewBinding` generates a binding class for each XML layout file, providing a type-safe
88+
// way to access views without `findViewById`. This is used in the XML-based activities.
89+
viewBinding = true
90+
// `compose` enables Jetpack Compose for the project.
91+
compose = true
92+
// `buildConfig` generates a `BuildConfig` class that contains constants from the build configuration,
93+
// such as the API key from the secrets plugin.
94+
buildConfig = true
95+
}
96+
97+
java {
98+
// Specifies the Java language version for the project's toolchain.
99+
toolchain {
100+
languageVersion.set(JavaLanguageVersion.of(17))
101+
}
102+
}
103+
composeOptions {
104+
// Sets the version of the Kotlin compiler extension for Compose. This version must be
105+
// compatible with the Kotlin version used in the project.
106+
kotlinCompilerExtensionVersion = "1.5.1"
107+
}
108+
}
109+
110+
// The `dependencies` block is where we declare all the external libraries the app needs.
111+
// These are fetched from repositories like Maven Central and Google's Maven repository.
112+
dependencies {
113+
// --- Core AndroidX & UI Libraries ---
114+
// These are foundational libraries for building modern Android apps.
115+
implementation(libs.androidx.core.ktx)
116+
implementation(libs.androidx.lifecycle.runtime.ktx)
117+
implementation(libs.androidx.fragment.ktx)
118+
implementation(libs.material) // For Material Design components (used in XML layouts).
119+
120+
// --- Jetpack Compose ---
121+
// These libraries are for building UIs with Jetpack Compose.
122+
implementation(libs.androidx.activity.compose) // Integration between Activity and Compose.
123+
implementation(platform(libs.androidx.compose.bom)) // The Compose Bill of Materials (BOM) ensures all Compose libraries use compatible versions.
124+
implementation(libs.androidx.ui)
125+
implementation(libs.androidx.ui.graphics)
126+
implementation(libs.androidx.ui.tooling.preview) // For displaying @Preview composables in Android Studio.
127+
implementation(libs.androidx.material3) // The latest Material Design components for Compose.
128+
implementation(libs.androidx.fragment.compose)
129+
implementation(libs.androidx.material.icons.extended)
130+
debugImplementation(libs.androidx.ui.tooling) // Provides tools for inspecting Compose UIs.
131+
132+
// --- Google Play Services ---
133+
// These are the essential libraries for this sample, providing Maps and Places functionality.
134+
implementation(libs.play.services.maps3d) // The core SDK for embedding 3D Google Maps.
135+
implementation(libs.places) // The SDK for the Places UI Kit (PlaceDetails fragments).
136+
implementation(libs.maps.utils.ktx) // Google Maps Utils for polyline decoding and other utilities.
137+
138+
// --- Dependency Injection ---
139+
// Hilt is used for managing dependencies and object lifecycles.
140+
implementation(libs.dagger)
141+
ksp(libs.hilt.android.compiler)
142+
implementation(libs.hilt.android)
143+
144+
// --- Miscellaneous ---
145+
implementation(libs.kotlinx.datetime)
146+
147+
// --- Testing Libraries ---
148+
// These libraries are for writing and running tests.
149+
// `testImplementation` is for local unit tests (running on the JVM).
150+
testImplementation(libs.junit)
151+
testImplementation(libs.google.truth)
152+
testImplementation(libs.robolectric)
153+
// `androidTestImplementation` is for instrumented tests (running on an Android device or emulator).
154+
androidTestImplementation(libs.androidx.junit)
155+
androidTestImplementation(libs.androidx.espresso.core) // For UI testing with the View system.
156+
157+
// --- Compose Testing ---
158+
// These are specific to testing Jetpack Compose UIs.
159+
androidTestImplementation(platform(libs.androidx.compose.bom)) // BOM for testing libraries.
160+
androidTestImplementation(libs.androidx.ui.test.junit4) // The main library for Compose UI tests.
161+
debugImplementation(libs.androidx.ui.test.manifest) // Provides a manifest for UI tests.
162+
}
163+
164+
// This block configures the Secrets Gradle Plugin.
165+
secrets {
166+
// Specifies a default properties file. This is useful for CI/CD environments where
167+
// you might not have a local `secrets.properties` file.
168+
defaultPropertiesFileName = "local.defaults.properties"
169+
// Specifies the local properties file where secret keys (like the Places API key) are stored.
170+
// This file should be added to .gitignore to prevent it from being committed to version control.
171+
propertiesFileName = "secrets.properties"
172+
}
173+
174+
tasks.register<Exec>("installAndLaunch") {
175+
description = "Installs the debug APK and launches the main activity."
176+
group = "application"
177+
dependsOn("installDebug")
178+
commandLine("adb", "shell", "am", "start", "-n", "com.example.placesuikit3d/com.example.placesuikit3d.MainActivity")
179+
}

PlacesUIKit3D/proguard-rules.pro

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Add project specific ProGuard rules here.
2+
# You can control the set of applied configuration files using the
3+
# proguardFiles setting in build.gradle.
4+
#
5+
# For more details, see
6+
# http://developer.android.com/guide/developing/tools/proguard.html
7+
8+
# If your project uses WebView with JS, uncomment the following
9+
# and specify the fully qualified class name to the JavaScript interface
10+
# class:
11+
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12+
# public *;
13+
#}
14+
15+
# Uncomment this to preserve the line number information for
16+
# debugging stack traces.
17+
#-keepattributes SourceFile,LineNumberTable
18+
19+
# If you keep the line number information, uncomment this to
20+
# hide the original source file name.
21+
#-renamesourcefileattribute SourceFile
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!--
3+
Copyright 2025 Google LLC
4+
5+
Licensed under the Apache License, Version 2.0 (the "License");
6+
you may not use this file except in compliance with the License.
7+
You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
-->
17+
18+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
19+
xmlns:tools="http://schemas.android.com/tools">
20+
21+
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
22+
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
23+
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
24+
25+
26+
<application
27+
android:name=".Maps3DPlacesApplication"
28+
android:allowBackup="true"
29+
android:dataExtractionRules="@xml/data_extraction_rules"
30+
android:fullBackupContent="@xml/backup_rules"
31+
android:icon="@mipmap/ic_launcher"
32+
android:label="@string/app_name"
33+
android:roundIcon="@mipmap/ic_launcher_round"
34+
android:supportsRtl="true"
35+
android:theme="@style/Theme.PlacesUIKit3D"
36+
tools:targetApi="31">
37+
38+
<meta-data
39+
android:name="com.google.android.geo.maps3d.API_KEY"
40+
android:value="${MAPS3D_API_KEY}"
41+
/>
42+
43+
<activity
44+
android:name=".MainActivity"
45+
android:exported="true"
46+
android:label="@string/app_name"
47+
android:theme="@style/Theme.PlacesUIKit3D">
48+
<intent-filter>
49+
<action android:name="android.intent.action.MAIN" />
50+
51+
<category android:name="android.intent.category.LAUNCHER" />
52+
</intent-filter>
53+
</activity>
54+
</application>
55+
56+
</manifest>
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Copyright 2026 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.example.placesuikit3d
16+
17+
import com.google.android.gms.maps3d.model.LatLngAltitude
18+
19+
/**
20+
* A data class representing a landmark in the demo.
21+
*
22+
* @property id The unique Place ID for this landmark.
23+
* @property name The human-readable name of the landmark.
24+
* @property location The coordinates on the 3D map where the camera should point.
25+
*/
26+
data class Landmark(
27+
val id: String,
28+
val name: String,
29+
val location: LatLngAltitude
30+
)

0 commit comments

Comments
 (0)