[google_maps_flutter] Adds support for holes in polygon overlays to the Google Maps plugin (#1721)

diff --git a/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md
index b23ef79..6dcb967 100644
--- a/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md
+++ b/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 1.1.0
+
+* Add support for holes in Polygons.
+
 ## 1.0.10
 
 * Update the example app: remove the deprecated `RaisedButton` and `FlatButton` widgets.
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java
index 7222511..4108a1d 100644
--- a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java
+++ b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java
@@ -468,6 +468,10 @@
     if (points != null) {
       sink.setPoints(toPoints(points));
     }
+    final Object holes = data.get("holes");
+    if (holes != null) {
+      sink.setHoles(toHoles(holes));
+    }
     final String polygonId = (String) data.get("polygonId");
     if (polygonId == null) {
       throw new IllegalArgumentException("polygonId was null");
@@ -576,13 +580,23 @@
     final List<?> data = toList(o);
     final List<LatLng> points = new ArrayList<>(data.size());
 
-    for (Object ob : data) {
-      final List<?> point = toList(ob);
+    for (Object rawPoint : data) {
+      final List<?> point = toList(rawPoint);
       points.add(new LatLng(toFloat(point.get(0)), toFloat(point.get(1))));
     }
     return points;
   }
 
+  private static List<List<LatLng>> toHoles(Object o) {
+    final List<?> data = toList(o);
+    final List<List<LatLng>> holes = new ArrayList<>(data.size());
+
+    for (Object rawHole : data) {
+      holes.add(toPoints(rawHole));
+    }
+    return holes;
+  }
+
   private static List<PatternItem> toPattern(Object o) {
     final List<?> data = toList(o);
 
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/PolygonBuilder.java b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/PolygonBuilder.java
index 600762a..7691e58 100644
--- a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/PolygonBuilder.java
+++ b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/PolygonBuilder.java
@@ -42,6 +42,13 @@
   }
 
   @Override
+  public void setHoles(List<List<LatLng>> holes) {
+    for (List<LatLng> hole : holes) {
+      polygonOptions.addHole(hole);
+    }
+  }
+
+  @Override
   public void setConsumeTapEvents(boolean consumeTapEvents) {
     this.consumeTapEvents = consumeTapEvents;
     polygonOptions.clickable(consumeTapEvents);
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/PolygonController.java b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/PolygonController.java
index adb01b8..43f1bfd 100644
--- a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/PolygonController.java
+++ b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/PolygonController.java
@@ -52,6 +52,10 @@
     polygon.setPoints(points);
   }
 
+  public void setHoles(List<List<LatLng>> holes) {
+    polygon.setHoles(holes);
+  }
+
   @Override
   public void setVisible(boolean visible) {
     polygon.setVisible(visible);
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/PolygonOptionsSink.java b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/PolygonOptionsSink.java
index df4dae0..2985a7b 100644
--- a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/PolygonOptionsSink.java
+++ b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/PolygonOptionsSink.java
@@ -20,6 +20,8 @@
 
   void setPoints(List<LatLng> points);
 
+  void setHoles(List<List<LatLng>> holes);
+
   void setVisible(boolean visible);
 
   void setStrokeWidth(float width);
diff --git a/packages/google_maps_flutter/google_maps_flutter/example/lib/place_polygon.dart b/packages/google_maps_flutter/google_maps_flutter/example/lib/place_polygon.dart
index af5ca16..5f2a098 100644
--- a/packages/google_maps_flutter/google_maps_flutter/example/lib/place_polygon.dart
+++ b/packages/google_maps_flutter/google_maps_flutter/example/lib/place_polygon.dart
@@ -30,7 +30,8 @@
 
   GoogleMapController controller;
   Map<PolygonId, Polygon> polygons = <PolygonId, Polygon>{};
-  int _polygonIdCounter = 1;
+  Map<PolygonId, double> polygonOffsets = <PolygonId, double>{};
+  int _polygonIdCounter = 0;
   PolygonId selectedPolygon;
 
   // Values when toggling polygon color
@@ -79,7 +80,6 @@
     }
 
     final String polygonIdVal = 'polygon_id_$_polygonIdCounter';
-    _polygonIdCounter++;
     final PolygonId polygonId = PolygonId(polygonIdVal);
 
     final Polygon polygon = Polygon(
@@ -96,6 +96,9 @@
 
     setState(() {
       polygons[polygonId] = polygon;
+      polygonOffsets[polygonId] = _polygonIdCounter.ceilToDouble();
+      // increment _polygonIdCounter to have unique polygon id each time
+      _polygonIdCounter++;
     });
   }
 
@@ -144,6 +147,22 @@
     });
   }
 
