Skip to content

feat: modernize Java snippets module with capabilities test suite - #2426

Open
dkhawk wants to merge 1 commit into
feat/snippets-commonfrom
feat/snippets-java-app
Open

dkhawk wants to merge 1 commit into
feat/snippets-commonfrom
feat/snippets-java-app

Conversation

@dkhawk

@dkhawk dkhawk commented Sep 15, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Create :snippets:java-app with 14 snippet categories and documentation region tags
  • Add Java snippet infrastructure (JavaSnippetsActivity, MapActivity, SnippetRegistry, TrackedMap)
  • Add Java capabilities test suite (CatalogCapabilitiesTestSuite, CameraControl, Events, MapInit, Marker)
  • Remove legacy Java snippet modules (snippets/app, snippets/app-rx, snippets/app-utils)
  • Update root settings.gradle.kts

Stacked Base

Stacked on #2425 (feat/snippets-common).

Reviewers

@kikoso

@snippet-bot

snippet-bot Bot commented Sep 15, 2026

Copy link
Copy Markdown

Here is the summary of changes.

You are about to add 125 region tags.
You are about to delete 202 region tags.

This comment is generated by snippet-bot.
If you find problems with this result, please file an issue at:
https://github.com/googleapis/repo-automation-bots/issues.
To update this comment, add snippet-bot:force-run label or use the checkbox below:

  • Refresh this comment

@dkhawk
dkhawk added this pull request to stack #2429 September 15, 2026 00:43
@dkhawk
dkhawk force-pushed the feat/snippets-java-app branch 2 times, most recently from 95f1d6b to 9303940 Compare September 15, 2026 22:06
Comment on lines +96 to +102
String codeSnippet = String.format(
"CameraPosition.builder()\n .target(new LatLng(%.6f, %.6f))\n .zoom(%.1ff)\n .tilt(%.1ff)\n .bearing(%.1ff)\n .build()",
target.latitude,
target.longitude,
cam.zoom,
cam.tilt,
bearing);
collapsedGroups.add(group.getTitle());
}
updateVisibleItems();
notifyDataSetChanged();
}

public void setOnMarkerClickListener(GoogleMap.OnMarkerClickListener listener) {
delegate.setOnMarkerClickListener(listener);
}

