Skip to content

Commit 65bd665

Browse files
authored
Add method to delete multiple annotations (#1081)
* add a method to delete multiple annotations at once * update examples * add changelog entry * address review feedback * add tests
1 parent cfac875 commit 65bd665

35 files changed

+696
-21
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
### main
22

3+
* Add `deleteMulti()` method to all annotation managers to enable batch deletion of annotations.
34
* Add experimental MapRecorder API to record and replay map interactions for debugging and performance testing.
45

56
> [!NOTE]

android/src/main/kotlin/com/mapbox/maps/mapbox_maps/annotation/CircleAnnotationController.kt

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,38 @@ class CircleAnnotationController(private val delegate: ControllerDelegate) : _Ci
130130
}
131131
}
132132

133+
override fun deleteMulti(
134+
managerId: String,
135+
annotations: List<CircleAnnotation>,
136+
callback: (Result<Unit>) -> Unit
137+
) {
138+
try {
139+
val manager = delegate.getManager(managerId) as CircleAnnotationManager
140+
141+
// Filter to only annotations that exist in the map
142+
val nativeAnnotations = annotations.mapNotNull { annotation ->
143+
annotationMap[annotation.id]
144+
}
145+
146+
// Delete from native manager (only existing annotations)
147+
if (nativeAnnotations.isNotEmpty()) {
148+
manager.delete(nativeAnnotations)
149+
}
150+
151+
// Clean up tracking maps (only for annotations that existed)
152+
annotations.forEach { annotation ->
153+
if (annotationMap.containsKey(annotation.id)) {
154+
annotationMap.remove(annotation.id)
155+
managerCreateAnnotationMap[managerId]?.remove(annotation.id)
156+
}
157+
}
158+
159+
callback(Result.success(Unit))
160+
} catch (e: Exception) {
161+
callback(Result.failure(e))
162+
}
163+
}
164+
133165
private fun updateAnnotation(annotation: CircleAnnotation): com.mapbox.maps.plugin.annotation.generated.CircleAnnotation {
134166
val originalAnnotation = annotationMap[annotation.id]!!
135167
annotation.geometry?.let {

android/src/main/kotlin/com/mapbox/maps/mapbox_maps/annotation/PointAnnotationController.kt

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,38 @@ class PointAnnotationController(private val delegate: ControllerDelegate) : _Poi
155155
}
156156
}
157157

158+
override fun deleteMulti(
159+
managerId: String,
160+
annotations: List<PointAnnotation>,
161+
callback: (Result<Unit>) -> Unit
162+
) {
163+
try {
164+
val manager = delegate.getManager(managerId) as PointAnnotationManager
165+
166+
// Filter to only annotations that exist in the map
167+
val nativeAnnotations = annotations.mapNotNull { annotation ->
168+
annotationMap[annotation.id]
169+
}
170+
171+
// Delete from native manager (only existing annotations)
172+
if (nativeAnnotations.isNotEmpty()) {
173+
manager.delete(nativeAnnotations)
174+
}
175+
176+
// Clean up tracking maps (only for annotations that existed)
177+
annotations.forEach { annotation ->
178+
if (annotationMap.containsKey(annotation.id)) {
179+
annotationMap.remove(annotation.id)
180+
managerCreateAnnotationMap[managerId]?.remove(annotation.id)
181+
}
182+
}
183+
184+
callback(Result.success(Unit))
185+
} catch (e: Exception) {
186+
callback(Result.failure(e))
187+
}
188+
}
189+
158190
private fun updateAnnotation(annotation: PointAnnotation): com.mapbox.maps.plugin.annotation.generated.PointAnnotation {
159191
val originalAnnotation = annotationMap[annotation.id]!!
160192
annotation.geometry?.let {

android/src/main/kotlin/com/mapbox/maps/mapbox_maps/annotation/PolygonAnnotationController.kt

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,38 @@ class PolygonAnnotationController(private val delegate: ControllerDelegate) : _P
126126
}
127127
}
128128

