From 05a38fee4a19cbee0e52d020b26be9d6cdc32a54 Mon Sep 17 00:00:00 2001
From: dkhawk <107309+dkhawk@users.noreply.github.com>
Date: Fri, 10 Apr 2026 14:44:23 -0600
Subject: [PATCH 01/11] feat(skill): Enhance Android Maps 3D SDK skill with
L1/L2 structure and Kotlin Views template
---
.gemini/skills/android-maps3d-sdk/SKILL.md | 250 +++---------------
.../references/kotlin_views_template.md | 205 ++++++++++++++
skill-dev.md | 215 +++++++++++++++
3 files changed, 456 insertions(+), 214 deletions(-)
create mode 100644 .gemini/skills/android-maps3d-sdk/references/kotlin_views_template.md
create mode 100644 skill-dev.md
diff --git a/.gemini/skills/android-maps3d-sdk/SKILL.md b/.gemini/skills/android-maps3d-sdk/SKILL.md
index 0692098..d2446d1 100644
--- a/.gemini/skills/android-maps3d-sdk/SKILL.md
+++ b/.gemini/skills/android-maps3d-sdk/SKILL.md
@@ -1,27 +1,41 @@
---
name: android-maps3d-sdk
-description: Guide for integrating the Google Maps 3D SDK into an Android Jetpack Compose application. Use when users ask to add Maps 3D, 3D maps, or Map3DView to their Android app in Compose.
+description: Use this skill when the user wants to integrate the Google Maps 3D SDK into an Android application. This skill provides procedural guidance for setup, lifecycle, and 3D object manipulation. Do NOT use for standard 2D maps.
---
# Android Maps 3D SDK Integration
-You are an expert Android developer specializing in Jetpack Compose and modern Android architecture. Follow these instructions carefully to integrate the `play-services-maps3d` library into the user's Android application.
+This skill guides you through integrating the Google Maps 3D SDK into an Android project. It follows the principles of progressive disclosure and relies on environment-specific templates.
-We should start with a few questions about how the developer want to use `Maps3DView`.
+## Prerequisites & Skill Alignment
-Are they using or planning on using Jetpack Compose?
+> [!NOTE]
+> This skill should be used in conjunction with:
+> - **Android Architecture Skill**: For proper MVVM/MVI layering.
+> - **Android Security Skill**: For API key protection and permissions.
-Are they using or planning on using dependency injection (such as Hilt or Koin)?
+## Procedural Workflow
-## 1. Setup Dependencies
+### Step 1: Determine the Environment
+You MUST ask the user which development environment they are targeting before providing code:
+1. **Kotlin (Views)** (Prioritized)
+2. **Kotlin + Compose**
+3. **Java**
-First, add the necessary versions and libraries to your `libs.versions.toml` file:
+Additionally, ask if they want to use the **Object Tracking Delegate** pattern (useful for clean cleanup in complex apps).
+
+Based on their response, you will load the appropriate template from the `references/` directory.
+
+### Step 2: Base Setup
+Regardless of the environment, the following setup is required.
+
+#### 1. Dependencies
+Add the necessary versions and libraries to your `libs.versions.toml` file:
```toml
[versions]
-# NOTE: Verify this is the latest version of the Maps 3D SDK, as it is subject to change.
+# NOTE: Verify this is the latest version of the Maps 3D SDK
playServicesMaps3d = "0.2.0"
-# NOTE: Verify this is the latest version of lifecycle-runtime-ktx.
lifecycleRuntimeKtx = "2.8.5"
[libraries]
@@ -29,20 +43,18 @@ play-services-maps3d = { group = "com.google.android.gms", name = "play-services
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
```
-Then, add the dependencies to the app-level `build.gradle.kts` file.
+Then, add the dependencies to the app-level `build.gradle.kts` file:
```kotlin
dependencies {
// Google Maps 3D SDK
implementation(libs.play.services.maps3d)
-
// Lifecycle Runtime KTX for Coroutine interop
implementation(libs.androidx.lifecycle.runtime.ktx)
}
```
-## 2. Setup the Secrets Gradle Plugin
-
+#### 2. API Key & Manifest
Use the Secrets Gradle Plugin for Android to inject the API key securely. In app-level `build.gradle.kts`:
```kotlin
@@ -64,7 +76,6 @@ In `AndroidManifest.xml`, add the required permissions and reference the injecte
-
```
-Add the API Key to `secrets.properties`:
-
-```properties
-MAPS3D_API_KEY=YOUR_API_KEY
-```
-
-## 3. Implement the Map3D Container Composable
-
-If the user is working in a Jetpack Compose app or is creating a Compose app, We can use an
-`AndroidView` to bridge between the View-based `Map3DView` and Jetpack Compose.
-
-```kotlin
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.runtime.*
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.viewinterop.AndroidView
-import com.google.android.gms.maps.model.LatLng
-import com.google.android.gms.maps.model.Map3DMode
-import com.google.android.gms.maps.model.Map3DOptions
-import com.google.android.gms.maps.Map3DView
-import com.google.android.gms.maps.GoogleMap3D
-import com.google.android.gms.maps.OnMap3DViewReadyCallback
-
-@Composable
-fun Map3DContainer(
- modifier: Modifier = Modifier,
- options: Map3DOptions
-) {
- // 1. Hoist State: Remember the map object
- var googleMap by remember { mutableStateOf(null) }
-
- Box(modifier = modifier.fillMaxSize()) {
- AndroidView(
- modifier = Modifier.fillMaxSize(),
- factory = { context ->
- Map3DView(context, options).apply {
- // Manually call onCreate.
- onCreate(null)
- }
- },
- update = { view ->
- view.getMap3DViewAsync(
- object : OnMap3DViewReadyCallback {
- override fun onMap3DViewReady(map3D: GoogleMap3D) {
- googleMap = map3D // Capture the controller
- }
- override fun onError(e: Exception) {
- googleMap = null
- throw e
- }
- }
- )
- },
- onRelease = { view ->
- googleMap = null
- view.onDestroy()
- }
- )
- }
-}
-```
-
-## 4. Best Practices & Guidelines
-* **Double-Wait Pattern:** Triggering animations from Compose buttons requires the **Double-Wait** pattern (`awaitCameraAnimation` + `awaitSteady`) to ensure peak visual quality.
-* **Coroutine Bridging:** Animations in the 3D SDK are fire-and-forget. Use an `awaitCameraAnimation(map: GoogleMap3D)` suspend wrapper function using `suspendCancellableCoroutine` for structured concurrency:
-
-```kotlin
-suspend fun awaitCameraAnimation(map: GoogleMap3D) = suspendCancellableCoroutine { continuation ->
- map.setCameraAnimationEndListener {
- map.setCameraAnimationEndListener(null) // Cleanup listener to avoid leaks
- if (continuation.isActive) {
- continuation.resume(Unit)
- }
- }
- continuation.invokeOnCancellation {
- map.setCameraAnimationEndListener(null)
- }
-}
-```
-
-* **Lifecycle:** You must pass lifecycle events down to `Map3DView`. In Compose, `factory` block takes care of instantiation and `onRelease` handles cleanup (`onDestroy()`). Ensure `onCreate` is called in the factory block.
- * *Critical Note:* The underlying `GoogleMap3D` engine instance is effectively created once per application lifecycle. If your `AndroidView` Composable leaves the composition and later returns (creating a new `Map3DView`), the underlying 3D engine may still retain previously added objects (like Polygons) from the destroyed view. You must manually clear or track your objects to avoid duplicates across recompositions or Navigation transitions.
-* **Initialization & Adding Objects:** Do **not** attempt to set the camera or add 3D objects (like Polygons) immediately after the `GoogleMap3D` reference is ready. The renderer needs time to warm up.
- * **Initial Camera:** Always set the initial camera position declaratively via `Map3DOptions` (passed into your container view) rather than imperatively moving the camera after the map loads. This avoids dizzying "flight" animations from coordinate `(0,0)` on startup.
- * **Adding Objects:** Only inject geometries into the scene after the map has signaled it is fully ready and stable. Typically, this means waiting for an `onMapSteady` callback.
-* **Updating Map Objects:** When updating an existing Map Object (e.g., `Polygon`, `Polyline`), do **not** use `remove()` and re-add a new one, as this causes flickering. Instead, use `getId()` from the existing object and pass it to a new `PolygonOptions` (or equivalent) builder, then call `addPolygon()` with those new options on the same `GoogleMap3D` instance. The SDK uses the matching ID to update the existing object gracefully without flickering.
-* **Nullable Camera Properties:** The 3D SDK's `Camera` object has 6 degrees of freedom. Properties like `heading`, `tilt`, `roll`, and `range` are returned as `Double?` (nullable) since the renderer does not always guarantee a value for every property. Handle these nulls defensively when extracting camera telemetry, especially when persisting position data.
-* **Parameter Validation:** The Maps 3D library will throw exceptions and crash if passed out-of-bounds telemetry for camera movements or locations. Standardize a validation/coercion layer (e.g., returning a `toValidCamera()` extension object) covering:
- * `latitude`: clamped to `[-90.0, 90.0]`
- * `longitude`: clamped to `[-180.0, 180.0]`
- * `tilt`: clamped to `[0.0, 90.0]`
- * `range`: clamped to `[0.0, 63170000.0]`
- * `heading`: wrapped to `[0.0, 360.0]`
- * `roll`: wrapped to `[-360.0, 360.0]`
- * `altitude`: clamped to `[0.0, MAX_ALTITUDE_METERS]`
-
- **Example Extension:**
- ```kotlin
- /** Helper to wrap cyclic values like heading and roll */
- fun Double.wrapIn(lower: Double, upper: Double): Double {
- val range = upper - lower
- if (range <= 0) return this
- val offset = this - lower
- return lower + (offset - Math.floor(offset / range) * range)
- }
-
- /** Extension to sanitize camera telemetry before passing to engine */
- fun Camera?.toValidCamera(): Camera {
- val source = this ?: return Camera.DEFAULT_CAMERA
- return camera {
- center = latLngAltitude {
- latitude = source.center.latitude.coerceIn(-90.0..90.0)
- longitude = source.center.longitude.coerceIn(-180.0..180.0)
- altitude = source.center.altitude.coerceIn(0.0..LatLngAltitude.MAX_ALTITUDE_METERS)
- }
- heading = source.heading?.toDouble()?.wrapIn(0.0, 360.0) ?: 0.0
- tilt = source.tilt?.toDouble()?.coerceIn(0.0..90.0) ?: 60.0
- roll = source.roll?.toDouble()?.wrapIn(-360.0, 360.0) ?: 0.0
- range = source.range?.toDouble()?.coerceIn(0.0..63170000.0) ?: 1500.0
- }
- }
- ```
-
-* **Immutable Updates (`copy` Extensions):** The 3D SDK builders (like `camera {}` or `latLngAltitude {}`) do not natively provide a `copy()` method like Kotlin data classes. To gracefully update a single property (like altitude) while retaining the rest of the object's complex state, implement custom `.copy()` extensions:
-
- ```kotlin
- /** Extension to clone and modify a Camera */
- fun Camera.copy(
- center: LatLngAltitude? = null,
- heading: Double? = null,
- tilt: Double? = null,
- range: Double? = null,
- roll: Double? = null,
- ): Camera {
- val objectToCopy = this
- return camera {
- this.center = center ?: objectToCopy.center
- this.heading = heading ?: objectToCopy.heading
- this.tilt = tilt ?: objectToCopy.tilt
- this.range = range ?: objectToCopy.range
- this.roll = roll ?: objectToCopy.roll
- }
- }
-
- /** Extension to clone and modify a LatLngAltitude */
- fun LatLngAltitude.copy(
- latitude: Double? = null,
- longitude: Double? = null,
- altitude: Double? = null,
- ): LatLngAltitude {
- val objectToCopy = this
- return latLngAltitude {
- this.latitude = latitude ?: objectToCopy.latitude
- this.longitude = longitude ?: objectToCopy.longitude
- this.altitude = altitude ?: objectToCopy.altitude
- }
- }
- ```
-
-## 5. A Note on Initialization
-
-Immediate Setup (onMap3DViewReady): Fails on cold starts because the viewport layout and binding matrix are not yet stable. Camera updates are completely ignored, and overlays may render offset.
-OnMapReady & OnMapSteady Listeners: These callbacks are strictly edge-triggered. While they may fire on a cold start, they will skip execution entirely on a warm restore (e.g., returning to the Activity) because the view is already considered ready/steady. This leaves the user with a frozen camera state and missing overlays.
-The Solution: Timer-Based Delay Workaround
-Until the SDK introduces native Coroutine support (like an .awaitMap() extension) or synchronous state getters (like isMapReady), the most reliable workaround for both cold and warm starts is a timer-based delay. By intentionally deferring the initialization logic slightly, we bypass the brittle edge-triggered listeners entirely.
-
-Kotlin Implementation (Preferred)
-Use a coroutine with delay() inside your initialization flow:
-
- ```kotlin
- // Ensure you are launching on the Main thread to interact with the Map3DView safely
- lifecycleScope.launch {
- // Wait for the viewport to fully inflate and bindings to stabilize.
- // 500ms is a safe brute-force threshold to avoid edge-trigger races.
- delay(500)
-
- // Position camera and add overlays safely
- setupMapElements()
- }
- ```
-
-Java Implementation
-Use a standard Handler mapped to the Main Looper:
-
- ```java
- new Handler(Looper.getMainLooper()).postDelayed(() -> {
- // Wait for the viewport to fully inflate, then safely apply updates
- setupMapElements();
- }, 500);
- ```
+### Step 3: Load Environment Template
+After the user selects the environment, load the corresponding file from:
+- Kotlin (Views): `references/kotlin_views_template.md`
+- Kotlin + Compose: (To be created)
+- Java: (To be created)
-[IMPORTANT] Even with the timer delay successfully ensuring your camera updates fire, you must still implement an isInitialized boolean latch
-(or dynamically check if your layers exist) within setupMapElements(). Otherwise, you will endlessly stack duplicate markers, model nodes,
-and polyline overlays on top of each other during every warm Activity re-entry.
+### Step 4: Apply Best Practices
+Follow these strict procedural rules to avoid common pitfalls:
-## 6. Execution Steps
-1. Add the 3D Maps SDK dependencies.
-2. Setup the Secrets Gradle plugin if not already set.
-3. Update `AndroidManifest.xml` with the specific `com.google.android.geo.maps3d.API_KEY` tag.
-4. Create the `Map3DContainer` composable wrapped in `AndroidView`.
-5. Inform the user how to add `MAPS3D_API_KEY` securely.
+1. **Initialization Delay**: Fails on cold starts due to viewport layout races. Always use a 500ms delay before initializing map elements (camera updates, adding objects).
+2. **Double-Wait Pattern**: For animations, wait for camera animation end AND map steady state.
+3. **Object Updates**: Use matching IDs to update Polygons/Polylines instead of removing and re-adding (prevents flickering).
+4. **Null Safety**: Camera properties like heading and tilt can be null. Handle them defensively.
diff --git a/.gemini/skills/android-maps3d-sdk/references/kotlin_views_template.md b/.gemini/skills/android-maps3d-sdk/references/kotlin_views_template.md
new file mode 100644
index 0000000..00b588e
--- /dev/null
+++ b/.gemini/skills/android-maps3d-sdk/references/kotlin_views_template.md
@@ -0,0 +1,205 @@
+# Kotlin (Views) Template for Android Maps 3D SDK
+
+Use this template when building an Android application using Kotlin and standard XML Views (not Compose).
+
+## 1. Layout XML
+
+Create a layout file (e.g., `activity_main.xml`) and add the `Map3DView`.
+
+```xml
+
+
+
+
+
+
+```
+
+## 2. Activity Code
+
+Implement the activity to handle the map lifecycle and initialization.
+
+```kotlin
+import android.os.Bundle
+import android.util.Log
+import androidx.appcompat.app.AppCompatActivity
+import androidx.lifecycle.DefaultLifecycleObserver
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import com.google.android.gms.maps3d.GoogleMap3D
+import com.google.android.gms.maps3d.Map3DView
+import com.google.android.gms.maps3d.OnMap3DViewReadyCallback
+import com.google.android.gms.maps3d.model.camera
+import com.google.android.gms.maps3d.model.latLngAltitude
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+
+class MainActivity : AppCompatActivity() {
+
+ private lateinit var mapView: Map3DView
+ private var googleMap: GoogleMap3D? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_main)
+
+ mapView = findViewById(R.id.map_view)
+ mapView.onCreate(savedInstanceState)
+
+ // Use DefaultLifecycleObserver to handle most lifecycle events automatically
+ lifecycle.addObserver(object : DefaultLifecycleObserver {
+ override fun onStart(owner: LifecycleOwner) { mapView.onStart() }
+ override fun onResume(owner: LifecycleOwner) { mapView.onResume() }
+ override fun onPause(owner: LifecycleOwner) { mapView.onPause() }
+ override fun onStop(owner: LifecycleOwner) { mapView.onStop() }
+ override fun onDestroy(owner: LifecycleOwner) { mapView.onDestroy() }
+ })
+
+ mapView.getMap3DViewAsync(object : OnMap3DViewReadyCallback {
+ override fun onMap3DViewReady(map: GoogleMap3D) {
+ googleMap = map
+
+ // Fails on cold starts because the viewport layout and binding matrix are not yet stable.
+ // Use a timer-based delay workaround.
+ lifecycleScope.launch {
+ // Wait for the viewport to fully inflate and bindings to stabilize.
+ delay(500)
+ setupMapElements()
+ }
+ }
+
+ override fun onError(e: Exception) {
+ Log.e("MainActivity", "Error loading map", e)
+ }
+ })
+ }
+
+ private fun setupMapElements() {
+ val map = googleMap ?: return
+
+ // Example: Set initial camera position
+ val initialCamera = camera {
+ center = latLngAltitude {
+ latitude = 40.0150
+ longitude = -105.2705
+ altitude = 5000.0
+ }
+ heading = 0.0
+ tilt = 45.0
+ roll = 0.0
+ range = 10000.0
+ }
+ map.setCamera(initialCamera)
+
+ // Add more initialization logic here (markers, polylines, etc.)
+ }
+
+ // onLowMemory and onSaveInstanceState still need to be forwarded manually
+ override fun onLowMemory() {
+ super.onLowMemory()
+ mapView.onLowMemory()
+ }
+
+ override fun onSaveInstanceState(outState: Bundle) {
+ super.onSaveInstanceState(outState)
+ mapView.onSaveInstanceState(outState)
+ }
+}
+```
+
+## 3. Best Practices
+
+- **Lifecycle**: Always forward lifecycle methods to `Map3DView`.
+- **Initialization Delay**: Use the 500ms delay pattern shown above to avoid edge-trigger races on startup.
+- **State Latch**: Implement an `isInitialized` boolean latch in `setupMapElements()` if you plan to call it multiple times or across lifecycle events to avoid stacking duplicate objects.
+
+## 4. Optional: Object Tracking Delegate
+
+For advanced use cases where you need to add and remove many objects (Markers, Polylines, etc.) dynamically, it is helpful to use a delegate wrapper to track these objects for easy cleanup.
+
+Here is a simplified version of the `TrackedMap3D` pattern used in the project samples:
+
+```kotlin
+import com.google.android.gms.maps3d.GoogleMap3D
+import com.google.android.gms.maps3d.model.Marker
+import com.google.android.gms.maps3d.model.MarkerOptions
+import com.google.android.gms.maps3d.model.Polygon
+import com.google.android.gms.maps3d.model.PolygonOptions
+import com.google.android.gms.maps3d.model.Polyline
+import com.google.android.gms.maps3d.model.PolylineOptions
+import com.google.android.gms.maps3d.model.Camera
+
+class TrackedMap3D(
+ val delegate: GoogleMap3D,
+ private val items: MutableList = mutableListOf()
+) {
+ fun addMarker(options: MarkerOptions): Marker? {
+ val marker = delegate.addMarker(options)
+ if (marker != null) items.add(marker)
+ return marker
+ }
+
+ fun addPolyline(options: PolylineOptions): Polyline? {
+ val polyline = delegate.addPolyline(options)
+ if (polyline != null) items.add(polyline)
+ return polyline
+ }
+
+ fun addPolygon(options: PolygonOptions): Polygon? {
+ val polygon = delegate.addPolygon(options)
+ if (polygon != null) items.add(polygon)
+ return polygon
+ }
+
+ /**
+ * Clears all tracked objects from the map.
+ */
+ fun clearAll() {
+ items.forEach { item ->
+ when (item) {
+ is Marker -> item.remove()
+ is Polyline -> item.remove()
+ is Polygon -> item.remove()
+ }
+ }
+ items.clear()
+ }
+
+ // Forward other necessary methods to the delegate as needed
+ fun setCamera(camera: Camera) = delegate.setCamera(camera)
+}
+```
+
+To use it, wrap the `GoogleMap3D` instance and ensure you clear it in `onDestroy` to avoid retaining objects across Activity instances (since the 3D engine persists across Activity recreation):
+
+```kotlin
+private var trackedMap: TrackedMap3D? = null
+
+override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ // ...
+
+ lifecycle.addObserver(object : DefaultLifecycleObserver {
+ // ...
+ override fun onDestroy(owner: LifecycleOwner) {
+ // Automatically clean up objects to avoid cruft
+ trackedMap?.clearAll()
+ mapView.onDestroy()
+ }
+ })
+
+ mapView.getMap3DViewAsync(object : OnMap3DViewReadyCallback {
+ override fun onMap3DViewReady(map: GoogleMap3D) {
+ trackedMap = TrackedMap3D(map)
+ // ...
+ }
+ // ...
+ })
+}
+```
+
diff --git a/skill-dev.md b/skill-dev.md
new file mode 100644
index 0000000..7805c15
--- /dev/null
+++ b/skill-dev.md
@@ -0,0 +1,215 @@
+# **Architectural Frameworks for Advanced Agentic Skill Development: A Technical Synthesis**
+
+The rapid maturation of Large Language Model (LLM) ecosystems has necessitated a transition from static Software Development Kits (SDKs) to dynamic, agent-centric capabilities known as Agentic Skills. This architectural shift represents a fundamental move from code libraries designed for human invocation to portable, reusable packages of instructions, scripts, and resources specifically optimized for autonomous systems1. For organizations possessing an extensive body of sample code but struggling with lackluster agent performance, the transformation involves more than simple prompt engineering; it requires the systematic encapsulation of domain expertise into the AgentSkills.io open standard, utilizing progressive disclosure to manage the inherent constraints of the LLM context window3.
+
+## **The Taxonomy of Agentic Skills versus Traditional SDKs**
+
+The primary limitation of traditional SDK-based approaches for agents is threefold: token inefficiency, exponential growth in decision complexity, and a lack of modular reusability3. When a developer provides an agent with thousands of lines of raw API samples, the agent often suffers from "context dilution," where the core task instructions compete for attention with low-level implementation details4. Agentic skills resolve this by functioning as "onboarding guides" or "static cheat-sheets" that transform a general-purpose model into a specialized agent equipped with procedural knowledge and explicit applicability conditions7.
+
+The formal definition of a skill extends beyond the simple prompt. It is a four-tuple formalization consisting of a textual descriptor (![][image1]), an intra-skill policy (![][image2]), a resource set (![][image3]), and a set of applicability conditions (![][image4])7. This formalization allows the skill to persist across sessions and carry executable policies that are significantly more robust than one-time, session-scoped plans10.
+
+| Concept | Unit of Reuse | Internal Logic | Context Impact |
+| :---- | :---- | :---- | :---- |
+| **SDK Tool** | Atomic Function | Fixed Code | High (if all loaded) |
+| **Prompt Template** | Static Text | No decision logic | High (monolithic) |
+| **Agent Skill** | Procedural Module | ReAct/Heuristics | Low (progressive) |
+| **Workflow** | Deterministic Graph | Rigid Sequence | Moderate |
+
+1
+
+## **The Three-Tier Progressive Disclosure Architecture**
+
+The defining characteristic of an "advanced" skill is its adherence to the principle of progressive disclosure. This architectural pattern originates from user interface design but, when applied to agents, ensures that the system loads only the necessary information at each phase of the task lifecycle5. Advanced SDKs, such as the Microsoft Agent Framework and Google ADK, implement this through three distinct levels of information loading1.
+
+### **Level 1: Metadata and Discovery (The L1 Layer)**
+
+At the start of any agentic session, only the metadata—specifically the name and description—is injected into the system prompt. This "menu" allows the agent to identify available capabilities without incurring a massive token penalty5. A skill with 100 installed capabilities might only consume 1,000 baseline tokens if structured correctly, representing a 90% reduction in context overhead compared to monolithic prompts12.
+
+### **Level 2: Instructions and Workflow (The L2 Layer)**
+
+The full SKILL.md body is loaded only when the agent explicitly activates a skill based on user intent. This file acts as the "brain," containing the high-level procedural instructions that guide the agent through multi-step tasks like deployment, code review, or architecture planning5. The specification recommends keeping this layer under 500 lines and 5,000 tokens to ensure the agent remains focused on the primary workflow4.
+
+### **Level 3: Resources and Deterministic Scripts (The L3 Layer)**
+
+Detailed reference materials, such as API specifications, schemas, and templates, are stored in a references/ or assets/ directory. These are loaded on demand only when the L2 instructions require them1. Furthermore, executable scripts (Python, Bash, or PowerShell) reside in a scripts/ directory to handle fragile, repetitive, or algorithmic tasks that the LLM would otherwise struggle to perform reliably15.
+
+## **Engineering Semantic Assets from Existing Code Samples**
+
+A common pitfall in skill development is providing the agent with raw code samples and expecting it to infer the underlying patterns. To create a "productive" skill, these samples must be transformed into structured assets and parameterized templates20.
+
+### **Few-Shot Template Parameterization**
+
+Few-shot prompting is a well-established best practice that significantly improves output consistency6. To utilize an existing body of sample code effectively, developers should extract 3 to 5 high-quality examples that cover different slices of the API usage space24. These should not be raw files but rather "semantic templates" where variable sections are marked for the agent to populate25.
+
+Mathematically, the probability of a correct agent response ![][image5] is positively correlated with the relevance and quality of the provided few-shot examples ![][image6]:
+
+![][image7]
+where ![][image8] is the user query and ![][image9] is the weight of the example based on its structural similarity to the task7.
+
+### **Asset vs. Reference Directory Organization**
+
+The distinction between assets/ and references/ is critical for accurate execution. Files that the agent is meant to read and build upon (e.g., boilerplate code, schemas) should be placed in references/17. Conversely, static files used "as-is" in the final output (e.g., images, company logos, fonts) belong in the assets/ directory17.
+
+| Directory | Purpose | Loaded into Context? | Interaction Type |
+| :---- | :---- | :---- | :---- |
+| references/ | API specs, Schemas | Yes, on-demand | Read and Interpret |
+| assets/ | Templates, Logos | No | Direct Output Injection |
+| scripts/ | Automation, CLI tools | No (Output only) | Execution |
+| examples/ | Test data, Use cases | Yes, if requested | Pattern Matching |
+
+15
+
+## **Intent Mapping and Description Optimization**
+
+The description field in the YAML frontmatter of the SKILL.md file carries the entire burden of triggering the skill during implicit invocation29. If the description fails to accurately map user intent to the skill's capabilities, the agent will never reach for the instructions, regardless of their quality29.
+
+### **The Imperative Phrase Principle**
+
+Advanced skill descriptions should use imperative phrasing—"Use this skill when..."—rather than descriptive phrasing—"This skill does..."29. This framing treats the description as an instruction for the agent to act29. Developers should also include "negative triggers" to prevent false positives, clearly stating scenarios where the skill should *not* be used15.
+
+### **The Trigger Evaluation Loop**
+
+To improve accuracy, developers should implement an optimization loop for their descriptions. This involves splitting a set of user queries into a "train set" (60%) and a "validation set" (40%)29. The train set queries guide the refinement of the description, while the validation set determines if the changes generalize across diverse phrasing and casual language29.
+
+| Strategy | Description Attribute | Impact on Accuracy |
+| :---- | :---- | :---- |
+| **Intent-Based** | Focuses on user's end goal | High |
+| **Domain-Specific** | Mentions specific file types or APIs | Moderate |
+| **Exclusionary** | Lists "do not use" cases | High (reduces false triggers) |
+| **Technical** | Describes internal mechanics | Low |
+
+15
+
+## **Procedural Logic and Deterministic Execution Patterns**
+
+A "lackluster" skill often provides vague guidance that leads to hallucinations. A productive, advanced skill employs strict procedural patterns such as ReAct (Reasoning \+ Acting) and Plan-Validate-Execute4.
+
+### **The ReAct Pattern in Skills**
+
+The ReAct pattern addresses the issue of models hallucinating tool actions. By forcing the agent to interleave thoughts (analyzing the state), actions (calling a tool or script), and observations (evaluating the result), the skill ensures the agent remains grounded in real-world data8.
+
+### **The Plan-Validate-Execute Cycle**
+
+For complex or destructive operations—such as database migrations or large-scale code refactors—the SKILL.md should enforce a "Plan-Validate-Execute" pattern4. The agent first creates an implementation plan (often as a JSON or Markdown artifact), validates it against a "source of truth" using a bundled validation script, and only executes the changes after the plan is verified4. This pattern is machine-verifiable and allows for reversible planning, where the agent can iterate on the plan without modifying the original files16.
+
+## **Advanced State Management and Persistence**
+
+Maintaining the state of an agentic workflow across multiple turns is a primary differentiator for advanced systems. Standard LLM calls are stateless, but advanced agent frameworks introduce "shared memory" through state schemas and checkpointers34.
+
+### **Stateful Orchestration with LangGraph**
+
+LangGraph represents a shift toward stateful, event-driven orchestration by modeling workflows as a StateGraph34. In this architecture, the state is a first-class citizen, typically defined as a Pydantic model that accumulates conversation history and task-specific memory34. Reducer functions are used to manage how nodes update the state, enabling "append-only" logs that prevent overwriting critical information34.
+
+### **Checkpointing and "Time Travel"**
+
+Production-grade skills implement state persistence using backends like SQLite or Postgres37. This allows for:
+
+* **Thread Recovery**: Resuming a session after an interruption33.
+* **Time Travel**: Reverting to a previous agent state to debug or retry from a different branch31.
+* **Approval Gates**: Pausing execution until a human provides validation, particularly for high-stakes financial or legal tasks32.
+
+## **Evaluation and Lifecycle Management of Agentic Skills**
+
+A skill's productivity is measured by its success rate, latency, and reliability. Tools like SkillGrade provide a framework for automated evaluation, allowing developers to catch regressions before deployment15.
+
+### **The SkillGrade Rubric**
+
+SkillGrade automates the testing of agent discovery and execution. It generates tasks based on the skill's eval.yaml and uses "grader" models to assess performance based on a rubric15. Effective grading should focus on outcomes (e.g., "Is the security vulnerability patched?") rather than procedural steps40.
+
+| Metric | Measurement Method | Target Baseline |
+| :---- | :---- | :---- |
+| **Discovery Rate** | Trigger success on validation set | \> 0.85 |
+| **Success Rate** | Task completion (Outcome-based) | \> 0.80 |
+| **Token Efficiency** | Token usage per successful task | Lower is better |
+| **Latency** | End-to-end processing time | Dependent on model |
+
+30
+
+### **Logic Validation and "Ruthless" QA**
+
+Before releasing a skill, developers should subject it to a "ruthless" QA audit. This involves feeding the full SKILL.md to an LLM and asking it to hunt for vulnerabilities, unsupported configurations, or implicit assumptions about the environment15. This "Logic Validation" phase uncovers "Execution Blockers" where the agent might be forced to guess due to ambiguous instructions15.
+
+## **Security and Governance in Skill Deployment**
+
+As agent skills become more autonomous, security teams must implement strict dependency governance and sandboxing1. Advanced skills should follow the principle of "least privilege," requesting only the minimum toolset required for their specific domain43.
+
+### **Sandboxing and Input Filtering**
+
+Executable scripts bundled with skills must run in isolated environments (e.g., containers, seccomp, or firejail) to prevent malicious filesystem or network access1. Furthermore, "Output Filtering" patterns should be used to scrub Personally Identifiable Information (PII) or harmful content before it is shared with the user39.
+
+### **The Role of Meta-Skills and Human Oversight**
+
+While the generation of skills can be automated using "Meta-Skills" (skills designed to create other skills), human oversight remains paramount1. Organizations are encouraged to treat SKILL.md files like code dependencies, requiring peer reviews and version control for every change12.
+
+## **Synthesis: The Roadmap to a Productive Agent Skill**
+
+The transition from a "lackluster" attempt to a "productive" skill is a multi-stage process of refinement. It begins with the extraction of real expertise from hands-on tasks and existing project artifacts4. By organizing this expertise into the three-level progressive disclosure architecture, developers ensure that deep domain knowledge is available on-demand without degrading the agent's performance5.
+
+The integration of semantic assets—such as parameterized templates derived from the user's extensive code samples—provides the agent with the "analogical reasoning" capabilities needed to excel at complex SDK implementations21. When combined with deterministic scripts for algorithmic verification and robust state management for multi-turn persistence, the resulting agent skill becomes a reliable, enterprise-ready capability that moves beyond basic assistance toward true autonomous agency8. The ultimate utility of these systems is found not just in the intelligence of the underlying model, but in the robustness of the structural and procedural guardrails provided by the well-designed skill package18.
+
+#### **Works cited**
+
+1. Agent Skills | Microsoft Learn, [https://learn.microsoft.com/en-us/agent-framework/agents/skills](https://learn.microsoft.com/en-us/agent-framework/agents/skills)
+2. Give Your Agents Domain Expertise with Agent Skills in Microsoft Agent Framework, [https://devblogs.microsoft.com/agent-framework/give-your-agents-domain-expertise-with-agent-skills-in-microsoft-agent-framework/](https://devblogs.microsoft.com/agent-framework/give-your-agents-domain-expertise-with-agent-skills-in-microsoft-agent-framework/)
+3. aws-samples/sample-strands-agents-agentskills: Agent Skills implementation for Strands Agents SDK \- GitHub, [https://github.com/aws-samples/sample-strands-agents-agentskills](https://github.com/aws-samples/sample-strands-agents-agentskills)
+4. [https://agentskills.io/skill-creation/best-practices](https://agentskills.io/skill-creation/best-practices)
+5. The SKILL.md Pattern: How to Write AI Agent Skills That Actually Work | by Bibek Poudel, [https://bibek-poudel.medium.com/the-skill-md-pattern-how-to-write-ai-agent-skills-that-actually-work-72a3169dd7ee](https://bibek-poudel.medium.com/the-skill-md-pattern-how-to-write-ai-agent-skills-that-actually-work-72a3169dd7ee)
+6. Effective context engineering for AI agents \- Anthropic, [https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents](https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents)
+7. Agent Skill Framework: Perspectives on the Potential of Small Language Models in Industrial Environments \- arXiv, [https://arxiv.org/html/2602.16653v1](https://arxiv.org/html/2602.16653v1)
+8. Agent Skills: The Missing Layer That Makes AI Agents Enterprise Ready \- DEV Community, [https://dev.to/sreeni5018/agent-skills-the-missing-layer-that-makes-ai-agents-enterprise-ready-3gc](https://dev.to/sreeni5018/agent-skills-the-missing-layer-that-makes-ai-agents-enterprise-ready-3gc)
+9. deepagentsjs/examples/skills/skill-creator/SKILL.md at main \- GitHub, [https://github.com/langchain-ai/deepagentsjs/blob/main/examples/skills/skill-creator/SKILL.md](https://github.com/langchain-ai/deepagentsjs/blob/main/examples/skills/skill-creator/SKILL.md)
+10. SoK: Agentic Skills — Beyond Tool Use in LLM Agents \- arXiv, [https://arxiv.org/html/2602.20867v1](https://arxiv.org/html/2602.20867v1)
+11. Choose a design pattern for your agentic AI system | Cloud Architecture Center, [https://docs.cloud.google.com/architecture/choose-design-pattern-agentic-ai-system](https://docs.cloud.google.com/architecture/choose-design-pattern-agentic-ai-system)
+12. Developer's Guide to Building ADK Agents with Skills, [https://developers.googleblog.com/en/developers-guide-to-building-adk-agents-with-skills/](https://developers.googleblog.com/en/developers-guide-to-building-adk-agents-with-skills/)
+13. Progressive Disclosure: the technique that helps control context (and tokens) in AI agents, [https://medium.com/@martia\_es/progressive-disclosure-the-technique-that-helps-control-context-and-tokens-in-ai-agents-8d6108b09289](https://medium.com/@martia_es/progressive-disclosure-the-technique-that-helps-control-context-and-tokens-in-ai-agents-8d6108b09289)
+14. Introducing Agent Plugins for AWS | AWS Developer Tools Blog, [https://aws.amazon.com/blogs/developer/introducing-agent-plugins-for-aws/](https://aws.amazon.com/blogs/developer/introducing-agent-plugins-for-aws/)
+15. GitHub \- mgechev/skills-best-practices: Write professional-grade skills for agents, validate them using LLMs, and maintain a lean context window., [https://github.com/mgechev/skills-best-practices](https://github.com/mgechev/skills-best-practices)
+16. Skill authoring best practices \- Claude API Docs, [https://platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices](https://platform.claude.com/docs/en/agents-and-tools/agent-skills/best-practices)
+17. skill-creator \- anthropics/financial-services-plugins \- GitHub, [https://github.com/anthropics/financial-services-plugins/blob/main/financial-analysis/skills/skill-creator/SKILL.md](https://github.com/anthropics/financial-services-plugins/blob/main/financial-analysis/skills/skill-creator/SKILL.md)
+18. GitHub \- gohypergiant/agent-skills: A collection of skills for AI coding agents. Skills are packaged instructions and scripts that extend agent capabilities., [https://github.com/gohypergiant/agent-skills](https://github.com/gohypergiant/agent-skills)
+19. agent-skills.instructions.md \- github/awesome-copilot, [https://github.com/github/awesome-copilot/blob/main/instructions/agent-skills.instructions.md](https://github.com/github/awesome-copilot/blob/main/instructions/agent-skills.instructions.md)
+20. Include few-shot examples | Generative AI on Vertex AI \- Google Cloud Documentation, [https://docs.cloud.google.com/vertex-ai/generative-ai/docs/learn/prompts/few-shot-examples](https://docs.cloud.google.com/vertex-ai/generative-ai/docs/learn/prompts/few-shot-examples)
+21. Achieving 5x Agentic Coding Performance with Few-Shot Prompting, [https://towardsdatascience.com/5x-agentic-coding-performance-with-few-shot-prompting/](https://towardsdatascience.com/5x-agentic-coding-performance-with-few-shot-prompting/)
+22. Daily Papers \- Hugging Face, [https://huggingface.co/papers?q=experience%20memory](https://huggingface.co/papers?q=experience+memory)
+23. Few-Shot Learning for LLMs: Examples and Implementation Guide \- Tetrate, [https://tetrate.io/learn/ai/few-shot-learning-llms](https://tetrate.io/learn/ai/few-shot-learning-llms)
+24. Few-Shot Prompting for Agentic Systems: Teaching by Example \- Comet, [https://www.comet.com/site/blog/few-shot-prompting/](https://www.comet.com/site/blog/few-shot-prompting/)
+25. Few-Shot Prompting \- Portkey Docs, [https://docs.portkey.ai/docs/guides/use-cases/few-shot-prompting](https://docs.portkey.ai/docs/guides/use-cases/few-shot-prompting)
+26. LangChain Prompt Templates: Complete Guide with Examples \- Latenode Blog, [https://latenode.com/blog/ai-frameworks-technical-infrastructure/langchain-setup-tools-agents-memory/langchain-prompt-templates-complete-guide-with-examples](https://latenode.com/blog/ai-frameworks-technical-infrastructure/langchain-setup-tools-agents-memory/langchain-prompt-templates-complete-guide-with-examples)
+27. FloorplanQA: A Benchmark for Spatial Reasoning in LLMs using Structured Representations, [https://arxiv.org/html/2507.07644v2](https://arxiv.org/html/2507.07644v2)
+28. Provide examples (few-shot prompting) \- Amazon Nova \- AWS Documentation, [https://docs.aws.amazon.com/nova/latest/userguide/prompting-examples.html](https://docs.aws.amazon.com/nova/latest/userguide/prompting-examples.html)
+29. Optimizing skill descriptions \- Agent Skills, [https://agentskills.io/skill-creation/optimizing-descriptions](https://agentskills.io/skill-creation/optimizing-descriptions)
+30. Building AI with Chatbot Intent Recognition: Guide \- IrisAgent, [https://irisagent.com/blog/building-chatbots-with-intent-detection-guide/](https://irisagent.com/blog/building-chatbots-with-intent-detection-guide/)
+31. Design Patterns for Agentic AI and Multi-Agent Systems \- AppsTek Corp, [https://appstekcorp.com/blog/design-patterns-for-agentic-ai-and-multi-agent-systems/](https://appstekcorp.com/blog/design-patterns-for-agentic-ai-and-multi-agent-systems/)
+32. 7 Must-Know Agentic AI Design Patterns \- MachineLearningMastery.com, [https://machinelearningmastery.com/7-must-know-agentic-ai-design-patterns/](https://machinelearningmastery.com/7-must-know-agentic-ai-design-patterns/)
+33. Single-responsibility agents and multi-agent workflows in AI-powered development tools, [https://www.epam.com/insights/ai/blogs/single-responsibility-agents-and-multi-agent-workflows](https://www.epam.com/insights/ai/blogs/single-responsibility-agents-and-multi-agent-workflows)
+34. The Architecture of Agents: Planning, Action, and State Management in Large Language Models | by Tejaswi kashyap \- GoPenAI, [https://blog.gopenai.com/the-architecture-of-agents-planning-action-and-state-management-in-large-language-models-e00b340fcf09](https://blog.gopenai.com/the-architecture-of-agents-planning-action-and-state-management-in-large-language-models-e00b340fcf09)
+35. LangGraph State Management Patterns | Claude Code Skill \- MCP Market, [https://mcpmarket.com/tools/skills/langgraph-state-management](https://mcpmarket.com/tools/skills/langgraph-state-management)
+36. The Best Open Source Frameworks For Building AI Agents in 2026 \- Firecrawl, [https://www.firecrawl.dev/blog/best-open-source-agent-frameworks](https://www.firecrawl.dev/blog/best-open-source-agent-frameworks)
+37. LangGraph Checkpoint Patterns \- Claude Code Skill \- MCP Market, [https://mcpmarket.com/tools/skills/langgraph-state-persistence](https://mcpmarket.com/tools/skills/langgraph-state-persistence)
+38. Claude Code Guide: Features and Best Practices, [https://www.elliotjreed.com/ai/claude-code-guide-and-tips](https://www.elliotjreed.com/ai/claude-code-guide-and-tips)
+39. claude-skills/engineering/agent-designer/SKILL.md at main \- GitHub, [https://github.com/alirezarezvani/claude-skills/blob/main/engineering/agent-designer/SKILL.md](https://github.com/alirezarezvani/claude-skills/blob/main/engineering/agent-designer/SKILL.md)
+40. GitHub \- mgechev/skillgrade: "Unit tests" for your agent skills, [https://github.com/mgechev/skillgrade](https://github.com/mgechev/skillgrade)
+41. Skill Design for LLM Agents by Minko Gechev \- GitNation, [https://gitnation.com/contents/skill-design-for-llm-agents](https://gitnation.com/contents/skill-design-for-llm-agents)
+42. What is agentic coding? How it works and use cases | Google Cloud, [https://cloud.google.com/discover/what-is-agentic-coding](https://cloud.google.com/discover/what-is-agentic-coding)
+43. Top 10 Claude Code Skills Every Builder Should Know in 2026 \- Composio, [https://composio.dev/content/top-claude-skills](https://composio.dev/content/top-claude-skills)
+44. CodeBuddy Code Best Practices, [https://www.codebuddy.ai/docs/cli/best-practices](https://www.codebuddy.ai/docs/cli/best-practices)
+45. 3 Principles for Designing Agent Skills \- Block Engineering Blog, [https://engineering.block.xyz/blog/3-principles-for-designing-agent-skills](https://engineering.block.xyz/blog/3-principles-for-designing-agent-skills)
+46. The Few Shot Prompting Guide \- PromptHub, [https://www.prompthub.us/blog/the-few-shot-prompting-guide](https://www.prompthub.us/blog/the-few-shot-prompting-guide)
+
+[image1]:
+
+[image2]:
+
+[image3]:
+
+[image4]:
+
+[image5]:
+
+[image6]:
+
+[image7]:
+
+[image8]:
+
+[image9]:
\ No newline at end of file
From 075872078ca50bd2b0105d43e978d3e0223ecd80 Mon Sep 17 00:00:00 2001
From: dkhawk <107309+dkhawk@users.noreply.github.com>
Date: Fri, 10 Apr 2026 14:58:55 -0600
Subject: [PATCH 02/11] feat(skill): Restructure skill into assets and
references
---
.gemini/skills/android-maps3d-sdk/SKILL.md | 41 ++--
.../samples/views_kotlin/MapActivity.kt.txt | 85 ++++++++
.../samples/views_kotlin/activity_main.xml | 11 +
.../views_kotlin/snippets/TrackedMap3D.kt.txt | 41 ++++
.../snippets/object_manager_usage.kt.txt | 18 ++
.../references/best_practices.md | 30 +++
.../references/kotlin_views_template.md | 205 ------------------
7 files changed, 209 insertions(+), 222 deletions(-)
create mode 100644 .gemini/skills/android-maps3d-sdk/assets/samples/views_kotlin/MapActivity.kt.txt
create mode 100644 .gemini/skills/android-maps3d-sdk/assets/samples/views_kotlin/activity_main.xml
create mode 100644 .gemini/skills/android-maps3d-sdk/assets/samples/views_kotlin/snippets/TrackedMap3D.kt.txt
create mode 100644 .gemini/skills/android-maps3d-sdk/assets/samples/views_kotlin/snippets/object_manager_usage.kt.txt
create mode 100644 .gemini/skills/android-maps3d-sdk/references/best_practices.md
delete mode 100644 .gemini/skills/android-maps3d-sdk/references/kotlin_views_template.md
diff --git a/.gemini/skills/android-maps3d-sdk/SKILL.md b/.gemini/skills/android-maps3d-sdk/SKILL.md
index d2446d1..b8ebc53 100644
--- a/.gemini/skills/android-maps3d-sdk/SKILL.md
+++ b/.gemini/skills/android-maps3d-sdk/SKILL.md
@@ -16,15 +16,20 @@ This skill guides you through integrating the Google Maps 3D SDK into an Android
## Procedural Workflow
-### Step 1: Determine the Environment
-You MUST ask the user which development environment they are targeting before providing code:
-1. **Kotlin (Views)** (Prioritized)
-2. **Kotlin + Compose**
-3. **Java**
+### Step 1: Determine the Environment and Features
+You MUST ask the user to clarify their stack and needs:
+1. **Language**: Kotlin or Java?
+2. **UI Framework**: Jetpack Compose or standard XML Views?
+3. **Features**: Do they need automatic object management (cleanup)?
-Additionally, ask if they want to use the **Object Tracking Delegate** pattern (useful for clean cleanup in complex apps).
+Based on their response, follow the **Selection Logic** to retrieve boilerplate from `assets/samples/` and consult rules in `references/`.
-Based on their response, you will load the appropriate template from the `references/` directory.
+### Implementation Guidance (Selection Logic)
+1. Identify the user's stack: (Language: Kotlin/Java, UI: Compose/Views).
+2. Check if Lifecycle Management is required (always for 3D maps).
+3. **Retrieve** the corresponding boilerplate from `assets/samples/` (e.g., `assets/samples/views_kotlin/MapActivity.kt.txt`).
+4. If object tracking is needed, **retrieve** the snippet from `assets/samples/views_kotlin/snippets/`.
+5. **Reference** the memory management best practices in `references/best_practices.md` to ensure the generated code follows SDK safety guidelines.
### Step 2: Base Setup
Regardless of the environment, the following setup is required.
@@ -86,15 +91,17 @@ In `AndroidManifest.xml`, add the required permissions and reference the injecte
```
### Step 3: Load Environment Template
-After the user selects the environment, load the corresponding file from:
-- Kotlin (Views): `references/kotlin_views_template.md`
-- Kotlin + Compose: (To be created)
-- Java: (To be created)
+After determining the stack, load the corresponding files from `assets/samples/`:
+- Kotlin (Views):
+ - Layout: `assets/samples/views_kotlin/activity_main.xml`
+ - Activity: `assets/samples/views_kotlin/MapActivity.kt.txt`
+ - Snippet (Object Manager): `assets/samples/views_kotlin/snippets/object_manager_usage.kt.txt`
+- Kotlin + Compose: (To be created in `assets/samples/compose/`)
+- Java: (To be created in `assets/samples/views_java/`)
### Step 4: Apply Best Practices
-Follow these strict procedural rules to avoid common pitfalls:
-
-1. **Initialization Delay**: Fails on cold starts due to viewport layout races. Always use a 500ms delay before initializing map elements (camera updates, adding objects).
-2. **Double-Wait Pattern**: For animations, wait for camera animation end AND map steady state.
-3. **Object Updates**: Use matching IDs to update Polygons/Polylines instead of removing and re-adding (prevents flickering).
-4. **Null Safety**: Camera properties like heading and tilt can be null. Handle them defensively.
+Consult `references/best_practices.md` for detailed explanation of rules. Key rules to enforce:
+1. **Initialization Delay**: Always use a 500ms delay before initializing map elements.
+2. **Object Management**: Use the `TrackedMap3D` delegate to clean up objects on destroy to avoid cruft.
+3. **Double-Wait Pattern**: For animations, wait for camera animation end AND map steady state.
+4. **Object Updates**: Use matching IDs to update Polygons/Polylines instead of removing and re-adding.
diff --git a/.gemini/skills/android-maps3d-sdk/assets/samples/views_kotlin/MapActivity.kt.txt b/.gemini/skills/android-maps3d-sdk/assets/samples/views_kotlin/MapActivity.kt.txt
new file mode 100644
index 0000000..f8c2b36
--- /dev/null
+++ b/.gemini/skills/android-maps3d-sdk/assets/samples/views_kotlin/MapActivity.kt.txt
@@ -0,0 +1,85 @@
+import android.os.Bundle
+import android.util.Log
+import androidx.appcompat.app.AppCompatActivity
+import androidx.lifecycle.DefaultLifecycleObserver
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.lifecycleScope
+import com.google.android.gms.maps3d.GoogleMap3D
+import com.google.android.gms.maps3d.Map3DView
+import com.google.android.gms.maps3d.OnMap3DViewReadyCallback
+import com.google.android.gms.maps3d.model.camera
+import com.google.android.gms.maps3d.model.latLngAltitude
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+
+class MainActivity : AppCompatActivity() {
+
+ private lateinit var mapView: Map3DView
+ private var googleMap: GoogleMap3D? = null
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_main)
+
+ mapView = findViewById(R.id.map_view)
+ mapView.onCreate(savedInstanceState)
+
+ // Use DefaultLifecycleObserver to handle most lifecycle events automatically
+ lifecycle.addObserver(object : DefaultLifecycleObserver {
+ override fun onStart(owner: LifecycleOwner) { mapView.onStart() }
+ override fun onResume(owner: LifecycleOwner) { mapView.onResume() }
+ override fun onPause(owner: LifecycleOwner) { mapView.onPause() }
+ override fun onStop(owner: LifecycleOwner) { mapView.onStop() }
+ override fun onDestroy(owner: LifecycleOwner) { mapView.onDestroy() }
+ })
+
+ mapView.getMap3DViewAsync(object : OnMap3DViewReadyCallback {
+ override fun onMap3DViewReady(map: GoogleMap3D) {
+ googleMap = map
+
+ // Fails on cold starts because the viewport layout and binding matrix are not yet stable.
+ // Use a timer-based delay workaround.
+ lifecycleScope.launch {
+ // Wait for the viewport to fully inflate and bindings to stabilize.
+ delay(500)
+ setupMapElements()
+ }
+ }
+
+ override fun onError(e: Exception) {
+ Log.e("MainActivity", "Error loading map", e)
+ }
+ })
+ }
+
+ private fun setupMapElements() {
+ val map = googleMap ?: return
+
+ // Example: Set initial camera position
+ val initialCamera = camera {
+ center = latLngAltitude {
+ latitude = 40.0150
+ longitude = -105.2705
+ altitude = 5000.0
+ }
+ heading = 0.0
+ tilt = 45.0
+ roll = 0.0
+ range = 10000.0
+ }
+ map.setCamera(initialCamera)
+
+ // Add more initialization logic here (markers, polylines, etc.)
+ }
+
+ // onLowMemory and onSaveInstanceState still need to be forwarded manually
+ override fun onLowMemory() {
+ super.onLowMemory()
+ mapView.onLowMemory()
+ }
+
+ override fun onSaveInstanceState(outState: Bundle) {
+ super.onSaveInstanceState(outState)
+ mapView.onSaveInstanceState(outState)
+ }
+}
diff --git a/.gemini/skills/android-maps3d-sdk/assets/samples/views_kotlin/activity_main.xml b/.gemini/skills/android-maps3d-sdk/assets/samples/views_kotlin/activity_main.xml
new file mode 100644
index 0000000..2fa964b
--- /dev/null
+++ b/.gemini/skills/android-maps3d-sdk/assets/samples/views_kotlin/activity_main.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
diff --git a/.gemini/skills/android-maps3d-sdk/assets/samples/views_kotlin/snippets/TrackedMap3D.kt.txt b/.gemini/skills/android-maps3d-sdk/assets/samples/views_kotlin/snippets/TrackedMap3D.kt.txt
new file mode 100644
index 0000000..570e3e9
--- /dev/null
+++ b/.gemini/skills/android-maps3d-sdk/assets/samples/views_kotlin/snippets/TrackedMap3D.kt.txt
@@ -0,0 +1,41 @@
+import com.google.android.gms.maps3d.GoogleMap3D
+import com.google.android.gms.maps3d.model.Marker
+import com.google.android.gms.maps3d.model.MarkerOptions
+import com.google.android.gms.maps3d.model.Polygon
+import com.google.android.gms.maps3d.model.PolygonOptions
+import com.google.android.gms.maps3d.model.Polyline
+import com.google.android.gms.maps3d.model.PolylineOptions
+
+class TrackedMap3D(
+ val delegate: GoogleMap3D,
+ private val items: MutableList = mutableListOf()
+) {
+ fun addMarker(options: MarkerOptions): Marker? {
+ val marker = delegate.addMarker(options)
+ if (marker != null) items.add(marker)
+ return marker
+ }
+
+ fun addPolyline(options: PolylineOptions): Polyline? {
+ val polyline = delegate.addPolyline(options)
+ if (polyline != null) items.add(polyline)
+ return polyline
+ }
+
+ fun addPolygon(options: PolygonOptions): Polygon? {
+ val polygon = delegate.addPolygon(options)
+ if (polygon != null) items.add(polygon)
+ return polygon
+ }
+
+ fun clearAll() {
+ items.forEach { item ->
+ when (item) {
+ is Marker -> item.remove()
+ is Polyline -> item.remove()
+ is Polygon -> item.remove()
+ }
+ }
+ items.clear()
+ }
+}
diff --git a/.gemini/skills/android-maps3d-sdk/assets/samples/views_kotlin/snippets/object_manager_usage.kt.txt b/.gemini/skills/android-maps3d-sdk/assets/samples/views_kotlin/snippets/object_manager_usage.kt.txt
new file mode 100644
index 0000000..1be702c
--- /dev/null
+++ b/.gemini/skills/android-maps3d-sdk/assets/samples/views_kotlin/snippets/object_manager_usage.kt.txt
@@ -0,0 +1,18 @@
+// 1. Declare the tracked map instance
+private var trackedMap: TrackedMap3D? = null
+
+// 2. In onCreate, update the lifecycle observer to clean up on destroy
+lifecycle.addObserver(object : DefaultLifecycleObserver {
+ // ... other methods
+ override fun onDestroy(owner: LifecycleOwner) {
+ // Automatically clean up objects to avoid cruft
+ trackedMap?.clearAll()
+ mapView.onDestroy()
+ }
+})
+
+// 3. In onMap3DViewReady, wrap the map
+override fun onMap3DViewReady(map: GoogleMap3D) {
+ trackedMap = TrackedMap3D(map)
+ // ...
+}
diff --git a/.gemini/skills/android-maps3d-sdk/references/best_practices.md b/.gemini/skills/android-maps3d-sdk/references/best_practices.md
new file mode 100644
index 0000000..463e17d
--- /dev/null
+++ b/.gemini/skills/android-maps3d-sdk/references/best_practices.md
@@ -0,0 +1,30 @@
+# Best Practices for Android Maps 3D SDK
+
+This document explains the architectural decisions and constraints when working with the Google Maps 3D SDK for Android.
+
+## 1. Lifecycle Management
+
+The `Map3DView` is a heavy component that requires explicit lifecycle management. Failing to forward lifecycle events can lead to memory leaks, crashes, or black screens.
+
+### The Observer Pattern
+Instead of overriding lifecycle methods in the Activity (like `onStart`, `onResume`, etc.), it is best practice to use a `DefaultLifecycleObserver`. This keeps the Activity code clean and ensures that lifecycle events are automatically forwarded.
+
+*Constraint*: `onCreate`, `onLowMemory`, and `onSaveInstanceState` still need manual handling in the Activity as they are not fully covered by the standard lifecycle observer or require specific arguments (like `Bundle`).
+
+## 2. Initialization Delay (Cold Starts)
+
+When the 3D map is first loaded, the viewport layout and binding matrix may not be fully stable immediately after `onMap3DViewReady` is called.
+
+### The Delay Pattern
+If you attempt to add objects or set the camera immediately in `onMap3DViewReady`, it might fail or render incorrectly on cold starts.
+*Rule*: Always introduce a small delay (e.g., 500ms using coroutines `delay(500)`) before initializing map elements to ensure the renderer is fully ready.
+
+## 3. Object Management and Cleanup
+
+The underlying `GoogleMap3D` engine instance is effectively created once per application lifecycle (singleton-like behavior). It persists even across Activity recreation.
+
+### The Cruft Pitfall
+If you add markers, polylines, or polygons to the map and do not remove them when the Activity is destroyed, they will remain on the map. When a new Activity instance is created, the user will see the old objects ("cruft").
+
+### The Delegate Solution
+Use a wrapper like `TrackedMap3D` to keep track of all objects added during a session. Hook into the `onDestroy` lifecycle event to call `clearAll()` on this delegate, ensuring a clean state for the next usage.
diff --git a/.gemini/skills/android-maps3d-sdk/references/kotlin_views_template.md b/.gemini/skills/android-maps3d-sdk/references/kotlin_views_template.md
deleted file mode 100644
index 00b588e..0000000
--- a/.gemini/skills/android-maps3d-sdk/references/kotlin_views_template.md
+++ /dev/null
@@ -1,205 +0,0 @@
-# Kotlin (Views) Template for Android Maps 3D SDK
-
-Use this template when building an Android application using Kotlin and standard XML Views (not Compose).
-
-## 1. Layout XML
-
-Create a layout file (e.g., `activity_main.xml`) and add the `Map3DView`.
-
-```xml
-
-
-
-
-
-
-```
-
-## 2. Activity Code
-
-Implement the activity to handle the map lifecycle and initialization.
-
-```kotlin
-import android.os.Bundle
-import android.util.Log
-import androidx.appcompat.app.AppCompatActivity
-import androidx.lifecycle.DefaultLifecycleObserver
-import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.lifecycleScope
-import com.google.android.gms.maps3d.GoogleMap3D
-import com.google.android.gms.maps3d.Map3DView
-import com.google.android.gms.maps3d.OnMap3DViewReadyCallback
-import com.google.android.gms.maps3d.model.camera
-import com.google.android.gms.maps3d.model.latLngAltitude
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.launch
-
-class MainActivity : AppCompatActivity() {
-
- private lateinit var mapView: Map3DView
- private var googleMap: GoogleMap3D? = null
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_main)
-
- mapView = findViewById(R.id.map_view)
- mapView.onCreate(savedInstanceState)
-
- // Use DefaultLifecycleObserver to handle most lifecycle events automatically
- lifecycle.addObserver(object : DefaultLifecycleObserver {
- override fun onStart(owner: LifecycleOwner) { mapView.onStart() }
- override fun onResume(owner: LifecycleOwner) { mapView.onResume() }
- override fun onPause(owner: LifecycleOwner) { mapView.onPause() }
- override fun onStop(owner: LifecycleOwner) { mapView.onStop() }
- override fun onDestroy(owner: LifecycleOwner) { mapView.onDestroy() }
- })
-
- mapView.getMap3DViewAsync(object : OnMap3DViewReadyCallback {
- override fun onMap3DViewReady(map: GoogleMap3D) {
- googleMap = map
-
- // Fails on cold starts because the viewport layout and binding matrix are not yet stable.
- // Use a timer-based delay workaround.
- lifecycleScope.launch {
- // Wait for the viewport to fully inflate and bindings to stabilize.
- delay(500)
- setupMapElements()
- }
- }
-
- override fun onError(e: Exception) {
- Log.e("MainActivity", "Error loading map", e)
- }
- })
- }
-
- private fun setupMapElements() {
- val map = googleMap ?: return
-
- // Example: Set initial camera position
- val initialCamera = camera {
- center = latLngAltitude {
- latitude = 40.0150
- longitude = -105.2705
- altitude = 5000.0
- }
- heading = 0.0
- tilt = 45.0
- roll = 0.0
- range = 10000.0
- }
- map.setCamera(initialCamera)
-
- // Add more initialization logic here (markers, polylines, etc.)
- }
-
- // onLowMemory and onSaveInstanceState still need to be forwarded manually
- override fun onLowMemory() {
- super.onLowMemory()
- mapView.onLowMemory()
- }
-
- override fun onSaveInstanceState(outState: Bundle) {
- super.onSaveInstanceState(outState)
- mapView.onSaveInstanceState(outState)
- }
-}
-```
-
-## 3. Best Practices
-
-- **Lifecycle**: Always forward lifecycle methods to `Map3DView`.
-- **Initialization Delay**: Use the 500ms delay pattern shown above to avoid edge-trigger races on startup.
-- **State Latch**: Implement an `isInitialized` boolean latch in `setupMapElements()` if you plan to call it multiple times or across lifecycle events to avoid stacking duplicate objects.
-
-## 4. Optional: Object Tracking Delegate
-
-For advanced use cases where you need to add and remove many objects (Markers, Polylines, etc.) dynamically, it is helpful to use a delegate wrapper to track these objects for easy cleanup.
-
-Here is a simplified version of the `TrackedMap3D` pattern used in the project samples:
-
-```kotlin
-import com.google.android.gms.maps3d.GoogleMap3D
-import com.google.android.gms.maps3d.model.Marker
-import com.google.android.gms.maps3d.model.MarkerOptions
-import com.google.android.gms.maps3d.model.Polygon
-import com.google.android.gms.maps3d.model.PolygonOptions
-import com.google.android.gms.maps3d.model.Polyline
-import com.google.android.gms.maps3d.model.PolylineOptions
-import com.google.android.gms.maps3d.model.Camera
-
-class TrackedMap3D(
- val delegate: GoogleMap3D,
- private val items: MutableList = mutableListOf()
-) {
- fun addMarker(options: MarkerOptions): Marker? {
- val marker = delegate.addMarker(options)
- if (marker != null) items.add(marker)
- return marker
- }
-
- fun addPolyline(options: PolylineOptions): Polyline? {
- val polyline = delegate.addPolyline(options)
- if (polyline != null) items.add(polyline)
- return polyline
- }
-
- fun addPolygon(options: PolygonOptions): Polygon? {
- val polygon = delegate.addPolygon(options)
- if (polygon != null) items.add(polygon)
- return polygon
- }
-
- /**
- * Clears all tracked objects from the map.
- */
- fun clearAll() {
- items.forEach { item ->
- when (item) {
- is Marker -> item.remove()
- is Polyline -> item.remove()
- is Polygon -> item.remove()
- }
- }
- items.clear()
- }
-
- // Forward other necessary methods to the delegate as needed
- fun setCamera(camera: Camera) = delegate.setCamera(camera)
-}
-```
-
-To use it, wrap the `GoogleMap3D` instance and ensure you clear it in `onDestroy` to avoid retaining objects across Activity instances (since the 3D engine persists across Activity recreation):
-
-```kotlin
-private var trackedMap: TrackedMap3D? = null
-
-override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- // ...
-
- lifecycle.addObserver(object : DefaultLifecycleObserver {
- // ...
- override fun onDestroy(owner: LifecycleOwner) {
- // Automatically clean up objects to avoid cruft
- trackedMap?.clearAll()
- mapView.onDestroy()
- }
- })
-
- mapView.getMap3DViewAsync(object : OnMap3DViewReadyCallback {
- override fun onMap3DViewReady(map: GoogleMap3D) {
- trackedMap = TrackedMap3D(map)
- // ...
- }
- // ...
- })
-}
-```
-
From 3e8567c073ef3f897d0af4bbc8ef6b1bbb9459c7 Mon Sep 17 00:00:00 2001
From: dkhawk <107309+dkhawk@users.noreply.github.com>
Date: Fri, 10 Apr 2026 15:40:29 -0600
Subject: [PATCH 03/11] feat(skill): Add Java samples, utilities, and secrets
enforcement to Maps 3D skill
---
.gemini/skills/android-maps3d-sdk/SKILL.md | 20 +-
.../samples/views_java/MapActivity.java.txt | 86 +++++++
.../samples/views_java/activity_main.xml | 29 +++
.../views_java/snippets/TrackedMap3D.java.txt | 53 ++++
.../snippets/object_manager_usage.java.txt | 23 ++
.../samples/views_kotlin/MapActivity.kt.txt | 5 +-
.../samples/views_kotlin/activity_main.xml | 20 +-
.../references/best_practices.md | 5 +-
.../references/secrets_enforcement.md | 94 +++++++
.../references/units_java.md | 121 +++++++++
.../references/units_kotlin.md | 100 ++++++++
.../references/utilities_java.md | 226 +++++++++++++++++
.../references/utilities_kotlin.md | 238 ++++++++++++++++++
13 files changed, 1013 insertions(+), 7 deletions(-)
create mode 100644 .gemini/skills/android-maps3d-sdk/assets/samples/views_java/MapActivity.java.txt
create mode 100644 .gemini/skills/android-maps3d-sdk/assets/samples/views_java/activity_main.xml
create mode 100644 .gemini/skills/android-maps3d-sdk/assets/samples/views_java/snippets/TrackedMap3D.java.txt
create mode 100644 .gemini/skills/android-maps3d-sdk/assets/samples/views_java/snippets/object_manager_usage.java.txt
create mode 100644 .gemini/skills/android-maps3d-sdk/references/secrets_enforcement.md
create mode 100644 .gemini/skills/android-maps3d-sdk/references/units_java.md
create mode 100644 .gemini/skills/android-maps3d-sdk/references/units_kotlin.md
create mode 100644 .gemini/skills/android-maps3d-sdk/references/utilities_java.md
create mode 100644 .gemini/skills/android-maps3d-sdk/references/utilities_kotlin.md
diff --git a/.gemini/skills/android-maps3d-sdk/SKILL.md b/.gemini/skills/android-maps3d-sdk/SKILL.md
index b8ebc53..76be23b 100644
--- a/.gemini/skills/android-maps3d-sdk/SKILL.md
+++ b/.gemini/skills/android-maps3d-sdk/SKILL.md
@@ -97,11 +97,23 @@ After determining the stack, load the corresponding files from `assets/samples/`
- Activity: `assets/samples/views_kotlin/MapActivity.kt.txt`
- Snippet (Object Manager): `assets/samples/views_kotlin/snippets/object_manager_usage.kt.txt`
- Kotlin + Compose: (To be created in `assets/samples/compose/`)
-- Java: (To be created in `assets/samples/views_java/`)
+- Java:
+ - Layout: `assets/samples/views_java/activity_main.xml`
+ - Activity: `assets/samples/views_java/MapActivity.java.txt`
+ - Snippet (Object Manager): `assets/samples/views_java/snippets/object_manager_usage.java.txt`
+
### Step 4: Apply Best Practices
Consult `references/best_practices.md` for detailed explanation of rules. Key rules to enforce:
-1. **Initialization Delay**: Always use a 500ms delay before initializing map elements.
+1. **Initialization Delay**: Always use a 1-second delay before initializing map elements.
2. **Object Management**: Use the `TrackedMap3D` delegate to clean up objects on destroy to avoid cruft.
-3. **Double-Wait Pattern**: For animations, wait for camera animation end AND map steady state.
-4. **Object Updates**: Use matching IDs to update Polygons/Polylines instead of removing and re-adding.
+3. **Utilities**: Use validation utilities to prevent crashes, and path utilities for smoothing/simplification (see `references/utilities_kotlin.md` or `references/utilities_java.md`).
+
+4. **Double-Wait Pattern**: For animations, wait for camera animation end AND map steady state.
+5. **Object Updates**: Use matching IDs to update Polygons/Polylines instead of removing and re-adding.
+6. **Unit Conversions**: For type-safe measurements and conversions, see `references/units_kotlin.md` or `references/units_java.md` (Optional).
+7. **Secrets Security**: NEVER add `secrets.properties` to version control. NEVER add real API keys to source code or `local.defaults.properties` (see `references/secrets_enforcement.md` for Gradle enforcement snippet).
+
+
+
+
diff --git a/.gemini/skills/android-maps3d-sdk/assets/samples/views_java/MapActivity.java.txt b/.gemini/skills/android-maps3d-sdk/assets/samples/views_java/MapActivity.java.txt
new file mode 100644
index 0000000..db0e930
--- /dev/null
+++ b/.gemini/skills/android-maps3d-sdk/assets/samples/views_java/MapActivity.java.txt
@@ -0,0 +1,86 @@
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.Log;
+import androidx.activity.EdgeToEdge;
+import androidx.annotation.NonNull;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.lifecycle.DefaultLifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+import com.google.android.gms.maps3d.GoogleMap3D;
+import com.google.android.gms.maps3d.Map3DView;
+import com.google.android.gms.maps3d.OnMap3DViewReadyCallback;
+
+
+public class MainActivity extends AppCompatActivity {
+
+ private Map3DView mapView;
+ private GoogleMap3D googleMap;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ EdgeToEdge.enable(this);
+ setContentView(R.layout.activity_main);
+
+ mapView = findViewById(R.id.map_view);
+ mapView.onCreate(savedInstanceState);
+
+ // Use DefaultLifecycleObserver to handle most lifecycle events automatically
+ getLifecycle().addObserver(new DefaultLifecycleObserver() {
+ @Override
+ public void onStart(@NonNull LifecycleOwner owner) { mapView.onStart(); }
+ @Override
+ public void onResume(@NonNull LifecycleOwner owner) { mapView.onResume(); }
+ @Override
+ public void onPause(@NonNull LifecycleOwner owner) { mapView.onPause(); }
+ @Override
+ public void onStop(@NonNull LifecycleOwner owner) { mapView.onStop(); }
+ @Override
+ public void onDestroy(@NonNull LifecycleOwner owner) { mapView.onDestroy(); }
+ });
+
+ mapView.getMap3DViewAsync(new OnMap3DViewReadyCallback() {
+ @Override
+ public void onMap3DViewReady(@NonNull GoogleMap3D map) {
+ googleMap = map;
+
+ // Fails on cold starts because the viewport layout and binding matrix are not yet stable.
+ // The SDK requires a delay to bypass these readiness bugs before adding objects or setting camera.
+ // 1 second is usually sufficient.
+ new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ setupMapElements();
+ }
+ }, 1000);
+
+
+ }
+
+ @Override
+ public void onError(@NonNull Exception e) {
+ Log.e("MainActivity", "Error loading map", e);
+ }
+ });
+ }
+
+ private void setupMapElements() {
+ if (googleMap == null) return;
+
+ Log.d("MainActivity", "Setting up map elements after delay");
+ // Add your map initialization logic here (markers, polylines, etc.)
+ }
+
+ @Override
+ public void onLowMemory() {
+ super.onLowMemory();
+ mapView.onLowMemory();
+ }
+
+ @Override
+ protected void onSaveInstanceState(@NonNull Bundle outState) {
+ super.onSaveInstanceState(outState);
+ mapView.onSaveInstanceState(outState);
+ }
+}
diff --git a/.gemini/skills/android-maps3d-sdk/assets/samples/views_java/activity_main.xml b/.gemini/skills/android-maps3d-sdk/assets/samples/views_java/activity_main.xml
new file mode 100644
index 0000000..53a6692
--- /dev/null
+++ b/.gemini/skills/android-maps3d-sdk/assets/samples/views_java/activity_main.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
diff --git a/.gemini/skills/android-maps3d-sdk/assets/samples/views_java/snippets/TrackedMap3D.java.txt b/.gemini/skills/android-maps3d-sdk/assets/samples/views_java/snippets/TrackedMap3D.java.txt
new file mode 100644
index 0000000..329191e
--- /dev/null
+++ b/.gemini/skills/android-maps3d-sdk/assets/samples/views_java/snippets/TrackedMap3D.java.txt
@@ -0,0 +1,53 @@
+import com.google.android.gms.maps3d.GoogleMap3D;
+import com.google.android.gms.maps3d.model.Marker;
+import com.google.android.gms.maps3d.model.MarkerOptions;
+import com.google.android.gms.maps3d.model.Polygon;
+import com.google.android.gms.maps3d.model.PolygonOptions;
+import com.google.android.gms.maps3d.model.Polyline;
+import com.google.android.gms.maps3d.model.PolylineOptions;
+import java.util.List;
+
+/** Decorator wrapper around GoogleMap3D to track elements for automated cleanup. */
+public class TrackedMap3D {
+
+ private final GoogleMap3D delegate;
+ private final List