public void setOnInfoWindowClickListener(GoogleMap.OnInfoWindowClickListener listener) {
delegate.setOnInfoWindowClickListener(listener);
map.setOnCameraMoveStartedListener(null);
map.setOnCameraMoveCanceledListener(null);
map.setOnCameraIdleListener(null);
map.setOnMarkerClickListener(null);
map.setOnCameraIdleListener(null);
map.setOnMarkerClickListener(null);
map.setOnMarkerDragListener(null);
map.setOnInfoWindowClickListener(null);
map.setOnMarkerClickListener(null);
map.setOnMarkerDragListener(null);
map.setOnInfoWindowClickListener(null);
map.setOnInfoWindowLongClickListener(null);
map.setOnInfoWindowClickListener(null);
map.setOnInfoWindowLongClickListener(null);
map.setOnInfoWindowCloseListener(null);
map.setInfoWindowAdapter(null);
android.widget.LinearLayout container = activity.findViewById(R.id.custom_controls_container);
if (container != null) {
android.widget.Button toggleButton = new android.widget.Button(context);
toggleButton.setText("Mode: Difficulty");
public URL getTileUrl(int x, int y, int zoom) {

/* Define the URL pattern for the tile images */
String s = String.format("http://my.image.server/images/%d/%d/%d.png", zoom, x, y);
);

// 2. Center the camera over Hana, Hawaii
map.getDelegate().moveCamera(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

map is a instance of TrackedMap and it does not wrap all GoogleMap methods, 32 lines inside active [START ...] / [END ...] tags call map.getDelegate() (which does not exist on GoogleMap and will not compile for developers copying snippets from developers.google.com)

);

// 2. Center the camera over Boulder OSMP Trails
map.getDelegate().moveCamera(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

map is a instance of TrackedMap and it does not wrap all GoogleMap methods, 32 lines inside active [START ...] / [END ...] tags call map.getDelegate() (which does not exist on GoogleMap and will not compile for developers copying snippets from developers.google.com)

)
public void focusedBuilding() {
// [START maps_android_events_active_level]
IndoorBuilding building = map.getDelegate().getFocusedBuilding();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

map is a instance of TrackedMap and it does not wrap all GoogleMap methods, 32 lines inside active [START ...] / [END ...] tags call map.getDelegate() (which does not exist on GoogleMap and will not compile for developers copying snippets from developers.google.com)

)
public void enableTrafficLayer() {
// [START maps_android_traffic_layer]
map.getDelegate().setTrafficEnabled(true);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

same for this map instance

// location permission from the user. This sample does not include
// a request for location permission.
map.setMyLocationEnabled(true);
map.getDelegate().setOnMyLocationButtonClickListener(new GoogleMap.OnMyLocationButtonClickListener() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

same for this map.getDelegate()

title = "Utility Library",
description = "Snippets demonstrating marker clustering, heatmaps, GeoJSON, KML, and Multilayer managers."
)
public class UtilsSnippets {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stateless Snippet Lifecycle Causes 7 of 15 UtilsSnippets (and Several Other Snippets) to Be Runtime No-Ops

Root Cause

Every time a snippet is selected or navigated to via Previous / Next:

  1. MapActivity.runSnippet() calls recreateMapView(), destroying the previous MapView and initializing a brand-new GoogleMap.
  2. SnippetRegistry.getSnippetGroups() (SnippetRegistry.java:108-115) invokes createInstance(clazz, context, trackedMap), creating a brand-new instance of the snippet class for that single @SnippetItem call:
    TrackedMap trackedMap = new TrackedMap(map, addedElements);
    Object instance = createInstance(clazz, context, trackedMap);
    method.invoke(instance);

Because a fresh instance of UtilsSnippets is created for every @SnippetItem execution, its instance fields (clusterManager, geoJsonLayer, heatmapTileOverlay) are always null when any follow-up snippet is selected.


1. UtilsSnippets.java — 7 Snippets Are Dead No-Ops (field == null)

All of the following snippets guard on an instance field that is only initialized in a different @SnippetItem method, so selecting them from the list (or via Next/Previous) results in a blank map with zero execution:

@SnippetItem Title Method (UtilsSnippets.java) Guard Condition Runtime Behavior
2. Disable Cluster Animation clusterAnimation() (L165-171) if (clusterManager != null) ❌ Always null → No-op
3. Add Clustering Info Window Item infoWindow() (L177-195) if (clusterManager != null) ❌ Always null → No-op
3b. Clear Cluster Items clearClusterItems() (L201-208) if (clusterManager != null) ❌ Always null → No-op
3c. Remove Single Cluster Item removeSingleClusterItem() (L214-222) if (clusterManager != null) ❌ Always null → No-op
3d. Cluster Listeners demonstrateClusterListeners() (L228-254) if (clusterManager == null) return; ❌ Always null → No-op
5b. Remove GeoJSON Layer removeGeoJsonLayerFile() (L290-296) if (geoJsonLayer != null) ❌ Always null → No-op
10b. Remove Custom Heatmap removeCustomHeatmap() (L535-541) if (heatmapTileOverlay != null) ❌ Always null → No-op

2. StreetViewSnippets.java — 3 Snippets Only Allocate Unused Local Variables

In StreetViewSnippets.java:52-80, the descriptions promise live visual changes on the Street View panorama, but the methods only instantiate unused local variables and immediately return without launching or updating Street View:

  • 2. Set Panorama Location (setLocation(), L56-58): Only runs LatLng sanFrancisco = new LatLng(37.754130, -122.447129); and exits.
  • 3. Zoom Panorama (zoomPanorama(), L64-69): Builds a local StreetViewPanoramaCamera object and discards it.
  • 4. Animate Camera (animatePanorama(), L75-80): Builds a local StreetViewPanoramaCamera object and discards it.

3. MapInitSnippets.java & CloudCustomizationSnippets.java — Unused Local Options/Fragments

  • MapInitSnippets.java (L98-156, L181-186): googleMapOptions(), fragmentMapId(), mapViewMapId(), liteMode(), cloudBasedMapStyling(), and setMapColorScheme() construct local GoogleMapOptions / SupportMapFragment / MapView objects and discard them without applying them to the active map (for example, setMapColorScheme() never calls map.getDelegate().setMapColorScheme(MapColorScheme.DARK), and googleMapOptions() never applies satellite mode or gesture settings to map).
  • CloudCustomizationSnippets.java (L45-143): All 8 @SnippetItem methods instantiate an unattached SupportMapFragment.newInstance(...) into a local variable and return without attaching it or configuring the active map.

💡 Suggested Fix

  1. Make dependent @SnippetItem methods self-contained: In UtilsSnippets.java, call the prerequisite setup (setUpClusterer(), addGeoJsonLayerFile(), addCustomHeatmap()) at the start of the dependent methods (outside the [START ...] tag or wrapped in // [START_EXCLUDE silent] ... // [END_EXCLUDE]) so clusterManager, geoJsonLayer, and heatmapTileOverlay are non-null and visible on the map before the snippet action runs:
    public void clusterAnimation() {
        setUpClusterer();
        // [START maps_android_utils_clustering_animation_off]
        clusterManager.setAnimation(false);
        // [END maps_android_utils_clustering_animation_off]
    }
  2. Apply live map state where descriptions promise visual feedback: For example, in MapInitSnippets.setMapColorScheme(), apply map.getDelegate().setMapColorScheme(MapColorScheme.DARK) (inside [START_EXCLUDE]) so the dark color scheme actually renders when the user opens that snippet.

}

@Test
public void verifyAllSnippetsLaunchWithoutCrash() throws Exception {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Silent Exception Swallowing in

SnippetRegistry.java:L116-L118
: SnippetRegistry catches Exception and only calls e.printStackTrace(). Consequently,

MapActivity.java:L303-L306
is unreachable, and

SnippetDiscoveryTest.verifyAllSnippetsLaunchWithoutCrash()
will pass even if every single snippet throws a NullPointerException or RuntimeException.
SnippetDiscoveryTest.verifyAllSnippetsLaunchWithoutCrash() Closes ActivityScenario Before onMapReady Fires (

SnippetDiscoveryTest.java:L66-L76
): scenario.onActivity(...) checks activity.mapView != null (which is synchronously created in onCreate before getMapAsync completes) and immediately closes the try (ActivityScenario ...) block—destroying the Activity before onMapReady even executes the snippet! Furthermore, launching and destroying MapActivity 73 times in a single @test loop rather than reusing the activity via onNewIntent risks OOM/timeout on CI emulators.
MapActivity.onCreate() Calls finish() When Using Default/Placeholder API Keys (

MapActivity.java:L72-L76
): If MAPS_API_KEY is "DEFAULT_API_KEY" (from local.defaults.properties), MapActivity.onCreate() calls finish(); return; before runSnippet() initializes mapView, causing all ActivityScenario tests in SnippetDiscoveryTest and CatalogCapabilitiesTestSuite to fail in environments without a live AIza... key.

For DataDrivenBoundarySnippets.java, i tried below code
public void styleLocalityBoundary() {
if (true) throw new RuntimeException("BOOM! Intentional crash!");

and run verifyAllSnippetsLaunchWithoutCrash(), still it's passed

String apiKey = appInfo.metaData.getString("com.google.android.geo.API_KEY");
if (apiKey == null || apiKey.isEmpty() || apiKey.equals("DEFAULT_API_KEY") || apiKey.equals("YOUR_API_KEY") || !apiKey.startsWith("AIza")) {
Toast.makeText(this, "ERROR: Invalid Google Maps API Key configured in secrets.properties", Toast.LENGTH_LONG).show();
Log.e("MapActivity", "Invalid MAPS_API_KEY: '" + apiKey + "'");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

If apiKey has leading whitespace (e.g. " AIza..."), !apiKey.startsWith("AIza") triggers and logs the raw secret key directly to system Logcat. Trim apiKey first and never log the raw key value.

- Create :snippets:java-app with 14 snippet categories and documentation region tags
- Add Java snippet infrastructure (JavaSnippetsActivity, MapActivity, SnippetRegistry, TrackedMap)
- Add Java capabilities test suite (CatalogCapabilitiesTestSuite, CameraControl, Events, MapInit, Marker)
- Remove legacy Java snippet modules (snippets/app, snippets/app-rx, snippets/app-utils)
- Update root settings.gradle.kts
@dkhawk
dkhawk force-pushed the feat/snippets-java-app branch from 9303940 to 4acc43c Compare September 17, 2026 22:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants