Add feature state functionality to Android (#4219)

This commit is contained in:
Brandon Roeder
2026-07-13 13:53:52 -05:00
committed by GitHub
parent 764accc8f5
commit c58080b491
23 changed files with 797 additions and 7 deletions

View File

@@ -37,6 +37,7 @@ class SequentialActivityTest(private val activity: Class<out Activity>) {
org.maplibre.android.testapp.activity.camera.CameraAnimatorActivity::class.java,
org.maplibre.android.testapp.activity.customlayer.CustomLayerActivity::class.java,
org.maplibre.android.testapp.activity.events.ObserverActivity::class.java,
org.maplibre.android.testapp.activity.feature.FeatureStateActivity::class.java,
org.maplibre.android.testapp.activity.feature.QuerySourceFeaturesActivity::class.java,
org.maplibre.android.testapp.activity.fragment.MapFragmentActivity::class.java,
org.maplibre.android.testapp.activity.imagegenerator.SnapshotActivity::class.java,

View File

@@ -1,5 +1,7 @@
package org.maplibre.android.testapp.style;
import com.google.gson.JsonObject;
import org.junit.Ignore;
import org.maplibre.android.style.sources.CannotAddSourceException;
import org.maplibre.android.style.sources.GeoJsonOptions;
@@ -7,6 +9,7 @@ import org.maplibre.geojson.Feature;
import org.maplibre.geojson.FeatureCollection;
import org.maplibre.geojson.Point;
import org.maplibre.android.geometry.LatLng;
import org.maplibre.android.maps.Style;
import org.maplibre.android.style.layers.CircleLayer;
import org.maplibre.android.style.layers.Layer;
import org.maplibre.android.style.sources.GeoJsonSource;
@@ -112,6 +115,77 @@ public class GeoJsonSourceTests extends EspressoTest {
});
}
@Test
public void testFeatureStateUpdatesStyleOwnedSourceImmediately() {
validateTestSetup();
MapLibreMapAction.invoke(maplibreMap, (uiController, maplibreMap) -> {
boolean[] styleLoaded = {false};
String styleJson = ResourceUtils.readRawResource(rule.getActivity(), R.raw.test_feature_state_style);
maplibreMap.setStyle(new Style.Builder().fromJson(styleJson), style -> styleLoaded[0] = true);
while (!styleLoaded[0]) {
uiController.loopMainThreadForAtLeast(100);
}
TestingAsyncUtils.INSTANCE.waitForLayer(uiController, mapView);
GeoJsonSource source = maplibreMap.getStyle().getSourceAs("style-source");
assertEquals(0, queryFeatureCount(maplibreMap));
JsonObject state = new JsonObject();
state.addProperty("selected", true);
source.setFeatureState("feature-1", state);
// The feature-state update is applied on a subsequent render pass, so
// poll until the filter re-evaluates rather than relying on a single
// didBecomeIdle callback (which can be missed if the repaint completes
// before the listener is attached).
assertEquals(1, pollFeatureCount(uiController, maplibreMap, 1));
});
}
@Test
public void testFeatureStateKeyRemovalAcrossSource() {
validateTestSetup();
MapLibreMapAction.invoke(maplibreMap, (uiController, maplibreMap) -> {
boolean[] styleLoaded = {false};
String styleJson = ResourceUtils.readRawResource(rule.getActivity(), R.raw.test_feature_state_style);
maplibreMap.setStyle(new Style.Builder().fromJson(styleJson), style -> styleLoaded[0] = true);
while (!styleLoaded[0]) {
uiController.loopMainThreadForAtLeast(100);
}
TestingAsyncUtils.INSTANCE.waitForLayer(uiController, mapView);
GeoJsonSource source = maplibreMap.getStyle().getSourceAs("style-source");
JsonObject state = new JsonObject();
state.addProperty("selected", true);
source.setFeatureState("feature-1", state);
assertEquals(1, pollFeatureCount(uiController, maplibreMap, 1));
source.removeFeatureState(null, "selected");
assertEquals(0, pollFeatureCount(uiController, maplibreMap, 0));
});
}
private int queryFeatureCount(org.maplibre.android.maps.MapLibreMap maplibreMap) {
return maplibreMap.queryRenderedFeatures(
maplibreMap.getProjection().toScreenLocation(new LatLng(0.0, 0.0)), "selected-layer").size();
}
private int pollFeatureCount(androidx.test.espresso.UiController uiController,
org.maplibre.android.maps.MapLibreMap maplibreMap,
int expected) {
long deadline = System.nanoTime() / 1_000_000L + 5_000L;
int count = queryFeatureCount(maplibreMap);
while (count != expected && System.nanoTime() / 1_000_000L < deadline) {
uiController.loopMainThreadForAtLeast(100);
count = queryFeatureCount(maplibreMap);
}
return count;
}
@Test
public void testClearCollectionDuringConversion() {
// https://github.com/mapbox/mapbox-gl-native/issues/14565