+  void _addHoles() {
+    final Polygon polygon = polygons[selectedPolygon];
+    setState(() {
+      polygons[selectedPolygon] = polygon.copyWith(holesParam: _createHoles());
+    });
+  }
+
+  void _removeHoles() {
+    final Polygon polygon = polygons[selectedPolygon];
+    setState(() {
+      polygons[selectedPolygon] = polygon.copyWith(
+        holesParam: <List<LatLng>>[],
+      );
+    });
+  }
+
   @override
   Widget build(BuildContext context) {
     return Column(
@@ -197,6 +216,22 @@
                     Column(
                       children: <Widget>[
                         TextButton(
+                          child: const Text('add holes'),
+                          onPressed: (selectedPolygon == null)
+                              ? null
+                              : ((polygons[selectedPolygon].holes.isNotEmpty)
+                                  ? null
+                                  : _addHoles),
+                        ),
+                        TextButton(
+                          child: const Text('remove holes'),
+                          onPressed: (selectedPolygon == null)
+                              ? null
+                              : ((polygons[selectedPolygon].holes.isEmpty)
+                                  ? null
+                                  : _removeHoles),
+                        ),
+                        TextButton(
                           child: const Text('change stroke width'),
                           onPressed:
                               (selectedPolygon == null) ? null : _changeWidth,
@@ -235,6 +270,27 @@
     return points;
   }
 
+  List<List<LatLng>> _createHoles() {
+    final List<List<LatLng>> holes = <List<LatLng>>[];
+    final double offset = polygonOffsets[selectedPolygon];
+
+    final List<LatLng> hole1 = <LatLng>[];
+    hole1.add(_createLatLng(51.8395 + offset, -3.8814));
+    hole1.add(_createLatLng(52.0234 + offset, -3.9914));
+    hole1.add(_createLatLng(52.1351 + offset, -4.4435));
+    hole1.add(_createLatLng(52.0231 + offset, -4.5829));
+    holes.add(hole1);
+
+    final List<LatLng> hole2 = <LatLng>[];
+    hole2.add(_createLatLng(52.2395 + offset, -3.6814));
+    hole2.add(_createLatLng(52.4234 + offset, -3.7914));
+    hole2.add(_createLatLng(52.5351 + offset, -4.2435));
+    hole2.add(_createLatLng(52.4231 + offset, -4.3829));
+    holes.add(hole2);
+
+    return holes;
+  }
+
   LatLng _createLatLng(double lat, double lng) {
     return LatLng(lat, lng);
   }
diff --git a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapPolygonController.h b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapPolygonController.h
index c7613fd..eb1735d 100644
--- a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapPolygonController.h
+++ b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapPolygonController.h
@@ -13,6 +13,7 @@
 - (void)setStrokeColor:(UIColor*)color;
 - (void)setStrokeWidth:(CGFloat)width;
 - (void)setPoints:(NSArray<CLLocation*>*)points;
+- (void)setHoles:(NSArray<NSArray<CLLocation*>*>*)holes;
 - (void)setZIndex:(int)zIndex;
 @end
 
diff --git a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapPolygonController.m b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapPolygonController.m
index 678d40e..1063a8c 100644
--- a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapPolygonController.m
+++ b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapPolygonController.m
@@ -45,6 +45,19 @@
   }
   _polygon.path = path;
 }
+- (void)setHoles:(NSArray<NSArray<CLLocation*>*>*)rawHoles {
+  NSMutableArray<GMSMutablePath*>* holes = [[NSMutableArray<GMSMutablePath*> alloc] init];
+
+  for (NSArray<CLLocation*>* points in rawHoles) {
+    GMSMutablePath* path = [GMSMutablePath path];
+    for (CLLocation* location in points) {
+      [path addCoordinate:location.coordinate];
+    }
+    [holes addObject:path];
+  }
+
+  _polygon.holes = holes;
+}
 
 - (void)setFillColor:(UIColor*)color {
   _polygon.fillColor = color;
@@ -65,6 +78,10 @@
   return [FLTGoogleMapJsonConversions toPoints:data];
 }
 
+static NSArray<NSArray<CLLocation*>*>* ToHoles(NSArray<NSArray*>* data) {
+  return [FLTGoogleMapJsonConversions toHoles:data];
+}
+
 static UIColor* ToColor(NSNumber* data) { return [FLTGoogleMapJsonConversions toColor:data]; }
 
 static void InterpretPolygonOptions(NSDictionary* data, id<FLTGoogleMapPolygonOptionsSink> sink,
@@ -89,6 +106,11 @@
     [sink setPoints:ToPoints(points)];
   }
 
+  NSArray* holes = data[@"holes"];
+  if (holes) {
+    [sink setHoles:ToHoles(holes)];
+  }
+
   NSNumber* fillColor = data[@"fillColor"];
   if (fillColor != nil) {
     [sink setFillColor:ToColor(fillColor)];
diff --git a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/JsonConversions.h b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/JsonConversions.h
index c54b2ad..099ff3b 100644
--- a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/JsonConversions.h
+++ b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/JsonConversions.h
@@ -15,4 +15,5 @@
 + (NSArray*)positionToJson:(CLLocationCoordinate2D)position;
 + (UIColor*)toColor:(NSNumber*)data;
 + (NSArray<CLLocation*>*)toPoints:(NSArray*)data;
++ (NSArray<NSArray<CLLocation*>*>*)toHoles:(NSArray*)data;
 @end
diff --git a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/JsonConversions.m b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/JsonConversions.m
index 6381bea..829f877 100644
--- a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/JsonConversions.m
+++ b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/JsonConversions.m
@@ -58,4 +58,14 @@
   return points;
 }
 
++ (NSArray<NSArray<CLLocation*>*>*)toHoles:(NSArray*)data {
+  NSMutableArray<NSArray<CLLocation*>*>* holes = [[[NSMutableArray alloc] init] init];
+  for (unsigned i = 0; i < [data count]; i++) {
+    NSArray<CLLocation*>* points = [FLTGoogleMapJsonConversions toPoints:data[i]];
+    [holes addObject:points];
+  }
+
+  return holes;
+}
+
 @end
diff --git a/packages/google_maps_flutter/google_maps_flutter/lib/google_maps_flutter.dart b/packages/google_maps_flutter/google_maps_flutter/lib/google_maps_flutter.dart
index b879f3d..682c901 100644
--- a/packages/google_maps_flutter/google_maps_flutter/lib/google_maps_flutter.dart
+++ b/packages/google_maps_flutter/google_maps_flutter/lib/google_maps_flutter.dart
@@ -13,7 +13,6 @@
 import 'package:flutter/gestures.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/services.dart';
-
 import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
 import 'package:google_maps_flutter_platform_interface/src/method_channel/method_channel_google_maps_flutter.dart';
 
diff --git a/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml
index 4faadf4..c07d189 100644
--- a/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml
+++ b/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml
@@ -1,13 +1,13 @@
 name: google_maps_flutter
 description: A Flutter plugin for integrating Google Maps in iOS and Android applications.
 homepage: https://github.com/flutter/plugins/tree/master/packages/google_maps_flutter/google_maps_flutter
-version: 1.0.10
+version: 1.1.0
 
 dependencies:
   flutter:
     sdk: flutter
   flutter_plugin_android_lifecycle: ^1.0.0
-  google_maps_flutter_platform_interface: ^1.0.4
+  google_maps_flutter_platform_interface: ^1.1.0
 
 dev_dependencies:
   flutter_test:
diff --git a/packages/google_maps_flutter/google_maps_flutter/test/fake_maps_controllers.dart b/packages/google_maps_flutter/google_maps_flutter/test/fake_maps_controllers.dart
index adca8b4..9a849bd 100644
--- a/packages/google_maps_flutter/google_maps_flutter/test/fake_maps_controllers.dart
+++ b/packages/google_maps_flutter/google_maps_flutter/test/fake_maps_controllers.dart
@@ -202,12 +202,14 @@
       final bool visible = polygonData['visible'];
       final bool geodesic = polygonData['geodesic'];
       final List<LatLng> points = _deserializePoints(polygonData['points']);
+      final List<List<LatLng>> holes = _deserializeHoles(polygonData['holes']);
 
       result.add(Polygon(
         polygonId: PolygonId(polygonId),
         visible: visible,
         geodesic: geodesic,
         points: points,
+        holes: holes,
       ));
     }
 
@@ -220,6 +222,14 @@
     }).toList();
   }
 