129+
override fun deleteMulti(
130+
managerId: String,
131+
annotations: List<PolygonAnnotation>,
132+
callback: (Result<Unit>) -> Unit
133+
) {
134+
try {
135+
val manager = delegate.getManager(managerId) as PolygonAnnotationManager
136+
137+
// Filter to only annotations that exist in the map
138+
val nativeAnnotations = annotations.mapNotNull { annotation ->
139+
annotationMap[annotation.id]
140+
}
141+
142+
// Delete from native manager (only existing annotations)
143+
if (nativeAnnotations.isNotEmpty()) {
144+
manager.delete(nativeAnnotations)
145+
}
146+
147+
// Clean up tracking maps (only for annotations that existed)
148+
annotations.forEach { annotation ->
149+
if (annotationMap.containsKey(annotation.id)) {
150+
annotationMap.remove(annotation.id)
151+
managerCreateAnnotationMap[managerId]?.remove(annotation.id)
152+
}
153+
}
154+
155+
callback(Result.success(Unit))
156+
} catch (e: Exception) {
157+
callback(Result.failure(e))
158+
}
159+
}
160+
129161
private fun updateAnnotation(annotation: PolygonAnnotation): com.mapbox.maps.plugin.annotation.generated.PolygonAnnotation {
130162
val originalAnnotation = annotationMap[annotation.id]!!
131163
annotation.geometry?.let {

android/src/main/kotlin/com/mapbox/maps/mapbox_maps/annotation/PolylineAnnotationController.kt

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,38 @@ class PolylineAnnotationController(private val delegate: ControllerDelegate) : _
133133
}
134134
}
135135

