You are working on the Google Maps 3D Android Samples repository (android-maps3d-samples). Apply the architecture and engineering standards defined below to guide code generation, feature development, and refactoring.
This repository contains both production-grade showcase architectures (like PlacesUIKit3D and new 3-way parity samples) and pre-existing legacy demos or minimal API snippets. To maintain real-world development velocity:
- New Features & Explicit Refactors:
- Follow the full Clean Architecture, MVVM, UDF, and testing standards defined below.
- Maintenance, Bug Fixes & Iterations on Existing Code:
- Never block user requests: If an existing feature or activity does not yet follow all layers or test tiers, do NOT refuse the task, demand a massive rewrite, or introduce unnecessary overhead.
- Targeted Scope: Focus directly and cleanly on what the user requested to change or fix.
- Pragmatic Boy Scout Rule: Leave touched lines cleaner (e.g., spotless formatting, no wildcards, no new anti-patterns) without disrupting working implementations or touching unrelated legacy code.
- Minimal API Demos vs Showcase Apps:
- Standalone API snippets designed to demonstrate single SDK calls do not require multi-module decomposition unless explicitly requested.
Refer to ARCHITECTURE.md for full architectural specifications.
Maps3DSamples(Feature Demos): Maintain 100% 3-way language parity across:- Kotlin Views (
Maps3DSamples/ApiDemos/kotlin-app) - Java Views (
Maps3DSamples/ApiDemos/java-app) - Jetpack Compose (
Maps3DSamples/ComposeDemos/app)
- Kotlin Views (
- Standalone Showcase Modules (e.g.,
PlacesUIKit3D): Decouple into Repository (Data), ViewModel (Presentation/UDF), Stateless Composables (UI), and Math/Kinematics helpers.
For Clean Architecture and MVVM modules, decouple business logic, math calculations, and repository fetching from Activity or Composable files:
- Domain Data & Models (
*Data.kt,*Model.kt): Immutable datasets, waypoints, constants, decoupled from raw SDK/network DTOs. - Repositories (
*Repository.kt): Abstract external SDK and data access behind interfaces with injectableCoroutineDispatcher. - Pure Math Engine (
*Engine.kt,*Animator.kt): Pure deterministic trigonometric, altitude, and kinematic functions (Zero Android UI / View / Context dependencies). - Pure State Machine Controller (
*Controller.kt): Pure Kotlin state machine managing progress, time integration, and step transitions. - Presentation ViewModel (
*ViewModel.kt,*State.kt): Android ViewModel exposing immutable UI state viaStateFlow(for Kotlin/Compose) andLiveData(for Java viaasLiveData()). Pure Kotlin/Compose modules rely solely onStateFlow. - Thin View Layer (
*Activity.kt,*Activity.java, Composable Screen): Thin views (~250 lines soft guideline) that only observe ViewModel state, forward user clicks, and apply map rendering updates.
Whenever creating new features or undertaking architectural refactors, maintain all applicable tiers of tests. For bug fixes or minor updates on existing code, focus tests on the touched area without requiring retroactive test suites:
- Tier 1: Engine & Math Unit Tests (
*EngineTest.kt,*AnimatorTest.kt): Tests angle normalization (360°/0° wrap-around), spherical math, coordinate bounds framing, and dynamic altitude calculation. - Tier 2: Controller & Repository Unit Tests (
*ControllerTest.kt,*RepositoryTest.kt): Tests state transitions, step sequencing, dwell delays, play/pause toggles, arrival conditions, and repository data mapping with test dispatchers. - Tier 3: ViewModel Unit Tests (
*ViewModelTest.kt): TestsStateFlow&LiveDataemissions, UI intent dispatches (selectPlace,flyToCommand,onFlyToCompleted,onFrameTick), and debounced flows usingrunTestandStandardTestDispatcher. - Tier 4: Visual Regression Tests (
*VisualTest.kt&*VisualTest.java): Automates UI Automator tests capturing live 3D map scene screenshots and validating visual correctness using the Gemini vision multimodal API.
- Kotlin Cross-Module Smart Casts: When referencing nullable properties of state classes defined in
commonor external models, always capture to a localvalfirst:val flyToCommand = state.flyToCommand if (flyToCommand != null) { executeNativeFlyTo(flyToCommand) }
- Injectable Coroutine Dispatchers: Never hardcode
Dispatchers.IOorDispatchers.Default. Always provide an injectable constructor parameter defaulting toDispatchers.IO:class PlacesRepositoryImpl( private val placesClient: PlacesClient, private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO ) : PlacesRepository
- Consumable Camera Commands: One-time camera animations (e.g. fly-to, auto-frame) must be consumed by the View and cleared immediately in the ViewModel via
onFlyToCompleted()to prevent animation loops on recomposition. - Explicit Altitude Modes: Always specify
AltitudeMode(CLAMP_TO_GROUND,RELATIVE_TO_GROUND, orABSOLUTE). Never assume ground altitude is0.0over 3D terrain meshes. - Hardware-Synchronized Tickers: Use
Choreographer.FrameCallbackin Views andwithFrameNanos/withFrameMillisin Compose for smooth 60/120fps animations. - StateFlow vs LiveData &
asLiveData():- Standardize on
StateFlowfor Kotlin Views and Jetpack Compose. Pure Kotlin/Compose modules must not exposeLiveData. - For shared hybrid ViewModels supporting Java Views (e.g.
Maps3DSamples/ApiDemos/common), exposeLiveDatacleanly usingasLiveData():This automatically handles coroutine thread-dispatching and eliminates redundant mutable backing properties (val liveData: LiveData<FeatureState> = _uiState.asLiveData()
_liveData) or manualpostValue()helpers.
- Standardize on
When generating, refactoring, or editing code, strictly adhere to these rules:
- Zero Wildcard Imports:
- ❌
import com.google.android.gms.maps.model.*is strictly forbidden. - ✅ Every imported symbol must be explicitly listed to conform to
ktlintandspotless.
- ❌
- Spotless Compliance:
- Always verify and run
./gradlew spotlessApplyon touched modules before finishing a task.
- Always verify and run
- Modern Kotlin Idioms & Pragmatic Warning Hygiene:
- Prefer modern standard library idioms where applicable (e.g.,
kotlin.time.Durationwithdelay(100.milliseconds)instead of legacy millisecondLongs). - Prefer Android KTX extensions (e.g.,
hexColorString.toColorInt()) over legacy utility methods. - Keep KDoc references clean by importing or fully qualifying symbols in brackets (e.g.,
[PlaceSearch3DScreenState]). - For unavoidable warnings (e.g., experimental Compose APIs or external SDK deprecations), use targeted
@OptIn(...)or@Suppress(...)with an explanatory comment rather than forcing brittle workarounds.
- Prefer modern standard library idioms where applicable (e.g.,
- Resource & Design Token Discipline (Zero Hardcoded Strings):
- ❌ No Hardcoded Strings: Hardcoding user-facing strings, titles, button labels, telemetry text, or format templates in Activity, Fragment, Composable, or Helper classes is strictly forbidden.
- ✅ Externalize to
strings.xml: Always externalize human-readable strings and format templates (e.g."%1$d px") tores/values/strings.xml. Reference them viastringResource(R.string...)in Jetpack Compose orgetString(R.string...)in Android Views. - Prefer theme tokens (
MaterialTheme.colorScheme.*) over hardcoded hex values. Document any canonical brand colors (e.g.#F4B400Google yellow) with an explanatory comment. - Compose state collections must use
collectAsStateWithLifecycle()fromandroidx.lifecycle.compose.
- KDoc / Javadoc Notation Discipline (No Raw LaTeX):
- ❌ No Raw LaTeX Markup: Do NOT write LaTeX markup (e.g.
$math$,$\mathbf{...}$,\Delta,\cdot) in KDoc or Javadoc comments. Standard Android Studio hover documentation tooltips, Dokka documentation pipelines, and GitHub code views do not parse LaTeX, leaving raw, unrendered markup. - ✅ Readable Unicode & Plain Text Math: Use standard Unicode characters (e.g.,
θ,φ,Δ,×,·,→, subscripts likeP₀, superscripts likeR²) and clean ASCII math expressions for universal readability across IDEs and documentation.
- ❌ No Raw LaTeX Markup: Do NOT write LaTeX markup (e.g.
- Snippet Region Tag Discipline (
snippets/):- When creating, modifying, or refactoring code in
snippets/, always preserve and properly place region tags (// [START ...]and// [END ...], along with// [START_EXCLUDE]/// [END_EXCLUDE]). - Ensures snippet boundaries remain discoverable and fully compatible with automated catalog scripts (
SAMPLE_CATALOG.md) and documentation extractors.
- When creating, modifying, or refactoring code in