+  List<List<LatLng>> _deserializeHoles(List<dynamic> holes) {
+    return holes.map<List<LatLng>>((dynamic hole) {
+      return hole.map<LatLng>((dynamic list) {
+        return LatLng(list[0], list[1]);
+      }).toList();
+    }).toList();
+  }
+
   void updatePolylines(Map<dynamic, dynamic> polylineUpdates) {
     if (polylineUpdates == null) {
       return;
diff --git a/packages/google_maps_flutter/google_maps_flutter/test/polygon_updates_test.dart b/packages/google_maps_flutter/google_maps_flutter/test/polygon_updates_test.dart
index 185c996..667c7d8 100644
--- a/packages/google_maps_flutter/google_maps_flutter/test/polygon_updates_test.dart
+++ b/packages/google_maps_flutter/google_maps_flutter/test/polygon_updates_test.dart
@@ -33,6 +33,29 @@
   );
 }
 
+List<LatLng> _rectPoints({
+  @required double size,
+  LatLng center = const LatLng(0, 0),
+}) {
+  final halfSize = size / 2;
+
+  return [
+    LatLng(center.latitude + halfSize, center.longitude + halfSize),
+    LatLng(center.latitude - halfSize, center.longitude + halfSize),
+    LatLng(center.latitude - halfSize, center.longitude - halfSize),
+    LatLng(center.latitude + halfSize, center.longitude - halfSize),
+  ];
+}
+
+Polygon _polygonWithPointsAndHole(PolygonId polygonId) {
+  _rectPoints(size: 1);
+  return Polygon(
+    polygonId: polygonId,
+    points: _rectPoints(size: 1),
+    holes: [_rectPoints(size: 0.5)],
+  );
+}
+
 void main() {
   TestWidgetsFlutterBinding.ensureInitialized();
 
@@ -113,23 +136,6 @@
     expect(platformGoogleMap.polygonsToAdd.isEmpty, true);
   });
 