136+
override fun deleteMulti(
137+
managerId: String,
138+
annotations: List<PolylineAnnotation>,
139+
callback: (Result<Unit>) -> Unit
140+
) {
141+
try {
142+
val manager = delegate.getManager(managerId) as PolylineAnnotationManager
143+
144+
// Filter to only annotations that exist in the map
145+
val nativeAnnotations = annotations.mapNotNull { annotation ->
146+
annotationMap[annotation.id]
147+
}
148+
149+
// Delete from native manager (only existing annotations)
150+
if (nativeAnnotations.isNotEmpty()) {
151+
manager.delete(nativeAnnotations)
152+
}
153+
154+
// Clean up tracking maps (only for annotations that existed)
155+
annotations.forEach { annotation ->
156+
if (annotationMap.containsKey(annotation.id)) {
157+
annotationMap.remove(annotation.id)
158+
managerCreateAnnotationMap[managerId]?.remove(annotation.id)
159+
}
160+
}
161+
162+
callback(Result.success(Unit))
163+
} catch (e: Exception) {
164+
callback(Result.failure(e))
165+
}
166+
}
167+
136168
private fun updateAnnotation(annotation: PolylineAnnotation): com.mapbox.maps.plugin.annotation.generated.PolylineAnnotation {
137169
val originalAnnotation = annotationMap[annotation.id]!!
138170
annotation.geometry?.let {

android/src/main/kotlin/com/mapbox/maps/mapbox_maps/pigeons/CircleAnnotationMessenger.kt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,7 @@ interface _CircleAnnotationMessenger {
415415
fun update(managerId: String, annotation: CircleAnnotation, callback: (Result<Unit>) -> Unit)
416416
fun delete(managerId: String, annotation: CircleAnnotation, callback: (Result<Unit>) -> Unit)
417417
fun deleteAll(managerId: String, callback: (Result<Unit>) -> Unit)
418+
fun deleteMulti(managerId: String, annotations: List<CircleAnnotation>, callback: (Result<Unit>) -> Unit)
418419
fun setCircleElevationReference(managerId: String, circleElevationReference: CircleElevationReference, callback: (Result<Unit>) -> Unit)
419420
fun getCircleElevationReference(managerId: String, callback: (Result<CircleElevationReference?>) -> Unit)
420421
fun setCircleSortKey(managerId: String, circleSortKey: Double, callback: (Result<Unit>) -> Unit)
@@ -574,6 +575,26 @@ interface _CircleAnnotationMessenger {
574575
channel.setMessageHandler(null)
575576
}
576577
}
578+
run {
579+
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.mapbox_maps_flutter._CircleAnnotationMessenger.deleteMulti$separatedMessageChannelSuffix", codec)
580+
if (api != null) {
581+
channel.setMessageHandler { message, reply ->
582+
val args = message as List<Any?>
583+
val managerIdArg = args[0] as String
584+
val annotationsArg = args[1] as List<CircleAnnotation>
585+
api.deleteMulti(managerIdArg, annotationsArg) { result: Result<Unit> ->
586+
val error = result.exceptionOrNull()
587+
if (error != null) {
588+
reply.reply(wrapError(error))
589+
} else {
590+
reply.reply(wrapResult(null))
591+
}
592+
}
593+
}
594+
} else {
595+
channel.setMessageHandler(null)
596+
}
597+
}
577598
run {
578599
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.mapbox_maps_flutter._CircleAnnotationMessenger.setCircleElevationReference$separatedMessageChannelSuffix", codec)
579600
if (api != null) {

android/src/main/kotlin/com/mapbox/maps/mapbox_maps/pigeons/PointAnnotationMessenger.kt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1259,6 +1259,7 @@ interface _PointAnnotationMessenger {
12591259
fun update(managerId: String, annotation: PointAnnotation, callback: (Result<Unit>) -> Unit)
12601260
fun delete(managerId: String, annotation: PointAnnotation, callback: (Result<Unit>) -> Unit)
12611261
fun deleteAll(managerId: String, callback: (Result<Unit>) -> Unit)
1262+
fun deleteMulti(managerId: String, annotations: List<PointAnnotation>, callback: (Result<Unit>) -> Unit)
12621263
fun setIconAllowOverlap(managerId: String, iconAllowOverlap: Boolean, callback: (Result<Unit>) -> Unit)
12631264
fun getIconAllowOverlap(managerId: String, callback: (Result<Boolean?>) -> Unit)
12641265
fun setIconAnchor(managerId: String, iconAnchor: IconAnchor, callback: (Result<Unit>) -> Unit)
@@ -1526,6 +1527,26 @@ interface _PointAnnotationMessenger {
15261527
channel.setMessageHandler(null)
15271528
}
15281529
}
1530+
run {
1531+
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.mapbox_maps_flutter._PointAnnotationMessenger.deleteMulti$separatedMessageChannelSuffix", codec)
1532+
if (api != null) {
1533+
channel.setMessageHandler { message, reply ->
1534+
val args = message as List<Any?>
1535+
val managerIdArg = args[0] as String
1536+
val annotationsArg = args[1] as List<PointAnnotation>
1537+
api.deleteMulti(managerIdArg, annotationsArg) { result: Result<Unit> ->
1538+
val error = result.exceptionOrNull()
1539+
if (error != null) {
1540+
reply.reply(wrapError(error))
1541+
} else {
1542+
reply.reply(wrapResult(null))
1543+
}
1544+
}
1545+
}
1546+
} else {
1547+
channel.setMessageHandler(null)
1548+
}
1549+
}
15291550
run {
15301551
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.mapbox_maps_flutter._PointAnnotationMessenger.setIconAllowOverlap$separatedMessageChannelSuffix", codec)
15311552
if (api != null) {

android/src/main/kotlin/com/mapbox/maps/mapbox_maps/pigeons/PolygonAnnotationMessenger.kt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,7 @@ interface _PolygonAnnotationMessenger {
377377
fun update(managerId: String, annotation: PolygonAnnotation, callback: (Result<Unit>) -> Unit)
378378
fun delete(managerId: String, annotation: PolygonAnnotation, callback: (Result<Unit>) -> Unit)
379379
fun deleteAll(managerId: String, callback: (Result<Unit>) -> Unit)
380+
fun deleteMulti(managerId: String, annotations: List<PolygonAnnotation>, callback: (Result<Unit>) -> Unit)
380381
fun setFillConstructBridgeGuardRail(managerId: String, fillConstructBridgeGuardRail: Boolean, callback: (Result<Unit>) -> Unit)
381382
fun getFillConstructBridgeGuardRail(managerId: String, callback: (Result<Boolean?>) -> Unit)
382383
fun setFillElevationReference(managerId: String, fillElevationReference: FillElevationReference, callback: (Result<Unit>) -> Unit)
@@ -536,6 +537,26 @@ interface _PolygonAnnotationMessenger {
536537
channel.setMessageHandler(null)
537538
}
538539
}
540+
run {
541+
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.mapbox_maps_flutter._PolygonAnnotationMessenger.deleteMulti$separatedMessageChannelSuffix", codec)
542+
if (api != null) {
543+
channel.setMessageHandler { message, reply ->
544+
val args = message as List<Any?>
545+
val managerIdArg = args[0] as String
546+
val annotationsArg = args[1] as List<PolygonAnnotation>
547+
api.deleteMulti(managerIdArg, annotationsArg) { result: Result<Unit> ->
548+
val error = result.exceptionOrNull()
549+
if (error != null) {
550+
reply.reply(wrapError(error))
551+
} else {
552+
reply.reply(wrapResult(null))
553+
}
554+
}
555+
}
556+
} else {
557+
channel.setMessageHandler(null)
558+
}
559+
}
539560
run {
540561
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.mapbox_maps_flutter._PolygonAnnotationMessenger.setFillConstructBridgeGuardRail$separatedMessageChannelSuffix", codec)
541562
if (api != null) {

android/src/main/kotlin/com/mapbox/maps/mapbox_maps/pigeons/PolylineAnnotationMessenger.kt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -541,6 +541,7 @@ interface _PolylineAnnotationMessenger {
541541
fun update(managerId: String, annotation: PolylineAnnotation, callback: (Result<Unit>) -> Unit)
542542
fun delete(managerId: String, annotation: PolylineAnnotation, callback: (Result<Unit>) -> Unit)
543543
fun deleteAll(managerId: String, callback: (Result<Unit>) -> Unit)
544+
fun deleteMulti(managerId: String, annotations: List<PolylineAnnotation>, callback: (Result<Unit>) -> Unit)
544545
fun setLineCap(managerId: String, lineCap: LineCap, callback: (Result<Unit>) -> Unit)
545546
fun getLineCap(managerId: String, callback: (Result<LineCap?>) -> Unit)
546547
fun setLineCrossSlope(managerId: String, lineCrossSlope: Double, callback: (Result<Unit>) -> Unit)
@@ -730,6 +731,26 @@ interface _PolylineAnnotationMessenger {
730731
channel.setMessageHandler(null)
731732
}
732733
}
734+
run {
735+
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.mapbox_maps_flutter._PolylineAnnotationMessenger.deleteMulti$separatedMessageChannelSuffix", codec)
736+
if (api != null) {
737+
channel.setMessageHandler { message, reply ->
738+
val args = message as List<Any?>
739+
val managerIdArg = args[0] as String
740+
val annotationsArg = args[1] as List<PolylineAnnotation>
741+
api.deleteMulti(managerIdArg, annotationsArg) { result: Result<Unit> ->
742+
val error = result.exceptionOrNull()
743+
if (error != null) {
744+
reply.reply(wrapError(error))
745+
} else {
746+
reply.reply(wrapResult(null))
747+
}
748+
}
749+
}
750+
} else {
751+
channel.setMessageHandler(null)
752+
}
753+
}
733754
run {
734755
val channel = BasicMessageChannel<Any?>(binaryMessenger, "dev.flutter.pigeon.mapbox_maps_flutter._PolylineAnnotationMessenger.setLineCap$separatedMessageChannelSuffix", codec)
735756
if (api != null) {

example/integration_test/annotations/circle_annotation_test.dart

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,5 +71,46 @@ void main() {
7171

7272
await manager.deleteAll();
7373
});
74+
75+
testWidgets('deleteMulti CircleAnnotation', (WidgetTester tester) async {
76+
final mapFuture = app.main();
77+
await tester.pumpAndSettle();
78+
final mapboxMap = await mapFuture;
79+
final manager = await mapboxMap.annotations.createCircleAnnotationManager();
80+
var geometry = Point(coordinates: Position(1.0, 2.0));
81+
82+
var circleAnnotationOptions = CircleAnnotationOptions(
83+
geometry: geometry,
84+
);
85+
86+
// Create 10 annotations
87+
var createdAnnotations = <CircleAnnotation>[];
88+
for (var i = 0; i < 10; i++) {
89+
final annotation = await manager.create(circleAnnotationOptions);
90+
createdAnnotations.add(annotation);
91+
}
92+
93+
var allAnnotations = await manager.getAnnotations();
94+
expect(allAnnotations.length, equals(10));
95+
96+
// Delete first 5 annotations
97+
final toDelete = createdAnnotations.take(5).toList();
98+
await manager.deleteMulti(toDelete);
99+
100+
allAnnotations = await manager.getAnnotations();
101+
expect(allAnnotations.length, equals(5));
102+
103+
// Delete remaining annotations plus some already deleted (partial deletion)
104+
await manager.deleteMulti(createdAnnotations);
105+
106+
allAnnotations = await manager.getAnnotations();
107+
expect(allAnnotations.length, equals(0));
108+
109+
// Delete empty list should succeed
110+
await manager.deleteMulti([]);
111+
112+
allAnnotations = await manager.getAnnotations();
113+
expect(allAnnotations.length, equals(0));
114+
});
74115
}
75116
// End of generated file.

0 commit comments

Comments
 (0)