-  testWidgets("Updating a polygon", (WidgetTester tester) async {
-    final Polygon p1 = Polygon(polygonId: PolygonId("polygon_1"));
-    final Polygon p2 =
-        Polygon(polygonId: PolygonId("polygon_1"), geodesic: true);
-
-    await tester.pumpWidget(_mapWithPolygons(_toSet(p1: p1)));
-    await tester.pumpWidget(_mapWithPolygons(_toSet(p1: p2)));
-
-    final FakePlatformGoogleMap platformGoogleMap =
-        fakePlatformViewsController.lastCreatedView;
-    expect(platformGoogleMap.polygonsToChange.length, 1);
-
-    final Polygon update = platformGoogleMap.polygonsToChange.first;
-    expect(update, equals(p2));
-    expect(update.geodesic, true);
-  });
-
   testWidgets("Mutate a polygon", (WidgetTester tester) async {
     final Polygon p1 = Polygon(
       polygonId: PolygonId("polygon_1"),
@@ -228,4 +234,185 @@
     expect(platformGoogleMap.polygonIdsToRemove.isEmpty, true);
     expect(platformGoogleMap.polygonsToAdd.isEmpty, true);
   });
+
+  testWidgets('Initializing a polygon with points and hole',
+      (WidgetTester tester) async {
+    final Polygon p1 = _polygonWithPointsAndHole(PolygonId("polygon_1"));
+    await tester.pumpWidget(_mapWithPolygons(_toSet(p1: p1)));
+
+    final FakePlatformGoogleMap platformGoogleMap =
+        fakePlatformViewsController.lastCreatedView;
+    expect(platformGoogleMap.polygonsToAdd.length, 1);
+
+    final Polygon initializedPolygon = platformGoogleMap.polygonsToAdd.first;
+    expect(initializedPolygon, equals(p1));
+    expect(platformGoogleMap.polygonIdsToRemove.isEmpty, true);
+    expect(platformGoogleMap.polygonsToChange.isEmpty, true);
+  });
+
+  testWidgets("Adding a polygon with points and hole",
+      (WidgetTester tester) async {
+    final Polygon p1 = Polygon(polygonId: PolygonId("polygon_1"));
+    final Polygon p2 = _polygonWithPointsAndHole(PolygonId("polygon_2"));
+
+    await tester.pumpWidget(_mapWithPolygons(_toSet(p1: p1)));
+    await tester.pumpWidget(_mapWithPolygons(_toSet(p1: p1, p2: p2)));
+
+    final FakePlatformGoogleMap platformGoogleMap =
+        fakePlatformViewsController.lastCreatedView;
+    expect(platformGoogleMap.polygonsToAdd.length, 1);
+
+    final Polygon addedPolygon = platformGoogleMap.polygonsToAdd.first;
+    expect(addedPolygon, equals(p2));
+
+    expect(platformGoogleMap.polygonIdsToRemove.isEmpty, true);
+
+    expect(platformGoogleMap.polygonsToChange.isEmpty, true);
+  });
+
+  testWidgets("Removing a polygon with points and hole",
+      (WidgetTester tester) async {
+    final Polygon p1 = _polygonWithPointsAndHole(PolygonId("polygon_1"));
+
+    await tester.pumpWidget(_mapWithPolygons(_toSet(p1: p1)));
+    await tester.pumpWidget(_mapWithPolygons(null));
+
+    final FakePlatformGoogleMap platformGoogleMap =
+        fakePlatformViewsController.lastCreatedView;
+    expect(platformGoogleMap.polygonIdsToRemove.length, 1);
+    expect(platformGoogleMap.polygonIdsToRemove.first, equals(p1.polygonId));
+
+    expect(platformGoogleMap.polygonsToChange.isEmpty, true);
+    expect(platformGoogleMap.polygonsToAdd.isEmpty, true);
+  });
+
+  testWidgets("Updating a polygon by adding points and hole",
+      (WidgetTester tester) async {
+    final Polygon p1 = Polygon(polygonId: PolygonId("polygon_1"));
+    final Polygon p2 = _polygonWithPointsAndHole(PolygonId("polygon_1"));
+
+    await tester.pumpWidget(_mapWithPolygons(_toSet(p1: p1)));
+    await tester.pumpWidget(_mapWithPolygons(_toSet(p1: p2)));
+
+    final FakePlatformGoogleMap platformGoogleMap =
+        fakePlatformViewsController.lastCreatedView;
+    expect(platformGoogleMap.polygonsToChange.length, 1);
+    expect(platformGoogleMap.polygonsToChange.first, equals(p2));
+
+    expect(platformGoogleMap.polygonIdsToRemove.isEmpty, true);
+    expect(platformGoogleMap.polygonsToAdd.isEmpty, true);
+  });
+
+  testWidgets("Mutate a polygon with points and holes",
+      (WidgetTester tester) async {
+    final Polygon p1 = Polygon(
+      polygonId: PolygonId("polygon_1"),
+      points: _rectPoints(size: 1),
+      holes: [_rectPoints(size: 0.5)],
+    );
+    await tester.pumpWidget(_mapWithPolygons(_toSet(p1: p1)));
+
+    p1.points
+      ..clear()
+      ..addAll(_rectPoints(size: 2));
+    p1.holes
+      ..clear()
+      ..addAll([_rectPoints(size: 1)]);
+    await tester.pumpWidget(_mapWithPolygons(_toSet(p1: p1)));
+
+    final FakePlatformGoogleMap platformGoogleMap =
+        fakePlatformViewsController.lastCreatedView;
+    expect(platformGoogleMap.polygonsToChange.length, 1);
+    expect(platformGoogleMap.polygonsToChange.first, equals(p1));
+
+    expect(platformGoogleMap.polygonIdsToRemove.isEmpty, true);
+    expect(platformGoogleMap.polygonsToAdd.isEmpty, true);
+  });
+
+  testWidgets("Multi Update polygons with points and hole",
+      (WidgetTester tester) async {
+    Polygon p1 = Polygon(polygonId: PolygonId("polygon_1"));
+    Polygon p2 = Polygon(
+      polygonId: PolygonId("polygon_2"),
+      points: _rectPoints(size: 2),
+      holes: [_rectPoints(size: 1)],
+    );
+    final Set<Polygon> prev = _toSet(p1: p1, p2: p2);
+    p1 = Polygon(polygonId: PolygonId("polygon_1"), visible: false);
+    p2 = p2.copyWith(
+      pointsParam: _rectPoints(size: 5),
+      holesParam: [_rectPoints(size: 2)],
+    );
+    final Set<Polygon> cur = _toSet(p1: p1, p2: p2);
+
+    await tester.pumpWidget(_mapWithPolygons(prev));
+    await tester.pumpWidget(_mapWithPolygons(cur));
+
+    final FakePlatformGoogleMap platformGoogleMap =
+        fakePlatformViewsController.lastCreatedView;
+
+    expect(platformGoogleMap.polygonsToChange, cur);
+    expect(platformGoogleMap.polygonIdsToRemove.isEmpty, true);
+    expect(platformGoogleMap.polygonsToAdd.isEmpty, true);
+  });
+
+  testWidgets("Multi Update polygons with points and hole",
+      (WidgetTester tester) async {
+    Polygon p2 = Polygon(
+      polygonId: PolygonId("polygon_2"),
+      points: _rectPoints(size: 2),
+      holes: [_rectPoints(size: 1)],
+    );
+    final Polygon p3 = Polygon(polygonId: PolygonId("polygon_3"));
+    final Set<Polygon> prev = _toSet(p2: p2, p3: p3);
+
+    // p1 is added, p2 is updated, p3 is removed.
+    final Polygon p1 = _polygonWithPointsAndHole(PolygonId("polygon_1"));
+    p2 = p2.copyWith(
+      pointsParam: _rectPoints(size: 5),
+      holesParam: [_rectPoints(size: 3)],
+    );
+    final Set<Polygon> cur = _toSet(p1: p1, p2: p2);
+
+    await tester.pumpWidget(_mapWithPolygons(prev));
+    await tester.pumpWidget(_mapWithPolygons(cur));
+
+    final FakePlatformGoogleMap platformGoogleMap =
+        fakePlatformViewsController.lastCreatedView;
+
+    expect(platformGoogleMap.polygonsToChange.length, 1);
+    expect(platformGoogleMap.polygonsToAdd.length, 1);
+    expect(platformGoogleMap.polygonIdsToRemove.length, 1);
+
+    expect(platformGoogleMap.polygonsToChange.first, equals(p2));
+    expect(platformGoogleMap.polygonsToAdd.first, equals(p1));
+    expect(platformGoogleMap.polygonIdsToRemove.first, equals(p3.polygonId));
+  });
+
+  testWidgets("Partial Update polygons with points and hole",
+      (WidgetTester tester) async {
+    final Polygon p1 = _polygonWithPointsAndHole(PolygonId("polygon_1"));
+    final Polygon p2 = Polygon(polygonId: PolygonId("polygon_2"));
+    Polygon p3 = Polygon(
+      polygonId: PolygonId("polygon_3"),
+      points: _rectPoints(size: 2),
+      holes: [_rectPoints(size: 1)],
+    );
+    final Set<Polygon> prev = _toSet(p1: p1, p2: p2, p3: p3);
+    p3 = p3.copyWith(
+      pointsParam: _rectPoints(size: 5),
+      holesParam: [_rectPoints(size: 3)],
+    );
+    final Set<Polygon> cur = _toSet(p1: p1, p2: p2, p3: p3);
+
+    await tester.pumpWidget(_mapWithPolygons(prev));
+    await tester.pumpWidget(_mapWithPolygons(cur));
+
+    final FakePlatformGoogleMap platformGoogleMap =
+        fakePlatformViewsController.lastCreatedView;
+
+    expect(platformGoogleMap.polygonsToChange, _toSet(p3: p3));
+    expect(platformGoogleMap.polygonIdsToRemove.isEmpty, true);
+    expect(platformGoogleMap.polygonsToAdd.isEmpty, true);
+  });
 }