[google_maps_flutter] Objective-C code clean up (#5780)

diff --git a/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md
index b4fb662..8d78ec3 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 @@
+## 2.1.7
+
+* Objective-C code cleanup.
+
 ## 2.1.6
 
 * Fixes issue in Flutter v3.0.0 where some updates to the map don't take effect on Android.
diff --git a/packages/google_maps_flutter/google_maps_flutter/example/ios/Runner.xcodeproj/project.pbxproj b/packages/google_maps_flutter/google_maps_flutter/example/ios/Runner.xcodeproj/project.pbxproj
index a8d3710..b37246b 100644
--- a/packages/google_maps_flutter/google_maps_flutter/example/ios/Runner.xcodeproj/project.pbxproj
+++ b/packages/google_maps_flutter/google_maps_flutter/example/ios/Runner.xcodeproj/project.pbxproj
@@ -10,6 +10,8 @@
 		1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
 		3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
 		4510D964F3B1259FEDD3ABA6 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7755F8F4BABC3D6A0BD4048B /* libPods-Runner.a */; };
+		6851F3562835BC180032B7C8 /* FLTGoogleMapJSONConversionsConversionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 6851F3552835BC180032B7C8 /* FLTGoogleMapJSONConversionsConversionTests.m */; };
+		68E4726A2836FF0C00BDDDAC /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 68E472692836FF0C00BDDDAC /* MapKit.framework */; };
 		978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; };
 		97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; };
 		97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
@@ -55,6 +57,8 @@
 		1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
 		1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
 		3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
+		6851F3552835BC180032B7C8 /* FLTGoogleMapJSONConversionsConversionTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLTGoogleMapJSONConversionsConversionTests.m; sourceTree = "<group>"; };
+		68E472692836FF0C00BDDDAC /* MapKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MapKit.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.0.sdk/System/iOSSupport/System/Library/Frameworks/MapKit.framework; sourceTree = DEVELOPER_DIR; };
 		733AFAB37683A9DA7512F09C /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
 		7755F8F4BABC3D6A0BD4048B /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; };
 		7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
@@ -95,6 +99,7 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				68E4726A2836FF0C00BDDDAC /* MapKit.framework in Frameworks */,
 				FC8F35FC8CD533B128950487 /* libPods-RunnerTests.a in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
@@ -112,6 +117,7 @@
 		1E7CF0857EFC88FC263CF3B2 /* Frameworks */ = {
 			isa = PBXGroup;
 			children = (
+				68E472692836FF0C00BDDDAC /* MapKit.framework */,
 				7755F8F4BABC3D6A0BD4048B /* libPods-Runner.a */,
 				F267F68029D1A4E2E4C572A7 /* libPods-RunnerTests.a */,
 			);
@@ -190,6 +196,7 @@
 		F7151F11265D7ED70028CB91 /* RunnerTests */ = {
 			isa = PBXGroup;
 			children = (
+				6851F3552835BC180032B7C8 /* FLTGoogleMapJSONConversionsConversionTests.m */,
 				F7151F12265D7ED70028CB91 /* GoogleMapsTests.m */,
 				982F2A6A27BADE17003C81F4 /* PartiallyMockedMapView.h */,
 				982F2A6B27BADE17003C81F4 /* PartiallyMockedMapView.m */,
@@ -446,6 +453,7 @@
 			buildActionMask = 2147483647;
 			files = (
 				F7151F13265D7ED70028CB91 /* GoogleMapsTests.m in Sources */,
+				6851F3562835BC180032B7C8 /* FLTGoogleMapJSONConversionsConversionTests.m in Sources */,
 				982F2A6C27BADE17003C81F4 /* PartiallyMockedMapView.m in Sources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
diff --git a/packages/google_maps_flutter/google_maps_flutter/example/ios/RunnerTests/FLTGoogleMapJSONConversionsConversionTests.m b/packages/google_maps_flutter/google_maps_flutter/example/ios/RunnerTests/FLTGoogleMapJSONConversionsConversionTests.m
new file mode 100644
index 0000000..bf226fe
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter/example/ios/RunnerTests/FLTGoogleMapJSONConversionsConversionTests.m
@@ -0,0 +1,290 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+@import google_maps_flutter;
+@import google_maps_flutter.Test;
+@import XCTest;
+@import MapKit;
+@import GoogleMaps;
+
+#import <OCMock/OCMock.h>
+#import "PartiallyMockedMapView.h"
+
+@interface FLTGoogleMapJSONConversionsTests : XCTestCase
+@end
+
+@implementation FLTGoogleMapJSONConversionsTests
+
+- (void)testLocationFromLatLong {
+  NSArray<NSNumber *> *latlong = @[ @1, @2 ];
+  CLLocationCoordinate2D location = [FLTGoogleMapJSONConversions locationFromLatLong:latlong];
+  XCTAssertEqual(location.latitude, 1);
+  XCTAssertEqual(location.longitude, 2);
+}
+
+- (void)testPointFromArray {
+  NSArray<NSNumber *> *array = @[ @1, @2 ];
+  CGPoint point = [FLTGoogleMapJSONConversions pointFromArray:array];
+  XCTAssertEqual(point.x, 1);
+  XCTAssertEqual(point.y, 2);
+}
+
+- (void)testArrayFromLocation {
+  CLLocationCoordinate2D location = CLLocationCoordinate2DMake(1, 2);
+  NSArray<NSNumber *> *array = [FLTGoogleMapJSONConversions arrayFromLocation:location];
+  XCTAssertEqual([array[0] integerValue], 1);
+  XCTAssertEqual([array[1] integerValue], 2);
+}
+
+- (void)testColorFromRGBA {
+  NSNumber *rgba = @(0x01020304);
+  UIColor *color = [FLTGoogleMapJSONConversions colorFromRGBA:rgba];
+  CGFloat red, green, blue, alpha;
+  BOOL success = [color getRed:&red green:&green blue:&blue alpha:&alpha];
+  XCTAssertTrue(success);
+  const CGFloat accuracy = 0.0001;
+  XCTAssertEqualWithAccuracy(red, 2 / 255.0, accuracy);
+  XCTAssertEqualWithAccuracy(green, 3 / 255.0, accuracy);
+  XCTAssertEqualWithAccuracy(blue, 4 / 255.0, accuracy);
+  XCTAssertEqualWithAccuracy(alpha, 1 / 255.0, accuracy);
+}
+
+- (void)testPointsFromLatLongs {
+  NSArray<NSArray *> *latlongs = @[ @[ @1, @2 ], @[ @(3), @(4) ] ];
+  NSArray<CLLocation *> *locations = [FLTGoogleMapJSONConversions pointsFromLatLongs:latlongs];
+  XCTAssertEqual(locations.count, 2);
+  XCTAssertEqual(locations[0].coordinate.latitude, 1);
+  XCTAssertEqual(locations[0].coordinate.longitude, 2);
+  XCTAssertEqual(locations[1].coordinate.latitude, 3);
+  XCTAssertEqual(locations[1].coordinate.longitude, 4);
+}
+
+- (void)testHolesFromPointsArray {
+  NSArray<NSArray *> *pointsArray =
+      @[ @[ @[ @1, @2 ], @[ @(3), @(4) ] ], @[ @[ @(5), @(6) ], @[ @(7), @(8) ] ] ];
+  NSArray<NSArray<CLLocation *> *> *holes =
+      [FLTGoogleMapJSONConversions holesFromPointsArray:pointsArray];
+  XCTAssertEqual(holes.count, 2);
+  XCTAssertEqual(holes[0][0].coordinate.latitude, 1);
+  XCTAssertEqual(holes[0][0].coordinate.longitude, 2);
+  XCTAssertEqual(holes[0][1].coordinate.latitude, 3);
+  XCTAssertEqual(holes[0][1].coordinate.longitude, 4);
+  XCTAssertEqual(holes[1][0].coordinate.latitude, 5);
+  XCTAssertEqual(holes[1][0].coordinate.longitude, 6);
+  XCTAssertEqual(holes[1][1].coordinate.latitude, 7);
+  XCTAssertEqual(holes[1][1].coordinate.longitude, 8);
+}
+
+- (void)testDictionaryFromPosition {
+  id mockPosition = OCMClassMock([GMSCameraPosition class]);
+  NSValue *locationValue = [NSValue valueWithMKCoordinate:CLLocationCoordinate2DMake(1, 2)];
+  [(GMSCameraPosition *)[[mockPosition stub] andReturnValue:locationValue] target];
+  [[[mockPosition stub] andReturnValue:@(2.0)] zoom];
+  [[[mockPosition stub] andReturnValue:@(3.0)] bearing];
+  [[[mockPosition stub] andReturnValue:@(75.0)] viewingAngle];
+  NSDictionary *dictionary = [FLTGoogleMapJSONConversions dictionaryFromPosition:mockPosition];
+  NSArray *targetArray = @[ @1, @2 ];
+  XCTAssertEqualObjects(dictionary[@"target"], targetArray);
+  XCTAssertEqualObjects(dictionary[@"zoom"], @2.0);
+  XCTAssertEqualObjects(dictionary[@"bearing"], @3.0);
+  XCTAssertEqualObjects(dictionary[@"tilt"], @75.0);
+}
+
+- (void)testDictionaryFromPoint {
+  CGPoint point = CGPointMake(10, 20);
+  NSDictionary *dictionary = [FLTGoogleMapJSONConversions dictionaryFromPoint:point];
+  const CGFloat accuracy = 0.0001;
+  XCTAssertEqualWithAccuracy([dictionary[@"x"] floatValue], point.x, accuracy);
+  XCTAssertEqualWithAccuracy([dictionary[@"y"] floatValue], point.y, accuracy);
+}
+
+- (void)testDictionaryFromCoordinateBounds {
+  XCTAssertNil([FLTGoogleMapJSONConversions dictionaryFromCoordinateBounds:nil]);
+
+  GMSCoordinateBounds *bounds =
+      [[GMSCoordinateBounds alloc] initWithCoordinate:CLLocationCoordinate2DMake(10, 20)
+                                           coordinate:CLLocationCoordinate2DMake(30, 40)];
+  NSDictionary *dictionary = [FLTGoogleMapJSONConversions dictionaryFromCoordinateBounds:bounds];
+  NSArray *southwest = @[ @10, @20 ];
+  NSArray *northeast = @[ @30, @40 ];
+  XCTAssertEqualObjects(dictionary[@"southwest"], southwest);
+  XCTAssertEqualObjects(dictionary[@"northeast"], northeast);
+}
+
+- (void)testCameraPostionFromDictionary {
+  XCTAssertNil([FLTGoogleMapJSONConversions cameraPostionFromDictionary:nil]);
+
+  NSDictionary *channelValue =
+      @{@"target" : @[ @1, @2 ], @"zoom" : @3, @"bearing" : @4, @"tilt" : @5};
+
+  GMSCameraPosition *cameraPosition =
+      [FLTGoogleMapJSONConversions cameraPostionFromDictionary:channelValue];
+
+  const CGFloat accuracy = 0.001;
+  XCTAssertEqualWithAccuracy(cameraPosition.target.latitude, 1, accuracy);
+  XCTAssertEqualWithAccuracy(cameraPosition.target.longitude, 2, accuracy);
+  XCTAssertEqualWithAccuracy(cameraPosition.zoom, 3, accuracy);
+  XCTAssertEqualWithAccuracy(cameraPosition.bearing, 4, accuracy);
+  XCTAssertEqualWithAccuracy(cameraPosition.viewingAngle, 5, accuracy);
+}
+
+- (void)testPointFromDictionary {
+  XCTAssertNil([FLTGoogleMapJSONConversions cameraPostionFromDictionary:nil]);
+
+  NSDictionary *dictionary = @{
+    @"x" : @1,
+    @"y" : @2,
+  };
+
+  CGPoint point = [FLTGoogleMapJSONConversions pointFromDictionary:dictionary];
+
+  const CGFloat accuracy = 0.001;
+  XCTAssertEqualWithAccuracy(point.x, 1, accuracy);
+  XCTAssertEqualWithAccuracy(point.y, 2, accuracy);
+}
+
+- (void)testCoordinateBoundsFromLatLongs {
+  NSArray<NSNumber *> *latlong1 = @[ @1, @2 ];
+  NSArray<NSNumber *> *latlong2 = @[ @(3), @(4) ];
+
+  GMSCoordinateBounds *bounds =
+      [FLTGoogleMapJSONConversions coordinateBoundsFromLatLongs:@[ latlong1, latlong2 ]];
+
+  const CGFloat accuracy = 0.001;
+  XCTAssertEqualWithAccuracy(bounds.southWest.latitude, 1, accuracy);
+  XCTAssertEqualWithAccuracy(bounds.southWest.longitude, 2, accuracy);
+  XCTAssertEqualWithAccuracy(bounds.northEast.latitude, 3, accuracy);
+  XCTAssertEqualWithAccuracy(bounds.northEast.longitude, 4, accuracy);
+}
+
+- (void)testMapViewTypeFromTypeValue {
+  XCTAssertEqual(kGMSTypeNormal, [FLTGoogleMapJSONConversions mapViewTypeFromTypeValue:@1]);
+  XCTAssertEqual(kGMSTypeSatellite, [FLTGoogleMapJSONConversions mapViewTypeFromTypeValue:@2]);
+  XCTAssertEqual(kGMSTypeTerrain, [FLTGoogleMapJSONConversions mapViewTypeFromTypeValue:@3]);
+  XCTAssertEqual(kGMSTypeHybrid, [FLTGoogleMapJSONConversions mapViewTypeFromTypeValue:@4]);
+  XCTAssertEqual(kGMSTypeNone, [FLTGoogleMapJSONConversions mapViewTypeFromTypeValue:@5]);
+}
+
+- (void)testCameraUpdateFromChannelValueNewCameraPosition {
+  NSArray *channelValue = @[
+    @"newCameraPosition", @{@"target" : @[ @1, @2 ], @"zoom" : @3, @"bearing" : @4, @"tilt" : @5}
+  ];
+  id classMockCameraUpdate = OCMClassMock([GMSCameraUpdate class]);
+  [FLTGoogleMapJSONConversions cameraUpdateFromChannelValue:channelValue];
+  [[classMockCameraUpdate expect]
+      setCamera:[FLTGoogleMapJSONConversions cameraPostionFromDictionary:channelValue[1]]];
+  [classMockCameraUpdate stopMocking];
+}
+
+// TODO(cyanglaz): Fix the test for CameraUpdateFromChannelValue with the "NewLatlng" key.
+// 2 approaches have been tried and neither worked for the tests.
+//
+// 1. Use OCMock to vefiry that [GMSCameraUpdate setTarget:] is triggered with the correct value.
+// This class method conflicts with certain category method in OCMock, causing OCMock not able to
+// disambigious them.
+//
+// 2. Directly verify the GMSCameraUpdate object returned by the method.
+// The GMSCameraUpdate object returned from the method doesn't have any accessors to the "target"
+// property. It can be used to update the "camera" property in GMSMapView. However, [GMSMapView
+// moveCamera:] doesn't update the camera immediately. Thus the GMSCameraUpdate object cannot be
+// verified.
+//
+// The code in below test uses the 2nd approach.
+- (void)skip_testCameraUpdateFromChannelValueNewLatLong {
+  NSArray *channelValue = @[ @"newLatLng", @[ @1, @2 ] ];
+
+  GMSCameraUpdate *update = [FLTGoogleMapJSONConversions cameraUpdateFromChannelValue:channelValue];
+
+  GMSMapView *mapView = [[GMSMapView alloc]
+      initWithFrame:CGRectZero
+             camera:[GMSCameraPosition cameraWithTarget:CLLocationCoordinate2DMake(5, 6) zoom:1]];
+  [mapView moveCamera:update];
+  const CGFloat accuracy = 0.001;
+  XCTAssertEqualWithAccuracy(mapView.camera.target.latitude, 1,
+                             accuracy);  // mapView.camera.target.latitude is still 5.
+  XCTAssertEqualWithAccuracy(mapView.camera.target.longitude, 2,
+                             accuracy);  // mapView.camera.target.longitude is still 6.
+}
+
+- (void)testCameraUpdateFromChannelValueNewLatLngBounds {
+  NSArray<NSNumber *> *latlong1 = @[ @1, @2 ];
+  NSArray<NSNumber *> *latlong2 = @[ @(3), @(4) ];
+  GMSCoordinateBounds *bounds =
+      [FLTGoogleMapJSONConversions coordinateBoundsFromLatLongs:@[ latlong1, latlong2 ]];
+
+  NSArray *channelValue = @[ @"newLatLngBounds", @[ latlong1, latlong2 ], @20 ];
+  id classMockCameraUpdate = OCMClassMock([GMSCameraUpdate class]);
+  [FLTGoogleMapJSONConversions cameraUpdateFromChannelValue:channelValue];
+
+  [[classMockCameraUpdate expect] fitBounds:bounds withPadding:20];
+  [classMockCameraUpdate stopMocking];
+}
+
+- (void)testCameraUpdateFromChannelValueNewLatLngZoom {
+  NSArray *channelValue = @[ @"newLatLngZoom", @[ @1, @2 ], @3 ];
+
+  id classMockCameraUpdate = OCMClassMock([GMSCameraUpdate class]);
+  [FLTGoogleMapJSONConversions cameraUpdateFromChannelValue:channelValue];
+
+  [[classMockCameraUpdate expect] setTarget:CLLocationCoordinate2DMake(1, 2) zoom:3];
+  [classMockCameraUpdate stopMocking];
+}
+
+- (void)testCameraUpdateFromChannelValueScrollBy {
+  NSArray *channelValue = @[ @"scrollBy", @1, @2 ];
+
+  id classMockCameraUpdate = OCMClassMock([GMSCameraUpdate class]);
+  [FLTGoogleMapJSONConversions cameraUpdateFromChannelValue:channelValue];
+
+  [[classMockCameraUpdate expect] scrollByX:1 Y:2];
+  [classMockCameraUpdate stopMocking];
+}
+
+- (void)testCameraUpdateFromChannelValueZoomBy {
+  NSArray *channelValueNoPoint = @[ @"zoomBy", @1 ];
+
+  id classMockCameraUpdate = OCMClassMock([GMSCameraUpdate class]);
+  [FLTGoogleMapJSONConversions cameraUpdateFromChannelValue:channelValueNoPoint];
+
+  [[classMockCameraUpdate expect] zoomBy:1];
+
+  NSArray *channelValueWithPoint = @[ @"zoomBy", @1, @[ @2, @3 ] ];
+
+  [FLTGoogleMapJSONConversions cameraUpdateFromChannelValue:channelValueWithPoint];
+
+  [[classMockCameraUpdate expect] zoomBy:1 atPoint:CGPointMake(2, 3)];
+  [classMockCameraUpdate stopMocking];
+}
+
+- (void)testCameraUpdateFromChannelValueZoomIn {
+  NSArray *channelValueNoPoint = @[ @"zoomIn" ];
+
+  id classMockCameraUpdate = OCMClassMock([GMSCameraUpdate class]);
+  [FLTGoogleMapJSONConversions cameraUpdateFromChannelValue:channelValueNoPoint];
+
+  [[classMockCameraUpdate expect] zoomIn];
+  [classMockCameraUpdate stopMocking];
+}
+
+- (void)testCameraUpdateFromChannelValueZoomOut {
+  NSArray *channelValueNoPoint = @[ @"zoomOut" ];
+
+  id classMockCameraUpdate = OCMClassMock([GMSCameraUpdate class]);
+  [FLTGoogleMapJSONConversions cameraUpdateFromChannelValue:channelValueNoPoint];
+
+  [[classMockCameraUpdate expect] zoomOut];
+  [classMockCameraUpdate stopMocking];
+}
+
+- (void)testCameraUpdateFromChannelValueZoomTo {
+  NSArray *channelValueNoPoint = @[ @"zoomTo", @1 ];
+
+  id classMockCameraUpdate = OCMClassMock([GMSCameraUpdate class]);
+  [FLTGoogleMapJSONConversions cameraUpdateFromChannelValue:channelValueNoPoint];
+
+  [[classMockCameraUpdate expect] zoomTo:1];
+  [classMockCameraUpdate stopMocking];
+}
+
+@end
diff --git a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/FLTGoogleMapJSONConversions.h b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/FLTGoogleMapJSONConversions.h
new file mode 100644
index 0000000..cfccb7b
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/FLTGoogleMapJSONConversions.h
@@ -0,0 +1,30 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import <Flutter/Flutter.h>
+#import <GoogleMaps/GoogleMaps.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface FLTGoogleMapJSONConversions : NSObject
+
++ (CLLocationCoordinate2D)locationFromLatLong:(NSArray *)latlong;
++ (CGPoint)pointFromArray:(NSArray *)array;
++ (NSArray *)arrayFromLocation:(CLLocationCoordinate2D)location;
++ (UIColor *)colorFromRGBA:(NSNumber *)data;
++ (NSArray<CLLocation *> *)pointsFromLatLongs:(NSArray *)data;
++ (NSArray<NSArray<CLLocation *> *> *)holesFromPointsArray:(NSArray *)data;
++ (nullable NSDictionary<NSString *, id> *)dictionaryFromPosition:
+    (nullable GMSCameraPosition *)position;
++ (NSDictionary<NSString *, NSNumber *> *)dictionaryFromPoint:(CGPoint)point;
++ (nullable NSDictionary *)dictionaryFromCoordinateBounds:(nullable GMSCoordinateBounds *)bounds;
++ (nullable GMSCameraPosition *)cameraPostionFromDictionary:(nullable NSDictionary *)channelValue;
++ (CGPoint)pointFromDictionary:(NSDictionary *)dictionary;
++ (GMSCoordinateBounds *)coordinateBoundsFromLatLongs:(NSArray *)latlongs;
++ (GMSMapViewType)mapViewTypeFromTypeValue:(NSNumber *)value;
++ (nullable GMSCameraUpdate *)cameraUpdateFromChannelValue:(NSArray *)channelValue;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/FLTGoogleMapJSONConversions.m b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/FLTGoogleMapJSONConversions.m
new file mode 100644
index 0000000..d554c50
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/FLTGoogleMapJSONConversions.m
@@ -0,0 +1,144 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#import "FLTGoogleMapJSONConversions.h"
+
+@implementation FLTGoogleMapJSONConversions
+
++ (CLLocationCoordinate2D)locationFromLatLong:(NSArray *)latlong {
+  return CLLocationCoordinate2DMake([latlong[0] doubleValue], [latlong[1] doubleValue]);
+}
+
++ (CGPoint)pointFromArray:(NSArray *)array {
+  return CGPointMake([array[0] doubleValue], [array[1] doubleValue]);
+}
+
++ (NSArray *)arrayFromLocation:(CLLocationCoordinate2D)location {
+  return @[ @(location.latitude), @(location.longitude) ];
+}
+
++ (UIColor *)colorFromRGBA:(NSNumber *)numberColor {
+  unsigned long value = [numberColor unsignedLongValue];
+  return [UIColor colorWithRed:((float)((value & 0xFF0000) >> 16)) / 255.0
+                         green:((float)((value & 0xFF00) >> 8)) / 255.0
+                          blue:((float)(value & 0xFF)) / 255.0
+                         alpha:((float)((value & 0xFF000000) >> 24)) / 255.0];
+}
+
++ (NSArray<CLLocation *> *)pointsFromLatLongs:(NSArray *)data {
+  NSMutableArray *points = [[NSMutableArray alloc] init];
+  for (unsigned i = 0; i < [data count]; i++) {
+    NSNumber *latitude = data[i][0];
+    NSNumber *longitude = data[i][1];
+    CLLocation *point = [[CLLocation alloc] initWithLatitude:[latitude doubleValue]
+                                                   longitude:[longitude doubleValue]];
+    [points addObject:point];
+  }
+
+  return points;
+}
+
++ (NSArray<NSArray<CLLocation *> *> *)holesFromPointsArray:(NSArray *)data {
+  NSMutableArray<NSArray<CLLocation *> *> *holes = [[[NSMutableArray alloc] init] init];
+  for (unsigned i = 0; i < [data count]; i++) {
+    NSArray<CLLocation *> *points = [FLTGoogleMapJSONConversions pointsFromLatLongs:data[i]];
+    [holes addObject:points];
+  }
+
+  return holes;
+}
+
++ (nullable NSDictionary<NSString *, id> *)dictionaryFromPosition:(GMSCameraPosition *)position {
+  if (!position) {
+    return nil;
+  }
+  return @{
+    @"target" : [FLTGoogleMapJSONConversions arrayFromLocation:[position target]],
+    @"zoom" : @([position zoom]),
+    @"bearing" : @([position bearing]),
+    @"tilt" : @([position viewingAngle]),
+  };
+}
+
++ (NSDictionary<NSString *, NSNumber *> *)dictionaryFromPoint:(CGPoint)point {
+  return @{
+    @"x" : @(lroundf(point.x)),
+    @"y" : @(lroundf(point.y)),
+  };
+}
+
++ (nullable NSDictionary *)dictionaryFromCoordinateBounds:(GMSCoordinateBounds *)bounds {
+  if (!bounds) {
+    return nil;
+  }
+  return @{
+    @"southwest" : [FLTGoogleMapJSONConversions arrayFromLocation:[bounds southWest]],
+    @"northeast" : [FLTGoogleMapJSONConversions arrayFromLocation:[bounds northEast]],
+  };
+}
+
++ (nullable GMSCameraPosition *)cameraPostionFromDictionary:(nullable NSDictionary *)data {
+  if (!data) {
+    return nil;
+  }
+  return [GMSCameraPosition
+      cameraWithTarget:[FLTGoogleMapJSONConversions locationFromLatLong:data[@"target"]]
+                  zoom:[data[@"zoom"] floatValue]
+               bearing:[data[@"bearing"] doubleValue]
+          viewingAngle:[data[@"tilt"] doubleValue]];
+}
+
++ (CGPoint)pointFromDictionary:(NSDictionary *)dictionary {
+  double x = [dictionary[@"x"] doubleValue];
+  double y = [dictionary[@"y"] doubleValue];
+  return CGPointMake(x, y);
+}
+
++ (GMSCoordinateBounds *)coordinateBoundsFromLatLongs:(NSArray *)latlongs {
+  return [[GMSCoordinateBounds alloc]
+      initWithCoordinate:[FLTGoogleMapJSONConversions locationFromLatLong:latlongs[0]]
+              coordinate:[FLTGoogleMapJSONConversions locationFromLatLong:latlongs[1]]];
+}
+
++ (GMSMapViewType)mapViewTypeFromTypeValue:(NSNumber *)typeValue {
+  int value = [typeValue intValue];
+  return (GMSMapViewType)(value == 0 ? 5 : value);
+}
+
++ (nullable GMSCameraUpdate *)cameraUpdateFromChannelValue:(NSArray *)channelValue {
+  NSString *update = channelValue[0];
+  if ([update isEqualToString:@"newCameraPosition"]) {
+    return [GMSCameraUpdate
+        setCamera:[FLTGoogleMapJSONConversions cameraPostionFromDictionary:channelValue[1]]];
+  } else if ([update isEqualToString:@"newLatLng"]) {
+    return [GMSCameraUpdate
+        setTarget:[FLTGoogleMapJSONConversions locationFromLatLong:channelValue[1]]];
+  } else if ([update isEqualToString:@"newLatLngBounds"]) {
+    return [GMSCameraUpdate
+          fitBounds:[FLTGoogleMapJSONConversions coordinateBoundsFromLatLongs:channelValue[1]]
+        withPadding:[channelValue[2] doubleValue]];
+  } else if ([update isEqualToString:@"newLatLngZoom"]) {
+    return
+        [GMSCameraUpdate setTarget:[FLTGoogleMapJSONConversions locationFromLatLong:channelValue[1]]
+                              zoom:[channelValue[2] floatValue]];
+  } else if ([update isEqualToString:@"scrollBy"]) {
+    return [GMSCameraUpdate scrollByX:[channelValue[1] doubleValue]
+                                    Y:[channelValue[2] doubleValue]];
+  } else if ([update isEqualToString:@"zoomBy"]) {
+    if (channelValue.count == 2) {
+      return [GMSCameraUpdate zoomBy:[channelValue[1] floatValue]];
+    } else {
+      return [GMSCameraUpdate zoomBy:[channelValue[1] floatValue]
+                             atPoint:[FLTGoogleMapJSONConversions pointFromArray:channelValue[2]]];
+    }
+  } else if ([update isEqualToString:@"zoomIn"]) {
+    return [GMSCameraUpdate zoomIn];
+  } else if ([update isEqualToString:@"zoomOut"]) {
+    return [GMSCameraUpdate zoomOut];
+  } else if ([update isEqualToString:@"zoomTo"]) {
+    return [GMSCameraUpdate zoomTo:[channelValue[1] floatValue]];
+  }
+  return nil;
+}
+@end
diff --git a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/FLTGoogleMapTileOverlayController.h b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/FLTGoogleMapTileOverlayController.h
index 356a13f..5dcc665 100644
--- a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/FLTGoogleMapTileOverlayController.h
+++ b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/FLTGoogleMapTileOverlayController.h
@@ -7,25 +7,19 @@
 
 NS_ASSUME_NONNULL_BEGIN
 
-// Defines map UI options writable from Flutter.
-@protocol FLTGoogleMapTileOverlayOptionsSink
-- (void)setFadeIn:(BOOL)fadeIn;
-- (void)setTransparency:(float)transparency;
-- (void)setZIndex:(int)zIndex;
-- (void)setVisible:(BOOL)visible;
-- (void)setTileSize:(NSInteger)tileSize;
-@end
-
-@interface FLTGoogleMapTileOverlayController : NSObject <FLTGoogleMapTileOverlayOptionsSink>
-- (instancetype)initWithTileLayer:(GMSTileLayer *)tileLayer mapView:(GMSMapView *)mapView;
+@interface FLTGoogleMapTileOverlayController : NSObject
+- (instancetype)initWithTileLayer:(GMSTileLayer *)tileLayer
+                          mapView:(GMSMapView *)mapView
+                          options:(NSDictionary *)optionsData;
 - (void)removeTileOverlay;
 - (void)clearTileCache;
 - (NSDictionary *)getTileOverlayInfo;
 @end
 
 @interface FLTTileProviderController : GMSTileLayer
-@property(copy, nonatomic, readonly) NSString *tileOverlayId;
-- (instancetype)init:(FlutterMethodChannel *)methodChannel tileOverlayId:(NSString *)tileOverlayId;
+@property(copy, nonatomic, readonly) NSString *tileOverlayIdentifier;
+- (instancetype)init:(FlutterMethodChannel *)methodChannel
+    withTileOverlayIdentifier:(NSString *)identifier;
 @end
 
 @interface FLTTileOverlaysController : NSObject
@@ -34,9 +28,9 @@
            registrar:(NSObject<FlutterPluginRegistrar> *)registrar;
 - (void)addTileOverlays:(NSArray *)tileOverlaysToAdd;
 - (void)changeTileOverlays:(NSArray *)tileOverlaysToChange;
-- (void)removeTileOverlayIds:(NSArray *)tileOverlayIdsToRemove;
-- (void)clearTileCache:(NSString *)tileOverlayId;
-- (nullable NSDictionary *)getTileOverlayInfo:(NSString *)tileverlayId;
+- (void)removeTileOverlayWithIdentifiers:(NSArray *)identifiers;
+- (void)clearTileCacheWithIdentifier:(NSString *)identifier;
+- (nullable NSDictionary *)tileOverlayInfoWithIdentifier:(NSString *)identifier;
 @end
 
 NS_ASSUME_NONNULL_END
diff --git a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/FLTGoogleMapTileOverlayController.m b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/FLTGoogleMapTileOverlayController.m
index 6baa753..5863697 100644
--- a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/FLTGoogleMapTileOverlayController.m
+++ b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/FLTGoogleMapTileOverlayController.m
@@ -3,36 +3,7 @@
 // found in the LICENSE file.
 
 #import "FLTGoogleMapTileOverlayController.h"
-#import "JsonConversions.h"
-
-static void InterpretTileOverlayOptions(NSDictionary *data,
-                                        id<FLTGoogleMapTileOverlayOptionsSink> sink,
-                                        NSObject<FlutterPluginRegistrar> *registrar) {
-  NSNumber *visible = data[@"visible"];
-  if (visible != nil) {
-    [sink setVisible:visible.boolValue];
-  }
-
-  NSNumber *transparency = data[@"transparency"];
-  if (transparency != nil) {
-    [sink setTransparency:transparency.floatValue];
-  }
-
-  NSNumber *zIndex = data[@"zIndex"];
-  if (zIndex != nil) {
-    [sink setZIndex:zIndex.intValue];
-  }
-
-  NSNumber *fadeIn = data[@"fadeIn"];
-  if (fadeIn != nil) {
-    [sink setFadeIn:fadeIn.boolValue];
-  }
-
-  NSNumber *tileSize = data[@"tileSize"];
-  if (tileSize != nil) {
-    [sink setTileSize:tileSize.integerValue];
-  }
-}
+#import "FLTGoogleMapJSONConversions.h"
 
 @interface FLTGoogleMapTileOverlayController ()
 
@@ -43,11 +14,14 @@
 
 @implementation FLTGoogleMapTileOverlayController
 
-- (instancetype)initWithTileLayer:(GMSTileLayer *)tileLayer mapView:(GMSMapView *)mapView {
+- (instancetype)initWithTileLayer:(GMSTileLayer *)tileLayer
+                          mapView:(GMSMapView *)mapView
+                          options:(NSDictionary *)optionsData {
   self = [super init];
   if (self) {
-    self.layer = tileLayer;
-    self.mapView = mapView;
+    _layer = tileLayer;
+    _mapView = mapView;
+    [self interpretTileOverlayOptions:optionsData];
   }
   return self;
 }
@@ -71,8 +45,6 @@
   return info;
 }
 
-#pragma mark - FLTGoogleMapTileOverlayOptionsSink methods
-
 - (void)setFadeIn:(BOOL)fadeIn {
   self.layer.fadeIn = fadeIn;
 }
@@ -93,22 +65,53 @@
 - (void)setTileSize:(NSInteger)tileSize {
   self.layer.tileSize = tileSize;
 }
+
+- (void)interpretTileOverlayOptions:(NSDictionary *)data {
+  if (!data) {
+    return;
+  }
+  NSNumber *visible = data[@"visible"];
+  if (visible != nil && visible != (id)[NSNull null]) {
+    [self setVisible:visible.boolValue];
+  }
+
+  NSNumber *transparency = data[@"transparency"];
+  if (transparency != nil && transparency != (id)[NSNull null]) {
+    [self setTransparency:transparency.floatValue];
+  }
+
+  NSNumber *zIndex = data[@"zIndex"];
+  if (zIndex != nil && zIndex != (id)[NSNull null]) {
+    [self setZIndex:zIndex.intValue];
+  }
+
+  NSNumber *fadeIn = data[@"fadeIn"];
+  if (fadeIn != nil && fadeIn != (id)[NSNull null]) {
+    [self setFadeIn:fadeIn.boolValue];
+  }
+
+  NSNumber *tileSize = data[@"tileSize"];
+  if (tileSize != nil && tileSize != (id)[NSNull null]) {
+    [self setTileSize:tileSize.integerValue];
+  }
+}
+
 @end
 
 @interface FLTTileProviderController ()
 
-@property(weak, nonatomic) FlutterMethodChannel *methodChannel;
-@property(copy, nonatomic, readwrite) NSString *tileOverlayId;
+@property(strong, nonatomic) FlutterMethodChannel *methodChannel;
 
 @end
 
 @implementation FLTTileProviderController
 
-- (instancetype)init:(FlutterMethodChannel *)methodChannel tileOverlayId:(NSString *)tileOverlayId {
+- (instancetype)init:(FlutterMethodChannel *)methodChannel
+    withTileOverlayIdentifier:(NSString *)identifier {
   self = [super init];
   if (self) {
-    self.methodChannel = methodChannel;
-    self.tileOverlayId = tileOverlayId;
+    _methodChannel = methodChannel;
+    _tileOverlayIdentifier = identifier;
   }
   return self;
 }
@@ -122,7 +125,7 @@
   [self.methodChannel
       invokeMethod:@"tileOverlay#getTile"
          arguments:@{
-           @"tileOverlayId" : self.tileOverlayId,
+           @"tileOverlayId" : self.tileOverlayIdentifier,
            @"x" : @(x),
            @"y" : @(y),
            @"zoom" : @(zoom)
@@ -156,9 +159,8 @@
 
 @interface FLTTileOverlaysController ()
 
-@property(strong, nonatomic) NSMutableDictionary *tileOverlayIdToController;
-@property(weak, nonatomic) FlutterMethodChannel *methodChannel;
-@property(weak, nonatomic) NSObject<FlutterPluginRegistrar> *registrar;
+@property(strong, nonatomic) NSMutableDictionary *tileOverlayIdentifierToController;
+@property(strong, nonatomic) FlutterMethodChannel *methodChannel;
 @property(weak, nonatomic) GMSMapView *mapView;
 
 @end
@@ -170,64 +172,67 @@
            registrar:(NSObject<FlutterPluginRegistrar> *)registrar {
   self = [super init];
   if (self) {
-    self.methodChannel = methodChannel;
-    self.mapView = mapView;
-    self.tileOverlayIdToController = [[NSMutableDictionary alloc] init];
-    self.registrar = registrar;
+    _methodChannel = methodChannel;
+    _mapView = mapView;
+    _tileOverlayIdentifierToController = [[NSMutableDictionary alloc] init];
   }
   return self;
 }
 
 - (void)addTileOverlays:(NSArray *)tileOverlaysToAdd {
   for (NSDictionary *tileOverlay in tileOverlaysToAdd) {
-    NSString *tileOverlayId = [FLTTileOverlaysController getTileOverlayId:tileOverlay];
+    NSString *identifier = [FLTTileOverlaysController identifierForTileOverlay:tileOverlay];
     FLTTileProviderController *tileProvider =
-        [[FLTTileProviderController alloc] init:self.methodChannel tileOverlayId:tileOverlayId];
+        [[FLTTileProviderController alloc] init:self.methodChannel
+                      withTileOverlayIdentifier:identifier];
     FLTGoogleMapTileOverlayController *controller =
         [[FLTGoogleMapTileOverlayController alloc] initWithTileLayer:tileProvider
-                                                             mapView:self.mapView];
-    InterpretTileOverlayOptions(tileOverlay, controller, self.registrar);
-    self.tileOverlayIdToController[tileOverlayId] = controller;
+                                                             mapView:self.mapView
+                                                             options:tileOverlay];
+    self.tileOverlayIdentifierToController[identifier] = controller;
   }
 }
 
 - (void)changeTileOverlays:(NSArray *)tileOverlaysToChange {
   for (NSDictionary *tileOverlay in tileOverlaysToChange) {
-    NSString *tileOverlayId = [FLTTileOverlaysController getTileOverlayId:tileOverlay];
-    FLTGoogleMapTileOverlayController *controller = self.tileOverlayIdToController[tileOverlayId];
+    NSString *identifier = [FLTTileOverlaysController identifierForTileOverlay:tileOverlay];
+    FLTGoogleMapTileOverlayController *controller =
+        self.tileOverlayIdentifierToController[identifier];
     if (!controller) {
       continue;
     }
-    InterpretTileOverlayOptions(tileOverlay, controller, self.registrar);
+    [controller interpretTileOverlayOptions:tileOverlay];
   }
 }
-- (void)removeTileOverlayIds:(NSArray *)tileOverlayIdsToRemove {
-  for (NSString *tileOverlayId in tileOverlayIdsToRemove) {
-    FLTGoogleMapTileOverlayController *controller = self.tileOverlayIdToController[tileOverlayId];
+- (void)removeTileOverlayWithIdentifiers:(NSArray *)identifiers {
+  for (NSString *identifier in identifiers) {
+    FLTGoogleMapTileOverlayController *controller =
+        self.tileOverlayIdentifierToController[identifier];
     if (!controller) {
       continue;
     }
     [controller removeTileOverlay];
-    [self.tileOverlayIdToController removeObjectForKey:tileOverlayId];
+    [self.tileOverlayIdentifierToController removeObjectForKey:identifier];
   }
 }
 
-- (void)clearTileCache:(NSString *)tileOverlayId {
-  FLTGoogleMapTileOverlayController *controller = self.tileOverlayIdToController[tileOverlayId];
+- (void)clearTileCacheWithIdentifier:(NSString *)identifier {
+  FLTGoogleMapTileOverlayController *controller =
+      self.tileOverlayIdentifierToController[identifier];
   if (!controller) {
     return;
   }
   [controller clearTileCache];
 }
 
-- (nullable NSDictionary *)getTileOverlayInfo:(NSString *)tileverlayId {
-  if (self.tileOverlayIdToController[tileverlayId] == nil) {
+- (nullable NSDictionary *)tileOverlayInfoWithIdentifier:(NSString *)identifier {
+  if (self.tileOverlayIdentifierToController[identifier] == nil) {
     return nil;
   }
-  return [self.tileOverlayIdToController[tileverlayId] getTileOverlayInfo];
+  return [self.tileOverlayIdentifierToController[identifier] getTileOverlayInfo];
 }
 
-+ (NSString *)getTileOverlayId:(NSDictionary *)tileOverlay {
++ (NSString *)identifierForTileOverlay:(NSDictionary *)tileOverlay {
   return tileOverlay[@"tileOverlayId"];
 }
 
diff --git a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/FLTGoogleMapsPlugin.h b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/FLTGoogleMapsPlugin.h
index 953c055..26f69ea 100644
--- a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/FLTGoogleMapsPlugin.h
+++ b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/FLTGoogleMapsPlugin.h
@@ -10,5 +10,9 @@
 #import "GoogleMapPolygonController.h"
 #import "GoogleMapPolylineController.h"
 
+NS_ASSUME_NONNULL_BEGIN
+
 @interface FLTGoogleMapsPlugin : NSObject <FlutterPlugin>
 @end
+
+NS_ASSUME_NONNULL_END
diff --git a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/FLTGoogleMapsPlugin.m b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/FLTGoogleMapsPlugin.m
index e78a505..d62f19c 100644
--- a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/FLTGoogleMapsPlugin.m
+++ b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/FLTGoogleMapsPlugin.m
@@ -6,11 +6,7 @@
 
 #pragma mark - GoogleMaps plugin implementation
 
-@implementation FLTGoogleMapsPlugin {
-  NSObject<FlutterPluginRegistrar> *_registrar;
-  FlutterMethodChannel *_channel;
-  NSMutableDictionary *_mapControllers;
-}
+@implementation FLTGoogleMapsPlugin
 
 + (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar {
   FLTGoogleMapFactory *googleMapFactory = [[FLTGoogleMapFactory alloc] initWithRegistrar:registrar];
@@ -20,12 +16,4 @@
           FlutterPlatformViewGestureRecognizersBlockingPolicyWaitUntilTouchesEnded];
 }
 
-- (FLTGoogleMapController *)mapFromCall:(FlutterMethodCall *)call error:(FlutterError **)error {
-  id mapId = call.arguments[@"map"];
-  FLTGoogleMapController *controller = _mapControllers[mapId];
-  if (!controller && error) {
-    *error = [FlutterError errorWithCode:@"unknown_map" message:nil details:mapId];
-  }
-  return controller;
-}
 @end
diff --git a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapCircleController.h b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapCircleController.h
index 26b7ce5..6b67760 100644
--- a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapCircleController.h
+++ b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapCircleController.h
@@ -5,25 +5,13 @@
 #import <Flutter/Flutter.h>
 #import <GoogleMaps/GoogleMaps.h>
 
-// Defines circle UI options writable from Flutter.
-@protocol FLTGoogleMapCircleOptionsSink
-- (void)setConsumeTapEvents:(BOOL)consume;
-- (void)setVisible:(BOOL)visible;
-- (void)setStrokeColor:(UIColor *)color;
-- (void)setStrokeWidth:(CGFloat)width;
-- (void)setFillColor:(UIColor *)color;
-- (void)setCenter:(CLLocationCoordinate2D)center;
-- (void)setRadius:(CLLocationDistance)radius;
-- (void)setZIndex:(int)zIndex;
-@end
-
 // Defines circle controllable by Flutter.
-@interface FLTGoogleMapCircleController : NSObject <FLTGoogleMapCircleOptionsSink>
-@property(atomic, readonly) NSString *circleId;
+@interface FLTGoogleMapCircleController : NSObject
 - (instancetype)initCircleWithPosition:(CLLocationCoordinate2D)position
                                 radius:(CLLocationDistance)radius
-                              circleId:(NSString *)circleId
-                               mapView:(GMSMapView *)mapView;
+                              circleId:(NSString *)circleIdentifier
+                               mapView:(GMSMapView *)mapView
+                               options:(NSDictionary *)options;
 - (void)removeCircle;
 @end
 
@@ -33,7 +21,7 @@
            registrar:(NSObject<FlutterPluginRegistrar> *)registrar;
 - (void)addCircles:(NSArray *)circlesToAdd;
 - (void)changeCircles:(NSArray *)circlesToChange;
-- (void)removeCircleIds:(NSArray *)circleIdsToRemove;
-- (void)onCircleTap:(NSString *)circleId;
-- (bool)hasCircleWithId:(NSString *)circleId;
+- (void)removeCircleWithIdentifiers:(NSArray *)identifiers;
+- (void)didTapCircleWithIdentifier:(NSString *)identifier;
+- (bool)hasCircleWithIdentifier:(NSString *)identifier;
 @end
diff --git a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapCircleController.m b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapCircleController.m
index d97de58..53bf690 100644
--- a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapCircleController.m
+++ b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapCircleController.m
@@ -3,122 +3,116 @@
 // found in the LICENSE file.
 
 #import "GoogleMapCircleController.h"
-#import "JsonConversions.h"
+#import "FLTGoogleMapJSONConversions.h"
 
-@implementation FLTGoogleMapCircleController {
-  GMSCircle *_circle;
-  GMSMapView *_mapView;
-}
+@interface FLTGoogleMapCircleController ()
+
+@property(nonatomic, strong) GMSCircle *circle;
+@property(nonatomic, weak) GMSMapView *mapView;
+
+@end
+
+@implementation FLTGoogleMapCircleController
+
 - (instancetype)initCircleWithPosition:(CLLocationCoordinate2D)position
                                 radius:(CLLocationDistance)radius
-                              circleId:(NSString *)circleId
-                               mapView:(GMSMapView *)mapView {
+                              circleId:(NSString *)circleIdentifier
+                               mapView:(GMSMapView *)mapView
+                               options:(NSDictionary *)options {
   self = [super init];
   if (self) {
     _circle = [GMSCircle circleWithPosition:position radius:radius];
     _mapView = mapView;
-    _circleId = circleId;
-    _circle.userData = @[ circleId ];
+    _circle.userData = @[ circleIdentifier ];
+    [self interpretCircleOptions:options];
   }
   return self;
 }
 
 - (void)removeCircle {
-  _circle.map = nil;
+  self.circle.map = nil;
 }
 
-#pragma mark - FLTGoogleMapCircleOptionsSink methods
-
 - (void)setConsumeTapEvents:(BOOL)consumes {
-  _circle.tappable = consumes;
+  self.circle.tappable = consumes;
 }
 - (void)setVisible:(BOOL)visible {
-  _circle.map = visible ? _mapView : nil;
+  self.circle.map = visible ? self.mapView : nil;
 }
 - (void)setZIndex:(int)zIndex {
-  _circle.zIndex = zIndex;
+  self.circle.zIndex = zIndex;
 }
 - (void)setCenter:(CLLocationCoordinate2D)center {
-  _circle.position = center;
+  self.circle.position = center;
 }
 - (void)setRadius:(CLLocationDistance)radius {
-  _circle.radius = radius;
+  self.circle.radius = radius;
 }
 
 - (void)setStrokeColor:(UIColor *)color {
-  _circle.strokeColor = color;
+  self.circle.strokeColor = color;
 }
 - (void)setStrokeWidth:(CGFloat)width {
-  _circle.strokeWidth = width;
+  self.circle.strokeWidth = width;
 }
 - (void)setFillColor:(UIColor *)color {
-  _circle.fillColor = color;
-}
-@end
-
-static int ToInt(NSNumber *data) { return [FLTGoogleMapJsonConversions toInt:data]; }
-
-static BOOL ToBool(NSNumber *data) { return [FLTGoogleMapJsonConversions toBool:data]; }
-
-static CLLocationCoordinate2D ToLocation(NSArray *data) {
-  return [FLTGoogleMapJsonConversions toLocation:data];
+  self.circle.fillColor = color;
 }
 
-static CLLocationDistance ToDistance(NSNumber *data) {
-  return [FLTGoogleMapJsonConversions toFloat:data];
-}
-
-static UIColor *ToColor(NSNumber *data) { return [FLTGoogleMapJsonConversions toColor:data]; }
-
-static void InterpretCircleOptions(NSDictionary *data, id<FLTGoogleMapCircleOptionsSink> sink,
-                                   NSObject<FlutterPluginRegistrar> *registrar) {
+- (void)interpretCircleOptions:(NSDictionary *)data {
   NSNumber *consumeTapEvents = data[@"consumeTapEvents"];
-  if (consumeTapEvents != nil) {
-    [sink setConsumeTapEvents:ToBool(consumeTapEvents)];
+  if (consumeTapEvents && consumeTapEvents != (id)[NSNull null]) {
+    [self setConsumeTapEvents:consumeTapEvents.boolValue];
   }
 
   NSNumber *visible = data[@"visible"];
-  if (visible != nil) {
-    [sink setVisible:ToBool(visible)];
+  if (visible && visible != (id)[NSNull null]) {
+    [self setVisible:[visible boolValue]];
   }
 
   NSNumber *zIndex = data[@"zIndex"];
-  if (zIndex != nil) {
-    [sink setZIndex:ToInt(zIndex)];
+  if (zIndex && zIndex != (id)[NSNull null]) {
+    [self setZIndex:[zIndex intValue]];
   }
 
   NSArray *center = data[@"center"];
-  if (center) {
-    [sink setCenter:ToLocation(center)];
+  if (center && center != (id)[NSNull null]) {
+    [self setCenter:[FLTGoogleMapJSONConversions locationFromLatLong:center]];
   }
 
   NSNumber *radius = data[@"radius"];
-  if (radius != nil) {
-    [sink setRadius:ToDistance(radius)];
+  if (radius && radius != (id)[NSNull null]) {
+    [self setRadius:[radius floatValue]];
   }
 
   NSNumber *strokeColor = data[@"strokeColor"];
-  if (strokeColor != nil) {
-    [sink setStrokeColor:ToColor(strokeColor)];
+  if (strokeColor && strokeColor != (id)[NSNull null]) {
+    [self setStrokeColor:[FLTGoogleMapJSONConversions colorFromRGBA:strokeColor]];
   }
 
   NSNumber *strokeWidth = data[@"strokeWidth"];
-  if (strokeWidth != nil) {
-    [sink setStrokeWidth:ToInt(strokeWidth)];
+  if (strokeWidth && strokeWidth != (id)[NSNull null]) {
+    [self setStrokeWidth:[strokeWidth intValue]];
   }
 
   NSNumber *fillColor = data[@"fillColor"];
-  if (fillColor != nil) {
-    [sink setFillColor:ToColor(fillColor)];
+  if (fillColor && fillColor != (id)[NSNull null]) {
+    [self setFillColor:[FLTGoogleMapJSONConversions colorFromRGBA:fillColor]];
   }
 }
 
-@implementation FLTCirclesController {
-  NSMutableDictionary *_circleIdToController;
-  FlutterMethodChannel *_methodChannel;
-  NSObject<FlutterPluginRegistrar> *_registrar;
-  GMSMapView *_mapView;
-}
+@end
+
+@interface FLTCirclesController ()
+
+@property(strong, nonatomic) FlutterMethodChannel *methodChannel;
+@property(weak, nonatomic) GMSMapView *mapView;
+@property(strong, nonatomic) NSMutableDictionary *circleIdToController;
+
+@end
+
+@implementation FLTCirclesController
+
 - (instancetype)init:(FlutterMethodChannel *)methodChannel
              mapView:(GMSMapView *)mapView
            registrar:(NSObject<FlutterPluginRegistrar> *)registrar {
@@ -127,10 +121,10 @@
     _methodChannel = methodChannel;
     _mapView = mapView;
     _circleIdToController = [NSMutableDictionary dictionaryWithCapacity:1];
-    _registrar = registrar;
   }
   return self;
 }
+
 - (void)addCircles:(NSArray *)circlesToAdd {
   for (NSDictionary *circle in circlesToAdd) {
     CLLocationCoordinate2D position = [FLTCirclesController getPosition:circle];
@@ -140,59 +134,64 @@
         [[FLTGoogleMapCircleController alloc] initCircleWithPosition:position
                                                               radius:radius
                                                             circleId:circleId
-                                                             mapView:_mapView];
-    InterpretCircleOptions(circle, controller, _registrar);
-    _circleIdToController[circleId] = controller;
+                                                             mapView:self.mapView
+                                                             options:circle];
+    self.circleIdToController[circleId] = controller;
   }
 }
+
 - (void)changeCircles:(NSArray *)circlesToChange {
   for (NSDictionary *circle in circlesToChange) {
     NSString *circleId = [FLTCirclesController getCircleId:circle];
-    FLTGoogleMapCircleController *controller = _circleIdToController[circleId];
+    FLTGoogleMapCircleController *controller = self.circleIdToController[circleId];
     if (!controller) {
       continue;
     }
-    InterpretCircleOptions(circle, controller, _registrar);
+    [controller interpretCircleOptions:circle];
   }
 }
-- (void)removeCircleIds:(NSArray *)circleIdsToRemove {
-  for (NSString *circleId in circleIdsToRemove) {
-    if (!circleId) {
-      continue;
-    }
-    FLTGoogleMapCircleController *controller = _circleIdToController[circleId];
+
+- (void)removeCircleWithIdentifiers:(NSArray *)identifiers {
+  for (NSString *identifier in identifiers) {
+    FLTGoogleMapCircleController *controller = self.circleIdToController[identifier];
     if (!controller) {
       continue;
     }
     [controller removeCircle];
-    [_circleIdToController removeObjectForKey:circleId];
+    [self.circleIdToController removeObjectForKey:identifier];
   }
 }
-- (bool)hasCircleWithId:(NSString *)circleId {
-  if (!circleId) {
+
+- (bool)hasCircleWithIdentifier:(NSString *)identifier {
+  if (!identifier) {
     return false;
   }
-  return _circleIdToController[circleId] != nil;
+  return self.circleIdToController[identifier] != nil;
 }
-- (void)onCircleTap:(NSString *)circleId {
-  if (!circleId) {
+
+- (void)didTapCircleWithIdentifier:(NSString *)identifier {
+  if (!identifier) {
     return;
   }
-  FLTGoogleMapCircleController *controller = _circleIdToController[circleId];
+  FLTGoogleMapCircleController *controller = self.circleIdToController[identifier];
   if (!controller) {
     return;
   }
-  [_methodChannel invokeMethod:@"circle#onTap" arguments:@{@"circleId" : circleId}];
+  [self.methodChannel invokeMethod:@"circle#onTap" arguments:@{@"circleId" : identifier}];
 }
+
 + (CLLocationCoordinate2D)getPosition:(NSDictionary *)circle {
   NSArray *center = circle[@"center"];
-  return ToLocation(center);
+  return [FLTGoogleMapJSONConversions locationFromLatLong:center];
 }
+
 + (CLLocationDistance)getRadius:(NSDictionary *)circle {
   NSNumber *radius = circle[@"radius"];
-  return ToDistance(radius);
+  return [radius floatValue];
 }
+
 + (NSString *)getCircleId:(NSDictionary *)circle {
   return circle[@"circleId"];
 }
+
 @end
diff --git a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapController.h b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapController.h
index a8cebb9..d1069ac 100644
--- a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapController.h
+++ b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapController.h
@@ -11,34 +11,13 @@
 
 NS_ASSUME_NONNULL_BEGIN
 
-// Defines map UI options writable from Flutter.
-@protocol FLTGoogleMapOptionsSink
-- (void)setCameraTargetBounds:(nullable GMSCoordinateBounds *)bounds;
-- (void)setCompassEnabled:(BOOL)enabled;
-- (void)setIndoorEnabled:(BOOL)enabled;
-- (void)setTrafficEnabled:(BOOL)enabled;
-- (void)setBuildingsEnabled:(BOOL)enabled;
-- (void)setMapType:(GMSMapViewType)type;
-- (void)setMinZoom:(float)minZoom maxZoom:(float)maxZoom;
-- (void)setPaddingTop:(float)top left:(float)left bottom:(float)bottom right:(float)right;
-- (void)setRotateGesturesEnabled:(BOOL)enabled;
-- (void)setScrollGesturesEnabled:(BOOL)enabled;
-- (void)setTiltGesturesEnabled:(BOOL)enabled;
-- (void)setTrackCameraPosition:(BOOL)enabled;
-- (void)setZoomGesturesEnabled:(BOOL)enabled;
-- (void)setMyLocationEnabled:(BOOL)enabled;
-- (void)setMyLocationButtonEnabled:(BOOL)enabled;
-- (nullable NSString *)setMapStyle:(NSString *)mapStyle;
-@end
-
 // Defines map overlay controllable from Flutter.
-@interface FLTGoogleMapController
-    : NSObject <GMSMapViewDelegate, FLTGoogleMapOptionsSink, FlutterPlatformView>
+@interface FLTGoogleMapController : NSObject <GMSMapViewDelegate, FlutterPlatformView>
 - (instancetype)initWithFrame:(CGRect)frame
                viewIdentifier:(int64_t)viewId
                     arguments:(nullable id)args
                     registrar:(NSObject<FlutterPluginRegistrar> *)registrar;
-- (void)showAtX:(CGFloat)x Y:(CGFloat)y;
+- (void)showAtOrigin:(CGPoint)origin;
 - (void)hide;
 - (void)animateWithCameraUpdate:(GMSCameraUpdate *)cameraUpdate;
 - (void)moveWithCameraUpdate:(GMSCameraUpdate *)cameraUpdate;
diff --git a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapController.m b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapController.m
index ca80681..6378994 100644
--- a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapController.m
+++ b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapController.m
@@ -3,25 +3,18 @@
 // found in the LICENSE file.
 
 #import "GoogleMapController.h"
+#import "FLTGoogleMapJSONConversions.h"
 #import "FLTGoogleMapTileOverlayController.h"
-#import "JsonConversions.h"
 
 #pragma mark - Conversion of JSON-like values sent via platform channels. Forward declarations.
 
-static NSDictionary *PositionToJson(GMSCameraPosition *position);
-static NSDictionary *PointToJson(CGPoint point);
-static NSArray *LocationToJson(CLLocationCoordinate2D position);
-static CGPoint ToCGPoint(NSDictionary *json);
-static GMSCameraPosition *ToOptionalCameraPosition(NSDictionary *json);
-static GMSCoordinateBounds *ToOptionalBounds(NSArray *json);
-static GMSCameraUpdate *ToCameraUpdate(NSArray *data);
-static NSDictionary *GMSCoordinateBoundsToJson(GMSCoordinateBounds *bounds);
-static void InterpretMapOptions(NSDictionary *data, id<FLTGoogleMapOptionsSink> sink);
-static double ToDouble(NSNumber *data) { return [FLTGoogleMapJsonConversions toDouble:data]; }
+@interface FLTGoogleMapFactory ()
 
-@implementation FLTGoogleMapFactory {
-  NSObject<FlutterPluginRegistrar> *_registrar;
-}
+@property(weak, nonatomic) NSObject<FlutterPluginRegistrar> *registrar;
+
+@end
+
+@implementation FLTGoogleMapFactory
 
 - (instancetype)initWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar {
   self = [super init];
@@ -41,28 +34,32 @@
   return [[FLTGoogleMapController alloc] initWithFrame:frame
                                         viewIdentifier:viewId
                                              arguments:args
-                                             registrar:_registrar];
+                                             registrar:self.registrar];
 }
 @end
 
-@implementation FLTGoogleMapController {
-  GMSMapView *_mapView;
-  int64_t _viewId;
-  FlutterMethodChannel *_channel;
-  BOOL _trackCameraPosition;
-  NSObject<FlutterPluginRegistrar> *_registrar;
-  FLTMarkersController *_markersController;
-  FLTPolygonsController *_polygonsController;
-  FLTPolylinesController *_polylinesController;
-  FLTCirclesController *_circlesController;
-  FLTTileOverlaysController *_tileOverlaysController;
-}
+@interface FLTGoogleMapController ()
+
+@property(nonatomic, strong) GMSMapView *mapView;
+@property(nonatomic, strong) FlutterMethodChannel *channel;
+@property(nonatomic, assign) BOOL trackCameraPosition;
+@property(nonatomic, weak) NSObject<FlutterPluginRegistrar> *registrar;
+@property(nonatomic, strong) FLTMarkersController *markersController;
+@property(nonatomic, strong) FLTPolygonsController *polygonsController;
+@property(nonatomic, strong) FLTPolylinesController *polylinesController;
+@property(nonatomic, strong) FLTCirclesController *circlesController;
+@property(nonatomic, strong) FLTTileOverlaysController *tileOverlaysController;
+
+@end
+
+@implementation FLTGoogleMapController
 
 - (instancetype)initWithFrame:(CGRect)frame
                viewIdentifier:(int64_t)viewId
                     arguments:(id _Nullable)args
                     registrar:(NSObject<FlutterPluginRegistrar> *)registrar {
-  GMSCameraPosition *camera = ToOptionalCameraPosition(args[@"initialCameraPosition"]);
+  GMSCameraPosition *camera =
+      [FLTGoogleMapJSONConversions cameraPostionFromDictionary:args[@"initialCameraPosition"]];
   GMSMapView *mapView = [GMSMapView mapWithFrame:frame camera:camera];
   return [self initWithMapView:mapView viewIdentifier:viewId arguments:args registrar:registrar];
 }
@@ -73,11 +70,11 @@
                       registrar:(NSObject<FlutterPluginRegistrar> *_Nonnull)registrar {
   if (self = [super init]) {
     _mapView = mapView;
-    _viewId = viewId;
 
     _mapView.accessibilityElementsHidden = NO;
-    _trackCameraPosition = NO;
-    InterpretMapOptions(args[@"options"], self);
+    // TODO(cyanglaz): avoid sending message to self in the middle of the init method.
+    // https://github.com/flutter/flutter/issues/104121
+    [self interpretMapOptions:args[@"options"]];
     NSString *channelName =
         [NSString stringWithFormat:@"plugins.flutter.io/google_maps_%lld", viewId];
     _channel = [FlutterMethodChannel methodChannelWithName:channelName
@@ -90,9 +87,9 @@
     }];
     _mapView.delegate = weakSelf;
     _registrar = registrar;
-    _markersController = [[FLTMarkersController alloc] init:_channel
-                                                    mapView:_mapView
-                                                  registrar:registrar];
+    _markersController = [[FLTMarkersController alloc] initWithMethodChannel:_channel
+                                                                     mapView:_mapView
+                                                                   registrar:registrar];
     _polygonsController = [[FLTPolygonsController alloc] init:_channel
                                                       mapView:_mapView
                                                     registrar:registrar];
@@ -132,25 +129,25 @@
 }
 
 - (UIView *)view {
-  return _mapView;
+  return self.mapView;
 }
 
 - (void)observeValueForKeyPath:(NSString *)keyPath
                       ofObject:(id)object
                         change:(NSDictionary *)change
                        context:(void *)context {
-  if (object == _mapView && [keyPath isEqualToString:@"frame"]) {
-    CGRect bounds = _mapView.bounds;
+  if (object == self.mapView && [keyPath isEqualToString:@"frame"]) {
+    CGRect bounds = self.mapView.bounds;
     if (CGRectEqualToRect(bounds, CGRectZero)) {
       // The workaround is to fix an issue that the camera location is not current when
       // the size of the map is zero at initialization.
-      // So We only care about the size of the `_mapView`, ignore the frame changes when the size is
-      // zero.
+      // So We only care about the size of the `self.mapView`, ignore the frame changes when the
+      // size is zero.
       return;
     }
     // We only observe the frame for initial setup.
-    [_mapView removeObserver:self forKeyPath:@"frame"];
-    [_mapView moveCamera:[GMSCameraUpdate setCamera:_mapView.camera]];
+    [self.mapView removeObserver:self forKeyPath:@"frame"];
+    [self.mapView moveCamera:[GMSCameraUpdate setCamera:self.mapView.camera]];
   } else {
     [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
   }
@@ -158,46 +155,50 @@
 
 - (void)onMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result {
   if ([call.method isEqualToString:@"map#show"]) {
-    [self showAtX:ToDouble(call.arguments[@"x"]) Y:ToDouble(call.arguments[@"y"])];
+    [self showAtOrigin:CGPointMake([call.arguments[@"x"] doubleValue],
+                                   [call.arguments[@"y"] doubleValue])];
     result(nil);
   } else if ([call.method isEqualToString:@"map#hide"]) {
     [self hide];
     result(nil);
   } else if ([call.method isEqualToString:@"camera#animate"]) {
-    [self animateWithCameraUpdate:ToCameraUpdate(call.arguments[@"cameraUpdate"])];
+    [self
+        animateWithCameraUpdate:[FLTGoogleMapJSONConversions
+                                    cameraUpdateFromChannelValue:call.arguments[@"cameraUpdate"]]];
     result(nil);
   } else if ([call.method isEqualToString:@"camera#move"]) {
-    [self moveWithCameraUpdate:ToCameraUpdate(call.arguments[@"cameraUpdate"])];
+    [self moveWithCameraUpdate:[FLTGoogleMapJSONConversions
+                                   cameraUpdateFromChannelValue:call.arguments[@"cameraUpdate"]]];
     result(nil);
   } else if ([call.method isEqualToString:@"map#update"]) {
-    InterpretMapOptions(call.arguments[@"options"], self);
-    result(PositionToJson([self cameraPosition]));
+    [self interpretMapOptions:call.arguments[@"options"]];
+    result([FLTGoogleMapJSONConversions dictionaryFromPosition:[self cameraPosition]]);
   } else if ([call.method isEqualToString:@"map#getVisibleRegion"]) {
-    if (_mapView != nil) {
-      GMSVisibleRegion visibleRegion = _mapView.projection.visibleRegion;
+    if (self.mapView != nil) {
+      GMSVisibleRegion visibleRegion = self.mapView.projection.visibleRegion;
       GMSCoordinateBounds *bounds = [[GMSCoordinateBounds alloc] initWithRegion:visibleRegion];
-
-      result(GMSCoordinateBoundsToJson(bounds));
+      result([FLTGoogleMapJSONConversions dictionaryFromCoordinateBounds:bounds]);
     } else {
       result([FlutterError errorWithCode:@"GoogleMap uninitialized"
                                  message:@"getVisibleRegion called prior to map initialization"
                                  details:nil]);
     }
   } else if ([call.method isEqualToString:@"map#getScreenCoordinate"]) {
-    if (_mapView != nil) {
-      CLLocationCoordinate2D location = [FLTGoogleMapJsonConversions toLocation:call.arguments];
-      CGPoint point = [_mapView.projection pointForCoordinate:location];
-      result(PointToJson(point));
+    if (self.mapView != nil) {
+      CLLocationCoordinate2D location =
+          [FLTGoogleMapJSONConversions locationFromLatLong:call.arguments];
+      CGPoint point = [self.mapView.projection pointForCoordinate:location];
+      result([FLTGoogleMapJSONConversions dictionaryFromPoint:point]);
     } else {
       result([FlutterError errorWithCode:@"GoogleMap uninitialized"
                                  message:@"getScreenCoordinate called prior to map initialization"
                                  details:nil]);
     }
   } else if ([call.method isEqualToString:@"map#getLatLng"]) {
-    if (_mapView != nil && call.arguments) {
-      CGPoint point = ToCGPoint(call.arguments);
-      CLLocationCoordinate2D latlng = [_mapView.projection coordinateForPoint:point];
-      result(LocationToJson(latlng));
+    if (self.mapView != nil && call.arguments) {
+      CGPoint point = [FLTGoogleMapJSONConversions pointFromDictionary:call.arguments];
+      CLLocationCoordinate2D latlng = [self.mapView.projection coordinateForPoint:point];
+      result([FLTGoogleMapJSONConversions arrayFromLocation:latlng]);
     } else {
       result([FlutterError errorWithCode:@"GoogleMap uninitialized"
                                  message:@"getLatLng called prior to map initialization"
@@ -207,14 +208,14 @@
     result(nil);
   } else if ([call.method isEqualToString:@"map#takeSnapshot"]) {
     if (@available(iOS 10.0, *)) {
-      if (_mapView != nil) {
+      if (self.mapView != nil) {
         UIGraphicsImageRendererFormat *format = [UIGraphicsImageRendererFormat defaultFormat];
         format.scale = [[UIScreen mainScreen] scale];
         UIGraphicsImageRenderer *renderer =
-            [[UIGraphicsImageRenderer alloc] initWithSize:_mapView.frame.size format:format];
+            [[UIGraphicsImageRenderer alloc] initWithSize:self.mapView.frame.size format:format];
 
         UIImage *image = [renderer imageWithActions:^(UIGraphicsImageRendererContext *context) {
-          [_mapView.layer renderInContext:context.CGContext];
+          [self.mapView.layer renderInContext:context.CGContext];
         }];
         result([FlutterStandardTypedData typedDataWithBytes:UIImagePNGRepresentation(image)]);
       } else {
@@ -229,21 +230,21 @@
   } else if ([call.method isEqualToString:@"markers#update"]) {
     id markersToAdd = call.arguments[@"markersToAdd"];
     if ([markersToAdd isKindOfClass:[NSArray class]]) {
-      [_markersController addMarkers:markersToAdd];
+      [self.markersController addMarkers:markersToAdd];
     }
     id markersToChange = call.arguments[@"markersToChange"];
     if ([markersToChange isKindOfClass:[NSArray class]]) {
-      [_markersController changeMarkers:markersToChange];
+      [self.markersController changeMarkers:markersToChange];
     }
     id markerIdsToRemove = call.arguments[@"markerIdsToRemove"];
     if ([markerIdsToRemove isKindOfClass:[NSArray class]]) {
-      [_markersController removeMarkerIds:markerIdsToRemove];
+      [self.markersController removeMarkersWithIdentifiers:markerIdsToRemove];
     }
     result(nil);
   } else if ([call.method isEqualToString:@"markers#showInfoWindow"]) {
     id markerId = call.arguments[@"markerId"];
     if ([markerId isKindOfClass:[NSString class]]) {
-      [_markersController showMarkerInfoWindow:markerId result:result];
+      [self.markersController showMarkerInfoWindowWithIdentifier:markerId result:result];
     } else {
       result([FlutterError errorWithCode:@"Invalid markerId"
                                  message:@"showInfoWindow called with invalid markerId"
@@ -252,7 +253,7 @@
   } else if ([call.method isEqualToString:@"markers#hideInfoWindow"]) {
     id markerId = call.arguments[@"markerId"];
     if ([markerId isKindOfClass:[NSString class]]) {
-      [_markersController hideMarkerInfoWindow:markerId result:result];
+      [self.markersController hideMarkerInfoWindowWithIdentifier:markerId result:result];
     } else {
       result([FlutterError errorWithCode:@"Invalid markerId"
                                  message:@"hideInfoWindow called with invalid markerId"
@@ -261,7 +262,7 @@
   } else if ([call.method isEqualToString:@"markers#isInfoWindowShown"]) {
     id markerId = call.arguments[@"markerId"];
     if ([markerId isKindOfClass:[NSString class]]) {
-      [_markersController isMarkerInfoWindowShown:markerId result:result];
+      [self.markersController isInfoWindowShownForMarkerWithIdentifier:markerId result:result];
     } else {
       result([FlutterError errorWithCode:@"Invalid markerId"
                                  message:@"isInfoWindowShown called with invalid markerId"
@@ -270,97 +271,97 @@
   } else if ([call.method isEqualToString:@"polygons#update"]) {
     id polygonsToAdd = call.arguments[@"polygonsToAdd"];
     if ([polygonsToAdd isKindOfClass:[NSArray class]]) {
-      [_polygonsController addPolygons:polygonsToAdd];
+      [self.polygonsController addPolygons:polygonsToAdd];
     }
     id polygonsToChange = call.arguments[@"polygonsToChange"];
     if ([polygonsToChange isKindOfClass:[NSArray class]]) {
-      [_polygonsController changePolygons:polygonsToChange];
+      [self.polygonsController changePolygons:polygonsToChange];
     }
     id polygonIdsToRemove = call.arguments[@"polygonIdsToRemove"];
     if ([polygonIdsToRemove isKindOfClass:[NSArray class]]) {
-      [_polygonsController removePolygonIds:polygonIdsToRemove];
+      [self.polygonsController removePolygonWithIdentifiers:polygonIdsToRemove];
     }
     result(nil);
   } else if ([call.method isEqualToString:@"polylines#update"]) {
     id polylinesToAdd = call.arguments[@"polylinesToAdd"];
     if ([polylinesToAdd isKindOfClass:[NSArray class]]) {
-      [_polylinesController addPolylines:polylinesToAdd];
+      [self.polylinesController addPolylines:polylinesToAdd];
     }
     id polylinesToChange = call.arguments[@"polylinesToChange"];
     if ([polylinesToChange isKindOfClass:[NSArray class]]) {
-      [_polylinesController changePolylines:polylinesToChange];
+      [self.polylinesController changePolylines:polylinesToChange];
     }
     id polylineIdsToRemove = call.arguments[@"polylineIdsToRemove"];
     if ([polylineIdsToRemove isKindOfClass:[NSArray class]]) {
-      [_polylinesController removePolylineIds:polylineIdsToRemove];
+      [self.polylinesController removePolylineWithIdentifiers:polylineIdsToRemove];
     }
     result(nil);
   } else if ([call.method isEqualToString:@"circles#update"]) {
     id circlesToAdd = call.arguments[@"circlesToAdd"];
     if ([circlesToAdd isKindOfClass:[NSArray class]]) {
-      [_circlesController addCircles:circlesToAdd];
+      [self.circlesController addCircles:circlesToAdd];
     }
     id circlesToChange = call.arguments[@"circlesToChange"];
     if ([circlesToChange isKindOfClass:[NSArray class]]) {
-      [_circlesController changeCircles:circlesToChange];
+      [self.circlesController changeCircles:circlesToChange];
     }
     id circleIdsToRemove = call.arguments[@"circleIdsToRemove"];
     if ([circleIdsToRemove isKindOfClass:[NSArray class]]) {
-      [_circlesController removeCircleIds:circleIdsToRemove];
+      [self.circlesController removeCircleWithIdentifiers:circleIdsToRemove];
     }
     result(nil);
   } else if ([call.method isEqualToString:@"tileOverlays#update"]) {
     id tileOverlaysToAdd = call.arguments[@"tileOverlaysToAdd"];
     if ([tileOverlaysToAdd isKindOfClass:[NSArray class]]) {
-      [_tileOverlaysController addTileOverlays:tileOverlaysToAdd];
+      [self.tileOverlaysController addTileOverlays:tileOverlaysToAdd];
     }
     id tileOverlaysToChange = call.arguments[@"tileOverlaysToChange"];
     if ([tileOverlaysToChange isKindOfClass:[NSArray class]]) {
-      [_tileOverlaysController changeTileOverlays:tileOverlaysToChange];
+      [self.tileOverlaysController changeTileOverlays:tileOverlaysToChange];
     }
     id tileOverlayIdsToRemove = call.arguments[@"tileOverlayIdsToRemove"];
     if ([tileOverlayIdsToRemove isKindOfClass:[NSArray class]]) {
-      [_tileOverlaysController removeTileOverlayIds:tileOverlayIdsToRemove];
+      [self.tileOverlaysController removeTileOverlayWithIdentifiers:tileOverlayIdsToRemove];
     }
     result(nil);
   } else if ([call.method isEqualToString:@"tileOverlays#clearTileCache"]) {
     id rawTileOverlayId = call.arguments[@"tileOverlayId"];
-    [_tileOverlaysController clearTileCache:rawTileOverlayId];
+    [self.tileOverlaysController clearTileCacheWithIdentifier:rawTileOverlayId];
     result(nil);
   } else if ([call.method isEqualToString:@"map#isCompassEnabled"]) {
-    NSNumber *isCompassEnabled = @(_mapView.settings.compassButton);
+    NSNumber *isCompassEnabled = @(self.mapView.settings.compassButton);
     result(isCompassEnabled);
   } else if ([call.method isEqualToString:@"map#isMapToolbarEnabled"]) {
     NSNumber *isMapToolbarEnabled = @NO;
     result(isMapToolbarEnabled);
   } else if ([call.method isEqualToString:@"map#getMinMaxZoomLevels"]) {
-    NSArray *zoomLevels = @[ @(_mapView.minZoom), @(_mapView.maxZoom) ];
+    NSArray *zoomLevels = @[ @(self.mapView.minZoom), @(self.mapView.maxZoom) ];
     result(zoomLevels);
   } else if ([call.method isEqualToString:@"map#getZoomLevel"]) {
-    result(@(_mapView.camera.zoom));
+    result(@(self.mapView.camera.zoom));
   } else if ([call.method isEqualToString:@"map#isZoomGesturesEnabled"]) {
-    NSNumber *isZoomGesturesEnabled = @(_mapView.settings.zoomGestures);
+    NSNumber *isZoomGesturesEnabled = @(self.mapView.settings.zoomGestures);
     result(isZoomGesturesEnabled);
   } else if ([call.method isEqualToString:@"map#isZoomControlsEnabled"]) {
     NSNumber *isZoomControlsEnabled = @NO;
     result(isZoomControlsEnabled);
   } else if ([call.method isEqualToString:@"map#isTiltGesturesEnabled"]) {
-    NSNumber *isTiltGesturesEnabled = @(_mapView.settings.tiltGestures);
+    NSNumber *isTiltGesturesEnabled = @(self.mapView.settings.tiltGestures);
     result(isTiltGesturesEnabled);
   } else if ([call.method isEqualToString:@"map#isRotateGesturesEnabled"]) {
-    NSNumber *isRotateGesturesEnabled = @(_mapView.settings.rotateGestures);
+    NSNumber *isRotateGesturesEnabled = @(self.mapView.settings.rotateGestures);
     result(isRotateGesturesEnabled);
   } else if ([call.method isEqualToString:@"map#isScrollGesturesEnabled"]) {
-    NSNumber *isScrollGesturesEnabled = @(_mapView.settings.scrollGestures);
+    NSNumber *isScrollGesturesEnabled = @(self.mapView.settings.scrollGestures);
     result(isScrollGesturesEnabled);
   } else if ([call.method isEqualToString:@"map#isMyLocationButtonEnabled"]) {
-    NSNumber *isMyLocationButtonEnabled = @(_mapView.settings.myLocationButton);
+    NSNumber *isMyLocationButtonEnabled = @(self.mapView.settings.myLocationButton);
     result(isMyLocationButtonEnabled);
   } else if ([call.method isEqualToString:@"map#isTrafficEnabled"]) {
-    NSNumber *isTrafficEnabled = @(_mapView.trafficEnabled);
+    NSNumber *isTrafficEnabled = @(self.mapView.trafficEnabled);
     result(isTrafficEnabled);
   } else if ([call.method isEqualToString:@"map#isBuildingsEnabled"]) {
-    NSNumber *isBuildingsEnabled = @(_mapView.buildingsEnabled);
+    NSNumber *isBuildingsEnabled = @(self.mapView.buildingsEnabled);
     result(isBuildingsEnabled);
   } else if ([call.method isEqualToString:@"map#setStyle"]) {
     NSString *mapStyle = [call arguments];
@@ -372,86 +373,84 @@
     }
   } else if ([call.method isEqualToString:@"map#getTileOverlayInfo"]) {
     NSString *rawTileOverlayId = call.arguments[@"tileOverlayId"];
-    result([_tileOverlaysController getTileOverlayInfo:rawTileOverlayId]);
+    result([self.tileOverlaysController tileOverlayInfoWithIdentifier:rawTileOverlayId]);
   } else {
     result(FlutterMethodNotImplemented);
   }
 }
 
-- (void)showAtX:(CGFloat)x Y:(CGFloat)y {
-  _mapView.frame =
-      CGRectMake(x, y, CGRectGetWidth(_mapView.frame), CGRectGetHeight(_mapView.frame));
-  _mapView.hidden = NO;
+- (void)showAtOrigin:(CGPoint)origin {
+  CGRect frame = {origin, self.mapView.frame.size};
+  self.mapView.frame = frame;
+  self.mapView.hidden = NO;
 }
 
 - (void)hide {
-  _mapView.hidden = YES;
+  self.mapView.hidden = YES;
 }
 
 - (void)animateWithCameraUpdate:(GMSCameraUpdate *)cameraUpdate {
-  [_mapView animateWithCameraUpdate:cameraUpdate];
+  [self.mapView animateWithCameraUpdate:cameraUpdate];
 }
 
 - (void)moveWithCameraUpdate:(GMSCameraUpdate *)cameraUpdate {
-  [_mapView moveCamera:cameraUpdate];
+  [self.mapView moveCamera:cameraUpdate];
 }
 
 - (GMSCameraPosition *)cameraPosition {
-  if (_trackCameraPosition) {
-    return _mapView.camera;
+  if (self.trackCameraPosition) {
+    return self.mapView.camera;
   } else {
     return nil;
   }
 }
 
-#pragma mark - FLTGoogleMapOptionsSink methods
-
 - (void)setCamera:(GMSCameraPosition *)camera {
-  _mapView.camera = camera;
+  self.mapView.camera = camera;
 }
 
 - (void)setCameraTargetBounds:(GMSCoordinateBounds *)bounds {
-  _mapView.cameraTargetBounds = bounds;
+  self.mapView.cameraTargetBounds = bounds;
 }
 
 - (void)setCompassEnabled:(BOOL)enabled {
-  _mapView.settings.compassButton = enabled;
+  self.mapView.settings.compassButton = enabled;
 }
 
 - (void)setIndoorEnabled:(BOOL)enabled {
-  _mapView.indoorEnabled = enabled;
+  self.mapView.indoorEnabled = enabled;
 }
 
 - (void)setTrafficEnabled:(BOOL)enabled {
-  _mapView.trafficEnabled = enabled;
+  self.mapView.trafficEnabled = enabled;
 }
 
 - (void)setBuildingsEnabled:(BOOL)enabled {
-  _mapView.buildingsEnabled = enabled;
+  self.mapView.buildingsEnabled = enabled;
 }
 
 - (void)setMapType:(GMSMapViewType)mapType {
-  _mapView.mapType = mapType;
+  self.mapView.mapType = mapType;
 }
 
 - (void)setMinZoom:(float)minZoom maxZoom:(float)maxZoom {
-  [_mapView setMinZoom:minZoom maxZoom:maxZoom];
+  [self.mapView setMinZoom:minZoom maxZoom:maxZoom];
 }
 
 - (void)setPaddingTop:(float)top left:(float)left bottom:(float)bottom right:(float)right {
-  _mapView.padding = UIEdgeInsetsMake(top, left, bottom, right);
+  self.mapView.padding = UIEdgeInsetsMake(top, left, bottom, right);
 }
 
 - (void)setRotateGesturesEnabled:(BOOL)enabled {
-  _mapView.settings.rotateGestures = enabled;
+  self.mapView.settings.rotateGestures = enabled;
 }
 
 - (void)setScrollGesturesEnabled:(BOOL)enabled {
-  _mapView.settings.scrollGestures = enabled;
+  self.mapView.settings.scrollGestures = enabled;
 }
 
 - (void)setTiltGesturesEnabled:(BOOL)enabled {
-  _mapView.settings.tiltGestures = enabled;
+  self.mapView.settings.tiltGestures = enabled;
 }
 
 - (void)setTrackCameraPosition:(BOOL)enabled {
@@ -459,20 +458,20 @@
 }
 
 - (void)setZoomGesturesEnabled:(BOOL)enabled {
-  _mapView.settings.zoomGestures = enabled;
+  self.mapView.settings.zoomGestures = enabled;
 }
 
 - (void)setMyLocationEnabled:(BOOL)enabled {
-  _mapView.myLocationEnabled = enabled;
+  self.mapView.myLocationEnabled = enabled;
 }
 
 - (void)setMyLocationButtonEnabled:(BOOL)enabled {
-  _mapView.settings.myLocationButton = enabled;
+  self.mapView.settings.myLocationButton = enabled;
 }
 
 - (NSString *)setMapStyle:(NSString *)mapStyle {
   if (mapStyle == (id)[NSNull null] || mapStyle.length == 0) {
-    _mapView.mapStyle = nil;
+    self.mapView.mapStyle = nil;
     return nil;
   }
   NSError *error;
@@ -480,7 +479,7 @@
   if (!style) {
     return [error localizedDescription];
   } else {
-    _mapView.mapStyle = style;
+    self.mapView.mapStyle = style;
     return nil;
   }
 }
@@ -488,236 +487,141 @@
 #pragma mark - GMSMapViewDelegate methods
 
 - (void)mapView:(GMSMapView *)mapView willMove:(BOOL)gesture {
-  [_channel invokeMethod:@"camera#onMoveStarted" arguments:@{@"isGesture" : @(gesture)}];
+  [self.channel invokeMethod:@"camera#onMoveStarted" arguments:@{@"isGesture" : @(gesture)}];
 }
 
 - (void)mapView:(GMSMapView *)mapView didChangeCameraPosition:(GMSCameraPosition *)position {
-  if (_trackCameraPosition) {
-    [_channel invokeMethod:@"camera#onMove" arguments:@{@"position" : PositionToJson(position)}];
+  if (self.trackCameraPosition) {
+    [self.channel invokeMethod:@"camera#onMove"
+                     arguments:@{
+                       @"position" : [FLTGoogleMapJSONConversions dictionaryFromPosition:position]
+                     }];
   }
 }
 
 - (void)mapView:(GMSMapView *)mapView idleAtCameraPosition:(GMSCameraPosition *)position {
-  [_channel invokeMethod:@"camera#onIdle" arguments:@{}];
+  [self.channel invokeMethod:@"camera#onIdle" arguments:@{}];
 }
 
 - (BOOL)mapView:(GMSMapView *)mapView didTapMarker:(GMSMarker *)marker {
   NSString *markerId = marker.userData[0];
-  return [_markersController onMarkerTap:markerId];
+  return [self.markersController didTapMarkerWithIdentifier:markerId];
 }
 
 - (void)mapView:(GMSMapView *)mapView didEndDraggingMarker:(GMSMarker *)marker {
   NSString *markerId = marker.userData[0];
-  [_markersController onMarkerDragEnd:markerId coordinate:marker.position];
+  [self.markersController didEndDraggingMarkerWithIdentifier:markerId location:marker.position];
 }
 
 - (void)mapView:(GMSMapView *)mapView didStartDraggingMarker:(GMSMarker *)marker {
   NSString *markerId = marker.userData[0];
-  [_markersController onMarkerDragStart:markerId coordinate:marker.position];
+  [self.markersController didStartDraggingMarkerWithIdentifier:markerId location:marker.position];
 }
 
 - (void)mapView:(GMSMapView *)mapView didDragMarker:(GMSMarker *)marker {
   NSString *markerId = marker.userData[0];
-  [_markersController onMarkerDrag:markerId coordinate:marker.position];
+  [self.markersController didDragMarkerWithIdentifier:markerId location:marker.position];
 }
 
 - (void)mapView:(GMSMapView *)mapView didTapInfoWindowOfMarker:(GMSMarker *)marker {
   NSString *markerId = marker.userData[0];
-  [_markersController onInfoWindowTap:markerId];
+  [self.markersController didTapInfoWindowOfMarkerWithIdentifier:markerId];
 }
 - (void)mapView:(GMSMapView *)mapView didTapOverlay:(GMSOverlay *)overlay {
   NSString *overlayId = overlay.userData[0];
-  if ([_polylinesController hasPolylineWithId:overlayId]) {
-    [_polylinesController onPolylineTap:overlayId];
-  } else if ([_polygonsController hasPolygonWithId:overlayId]) {
-    [_polygonsController onPolygonTap:overlayId];
-  } else if ([_circlesController hasCircleWithId:overlayId]) {
-    [_circlesController onCircleTap:overlayId];
+  if ([self.polylinesController hasPolylineWithIdentifier:overlayId]) {
+    [self.polylinesController didTapPolylineWithIdentifier:overlayId];
+  } else if ([self.polygonsController hasPolygonWithIdentifier:overlayId]) {
+    [self.polygonsController didTapPolygonWithIdentifier:overlayId];
+  } else if ([self.circlesController hasCircleWithIdentifier:overlayId]) {
+    [self.circlesController didTapCircleWithIdentifier:overlayId];
   }
 }
 
 - (void)mapView:(GMSMapView *)mapView didTapAtCoordinate:(CLLocationCoordinate2D)coordinate {
-  [_channel invokeMethod:@"map#onTap" arguments:@{@"position" : LocationToJson(coordinate)}];
+  [self.channel
+      invokeMethod:@"map#onTap"
+         arguments:@{@"position" : [FLTGoogleMapJSONConversions arrayFromLocation:coordinate]}];
 }
 
 - (void)mapView:(GMSMapView *)mapView didLongPressAtCoordinate:(CLLocationCoordinate2D)coordinate {
-  [_channel invokeMethod:@"map#onLongPress" arguments:@{@"position" : LocationToJson(coordinate)}];
+  [self.channel
+      invokeMethod:@"map#onLongPress"
+         arguments:@{@"position" : [FLTGoogleMapJSONConversions arrayFromLocation:coordinate]}];
 }
 
-@end
-
-#pragma mark - Implementations of JSON conversion functions.
-
-static NSArray *LocationToJson(CLLocationCoordinate2D position) {
-  return @[ @(position.latitude), @(position.longitude) ];
-}
-
-static NSDictionary *PositionToJson(GMSCameraPosition *position) {
-  if (!position) {
-    return nil;
-  }
-  return @{
-    @"target" : LocationToJson([position target]),
-    @"zoom" : @([position zoom]),
-    @"bearing" : @([position bearing]),
-    @"tilt" : @([position viewingAngle]),
-  };
-}
-
-static NSDictionary *PointToJson(CGPoint point) {
-  return @{
-    @"x" : @(lroundf(point.x)),
-    @"y" : @(lroundf(point.y)),
-  };
-}
-
-static NSDictionary *GMSCoordinateBoundsToJson(GMSCoordinateBounds *bounds) {
-  if (!bounds) {
-    return nil;
-  }
-  return @{
-    @"southwest" : LocationToJson([bounds southWest]),
-    @"northeast" : LocationToJson([bounds northEast]),
-  };
-}
-
-static float ToFloat(NSNumber *data) { return [FLTGoogleMapJsonConversions toFloat:data]; }
-
-static CLLocationCoordinate2D ToLocation(NSArray *data) {
-  return [FLTGoogleMapJsonConversions toLocation:data];
-}
-
-static int ToInt(NSNumber *data) { return [FLTGoogleMapJsonConversions toInt:data]; }
-
-static BOOL ToBool(NSNumber *data) { return [FLTGoogleMapJsonConversions toBool:data]; }
-
-static CGPoint ToPoint(NSArray *data) { return [FLTGoogleMapJsonConversions toPoint:data]; }
-
-static GMSCameraPosition *ToCameraPosition(NSDictionary *data) {
-  return [GMSCameraPosition cameraWithTarget:ToLocation(data[@"target"])
-                                        zoom:ToFloat(data[@"zoom"])
-                                     bearing:ToDouble(data[@"bearing"])
-                                viewingAngle:ToDouble(data[@"tilt"])];
-}
-
-static GMSCameraPosition *ToOptionalCameraPosition(NSDictionary *json) {
-  return json ? ToCameraPosition(json) : nil;
-}
-
-static CGPoint ToCGPoint(NSDictionary *json) {
-  double x = ToDouble(json[@"x"]);
-  double y = ToDouble(json[@"y"]);
-  return CGPointMake(x, y);
-}
-
-static GMSCoordinateBounds *ToBounds(NSArray *data) {
-  return [[GMSCoordinateBounds alloc] initWithCoordinate:ToLocation(data[0])
-                                              coordinate:ToLocation(data[1])];
-}
-
-static GMSCoordinateBounds *ToOptionalBounds(NSArray *data) {
-  return (data[0] == [NSNull null]) ? nil : ToBounds(data[0]);
-}
-
-static GMSMapViewType ToMapViewType(NSNumber *json) {
-  int value = ToInt(json);
-  return (GMSMapViewType)(value == 0 ? 5 : value);
-}
-
-static GMSCameraUpdate *ToCameraUpdate(NSArray *data) {
-  NSString *update = data[0];
-  if ([update isEqualToString:@"newCameraPosition"]) {
-    return [GMSCameraUpdate setCamera:ToCameraPosition(data[1])];
-  } else if ([update isEqualToString:@"newLatLng"]) {
-    return [GMSCameraUpdate setTarget:ToLocation(data[1])];
-  } else if ([update isEqualToString:@"newLatLngBounds"]) {
-    return [GMSCameraUpdate fitBounds:ToBounds(data[1]) withPadding:ToDouble(data[2])];
-  } else if ([update isEqualToString:@"newLatLngZoom"]) {
-    return [GMSCameraUpdate setTarget:ToLocation(data[1]) zoom:ToFloat(data[2])];
-  } else if ([update isEqualToString:@"scrollBy"]) {
-    return [GMSCameraUpdate scrollByX:ToDouble(data[1]) Y:ToDouble(data[2])];
-  } else if ([update isEqualToString:@"zoomBy"]) {
-    if (data.count == 2) {
-      return [GMSCameraUpdate zoomBy:ToFloat(data[1])];
-    } else {
-      return [GMSCameraUpdate zoomBy:ToFloat(data[1]) atPoint:ToPoint(data[2])];
-    }
-  } else if ([update isEqualToString:@"zoomIn"]) {
-    return [GMSCameraUpdate zoomIn];
-  } else if ([update isEqualToString:@"zoomOut"]) {
-    return [GMSCameraUpdate zoomOut];
-  } else if ([update isEqualToString:@"zoomTo"]) {
-    return [GMSCameraUpdate zoomTo:ToFloat(data[1])];
-  }
-  return nil;
-}
-
-static void InterpretMapOptions(NSDictionary *data, id<FLTGoogleMapOptionsSink> sink) {
+- (void)interpretMapOptions:(NSDictionary *)data {
   NSArray *cameraTargetBounds = data[@"cameraTargetBounds"];
-  if (cameraTargetBounds) {
-    [sink setCameraTargetBounds:ToOptionalBounds(cameraTargetBounds)];
+  if (cameraTargetBounds && cameraTargetBounds != (id)[NSNull null]) {
+    [self
+        setCameraTargetBounds:cameraTargetBounds.count > 0 && cameraTargetBounds[0] != [NSNull null]
+                                  ? [FLTGoogleMapJSONConversions
+                                        coordinateBoundsFromLatLongs:cameraTargetBounds.firstObject]
+                                  : nil];
   }
   NSNumber *compassEnabled = data[@"compassEnabled"];
-  if (compassEnabled != nil) {
-    [sink setCompassEnabled:ToBool(compassEnabled)];
+  if (compassEnabled && compassEnabled != (id)[NSNull null]) {
+    [self setCompassEnabled:[compassEnabled boolValue]];
   }
   id indoorEnabled = data[@"indoorEnabled"];
-  if (indoorEnabled) {
-    [sink setIndoorEnabled:ToBool(indoorEnabled)];
+  if (indoorEnabled && indoorEnabled != [NSNull null]) {
+    [self setIndoorEnabled:[indoorEnabled boolValue]];
   }
   id trafficEnabled = data[@"trafficEnabled"];
-  if (trafficEnabled) {
-    [sink setTrafficEnabled:ToBool(trafficEnabled)];
+  if (trafficEnabled && trafficEnabled != [NSNull null]) {
+    [self setTrafficEnabled:[trafficEnabled boolValue]];
   }
   id buildingsEnabled = data[@"buildingsEnabled"];
-  if (buildingsEnabled) {
-    [sink setBuildingsEnabled:ToBool(buildingsEnabled)];
+  if (buildingsEnabled && buildingsEnabled != [NSNull null]) {
+    [self setBuildingsEnabled:[buildingsEnabled boolValue]];
   }
   id mapType = data[@"mapType"];
-  if (mapType) {
-    [sink setMapType:ToMapViewType(mapType)];
+  if (mapType && mapType != [NSNull null]) {
+    [self setMapType:[FLTGoogleMapJSONConversions mapViewTypeFromTypeValue:mapType]];
   }
   NSArray *zoomData = data[@"minMaxZoomPreference"];
-  if (zoomData) {
-    float minZoom = (zoomData[0] == [NSNull null]) ? kGMSMinZoomLevel : ToFloat(zoomData[0]);
-    float maxZoom = (zoomData[1] == [NSNull null]) ? kGMSMaxZoomLevel : ToFloat(zoomData[1]);
-    [sink setMinZoom:minZoom maxZoom:maxZoom];
+  if (zoomData && zoomData != (id)[NSNull null]) {
+    float minZoom = (zoomData[0] == [NSNull null]) ? kGMSMinZoomLevel : [zoomData[0] floatValue];
+    float maxZoom = (zoomData[1] == [NSNull null]) ? kGMSMaxZoomLevel : [zoomData[1] floatValue];
+    [self setMinZoom:minZoom maxZoom:maxZoom];
   }
   NSArray *paddingData = data[@"padding"];
   if (paddingData) {
-    float top = (paddingData[0] == [NSNull null]) ? 0 : ToFloat(paddingData[0]);
-    float left = (paddingData[1] == [NSNull null]) ? 0 : ToFloat(paddingData[1]);
-    float bottom = (paddingData[2] == [NSNull null]) ? 0 : ToFloat(paddingData[2]);
-    float right = (paddingData[3] == [NSNull null]) ? 0 : ToFloat(paddingData[3]);
-    [sink setPaddingTop:top left:left bottom:bottom right:right];
+    float top = (paddingData[0] == [NSNull null]) ? 0 : [paddingData[0] floatValue];
+    float left = (paddingData[1] == [NSNull null]) ? 0 : [paddingData[1] floatValue];
+    float bottom = (paddingData[2] == [NSNull null]) ? 0 : [paddingData[2] floatValue];
+    float right = (paddingData[3] == [NSNull null]) ? 0 : [paddingData[3] floatValue];
+    [self setPaddingTop:top left:left bottom:bottom right:right];
   }
 
   NSNumber *rotateGesturesEnabled = data[@"rotateGesturesEnabled"];
-  if (rotateGesturesEnabled != nil) {
-    [sink setRotateGesturesEnabled:ToBool(rotateGesturesEnabled)];
+  if (rotateGesturesEnabled && rotateGesturesEnabled != (id)[NSNull null]) {
+    [self setRotateGesturesEnabled:[rotateGesturesEnabled boolValue]];
   }
   NSNumber *scrollGesturesEnabled = data[@"scrollGesturesEnabled"];
-  if (scrollGesturesEnabled != nil) {
-    [sink setScrollGesturesEnabled:ToBool(scrollGesturesEnabled)];
+  if (scrollGesturesEnabled && scrollGesturesEnabled != (id)[NSNull null]) {
+    [self setScrollGesturesEnabled:[scrollGesturesEnabled boolValue]];
   }
   NSNumber *tiltGesturesEnabled = data[@"tiltGesturesEnabled"];
-  if (tiltGesturesEnabled != nil) {
-    [sink setTiltGesturesEnabled:ToBool(tiltGesturesEnabled)];
+  if (tiltGesturesEnabled && tiltGesturesEnabled != (id)[NSNull null]) {
+    [self setTiltGesturesEnabled:[tiltGesturesEnabled boolValue]];
   }
   NSNumber *trackCameraPosition = data[@"trackCameraPosition"];
-  if (trackCameraPosition != nil) {
-    [sink setTrackCameraPosition:ToBool(trackCameraPosition)];
+  if (trackCameraPosition && trackCameraPosition != (id)[NSNull null]) {
+    [self setTrackCameraPosition:[trackCameraPosition boolValue]];
   }
   NSNumber *zoomGesturesEnabled = data[@"zoomGesturesEnabled"];
-  if (zoomGesturesEnabled != nil) {
-    [sink setZoomGesturesEnabled:ToBool(zoomGesturesEnabled)];
+  if (zoomGesturesEnabled && zoomGesturesEnabled != (id)[NSNull null]) {
+    [self setZoomGesturesEnabled:[zoomGesturesEnabled boolValue]];
   }
   NSNumber *myLocationEnabled = data[@"myLocationEnabled"];
-  if (myLocationEnabled != nil) {
-    [sink setMyLocationEnabled:ToBool(myLocationEnabled)];
+  if (myLocationEnabled && myLocationEnabled != (id)[NSNull null]) {
+    [self setMyLocationEnabled:[myLocationEnabled boolValue]];
   }
   NSNumber *myLocationButtonEnabled = data[@"myLocationButtonEnabled"];
-  if (myLocationButtonEnabled != nil) {
-    [sink setMyLocationButtonEnabled:ToBool(myLocationButtonEnabled)];
+  if (myLocationButtonEnabled && myLocationButtonEnabled != (id)[NSNull null]) {
+    [self setMyLocationButtonEnabled:[myLocationButtonEnabled boolValue]];
   }
 }
+
+@end
diff --git a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapMarkerController.h b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapMarkerController.h
index 8734c06..a33d480 100644
--- a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapMarkerController.h
+++ b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapMarkerController.h
@@ -8,50 +8,37 @@
 
 NS_ASSUME_NONNULL_BEGIN
 
-// Defines marker UI options writable from Flutter.
-@protocol FLTGoogleMapMarkerOptionsSink
-- (void)setAlpha:(float)alpha;
-- (void)setAnchor:(CGPoint)anchor;
-- (void)setConsumeTapEvents:(BOOL)consume;
-- (void)setDraggable:(BOOL)draggable;
-- (void)setFlat:(BOOL)flat;
-- (void)setIcon:(UIImage *)icon;
-- (void)setInfoWindowAnchor:(CGPoint)anchor;
-- (void)setInfoWindowTitle:(NSString *)title snippet:(NSString *)snippet;
-- (void)setPosition:(CLLocationCoordinate2D)position;
-- (void)setRotation:(CLLocationDegrees)rotation;
-- (void)setVisible:(BOOL)visible;
-- (void)setZIndex:(int)zIndex;
-@end
-
 // Defines marker controllable by Flutter.
-@interface FLTGoogleMapMarkerController : NSObject <FLTGoogleMapMarkerOptionsSink>
-@property(atomic, readonly) NSString *markerId;
+@interface FLTGoogleMapMarkerController : NSObject
+@property(assign, nonatomic, readonly) BOOL consumeTapEvents;
 - (instancetype)initMarkerWithPosition:(CLLocationCoordinate2D)position
-                              markerId:(NSString *)markerId
+                            identifier:(NSString *)identifier
                                mapView:(GMSMapView *)mapView;
 - (void)showInfoWindow;
 - (void)hideInfoWindow;
 - (BOOL)isInfoWindowShown;
-- (BOOL)consumeTapEvents;
 - (void)removeMarker;
 @end
 
 @interface FLTMarkersController : NSObject
-- (instancetype)init:(FlutterMethodChannel *)methodChannel
-             mapView:(GMSMapView *)mapView
-           registrar:(NSObject<FlutterPluginRegistrar> *)registrar;
+- (instancetype)initWithMethodChannel:(FlutterMethodChannel *)methodChannel
+                              mapView:(GMSMapView *)mapView
+                            registrar:(NSObject<FlutterPluginRegistrar> *)registrar;
 - (void)addMarkers:(NSArray *)markersToAdd;
 - (void)changeMarkers:(NSArray *)markersToChange;
-- (void)removeMarkerIds:(NSArray *)markerIdsToRemove;
-- (BOOL)onMarkerTap:(NSString *)markerId;
-- (void)onMarkerDragStart:(NSString *)markerId coordinate:(CLLocationCoordinate2D)coordinate;
-- (void)onMarkerDragEnd:(NSString *)markerId coordinate:(CLLocationCoordinate2D)coordinate;
-- (void)onMarkerDrag:(NSString *)markerId coordinate:(CLLocationCoordinate2D)coordinate;
-- (void)onInfoWindowTap:(NSString *)markerId;
-- (void)showMarkerInfoWindow:(NSString *)markerId result:(FlutterResult)result;
-- (void)hideMarkerInfoWindow:(NSString *)markerId result:(FlutterResult)result;
-- (void)isMarkerInfoWindowShown:(NSString *)markerId result:(FlutterResult)result;
+- (void)removeMarkersWithIdentifiers:(NSArray *)identifiers;
+- (BOOL)didTapMarkerWithIdentifier:(NSString *)identifier;
+- (void)didStartDraggingMarkerWithIdentifier:(NSString *)identifier
+                                    location:(CLLocationCoordinate2D)coordinate;
+- (void)didEndDraggingMarkerWithIdentifier:(NSString *)identifier
+                                  location:(CLLocationCoordinate2D)coordinate;
+- (void)didDragMarkerWithIdentifier:(NSString *)identifier
+                           location:(CLLocationCoordinate2D)coordinate;
+- (void)didTapInfoWindowOfMarkerWithIdentifier:(NSString *)identifier;
+- (void)showMarkerInfoWindowWithIdentifier:(NSString *)identifier result:(FlutterResult)result;
+- (void)hideMarkerInfoWindowWithIdentifier:(NSString *)identifier result:(FlutterResult)result;
+- (void)isInfoWindowShownForMarkerWithIdentifier:(NSString *)identifier
+                                          result:(FlutterResult)result;
 @end
 
 NS_ASSUME_NONNULL_END
diff --git a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapMarkerController.m b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapMarkerController.m
index c2877e2..dd07e79 100644
--- a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapMarkerController.m
+++ b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapMarkerController.m
@@ -3,184 +3,159 @@
 // found in the LICENSE file.
 
 #import "GoogleMapMarkerController.h"
-#import "JsonConversions.h"
+#import "FLTGoogleMapJSONConversions.h"
 
-static UIImage *ExtractIcon(NSObject<FlutterPluginRegistrar> *registrar, NSArray *icon);
-static void InterpretInfoWindow(id<FLTGoogleMapMarkerOptionsSink> sink, NSDictionary *data);
+@interface FLTGoogleMapMarkerController ()
 
-@implementation FLTGoogleMapMarkerController {
-  GMSMarker *_marker;
-  GMSMapView *_mapView;
-  BOOL _consumeTapEvents;
-}
+@property(strong, nonatomic) GMSMarker *marker;
+@property(weak, nonatomic) GMSMapView *mapView;
+@property(assign, nonatomic, readwrite) BOOL consumeTapEvents;
+
+@end
+
+@implementation FLTGoogleMapMarkerController
+
 - (instancetype)initMarkerWithPosition:(CLLocationCoordinate2D)position
-                              markerId:(NSString *)markerId
+                            identifier:(NSString *)identifier
                                mapView:(GMSMapView *)mapView {
   self = [super init];
   if (self) {
     _marker = [GMSMarker markerWithPosition:position];
     _mapView = mapView;
-    _markerId = markerId;
-    _marker.userData = @[ _markerId ];
-    _consumeTapEvents = NO;
+    _marker.userData = @[ identifier ];
   }
   return self;
 }
+
 - (void)showInfoWindow {
-  _mapView.selectedMarker = _marker;
-}
-- (void)hideInfoWindow {
-  if (_mapView.selectedMarker == _marker) {
-    _mapView.selectedMarker = nil;
-  }
-}
-- (BOOL)isInfoWindowShown {
-  return _mapView.selectedMarker == _marker;
-}
-- (BOOL)consumeTapEvents {
-  return _consumeTapEvents;
-}
-- (void)removeMarker {
-  _marker.map = nil;
+  self.mapView.selectedMarker = self.marker;
 }
 
-#pragma mark - FLTGoogleMapMarkerOptionsSink methods
+- (void)hideInfoWindow {
+  if (self.mapView.selectedMarker == self.marker) {
+    self.mapView.selectedMarker = nil;
+  }
+}
+
+- (BOOL)isInfoWindowShown {
+  return self.mapView.selectedMarker == self.marker;
+}
+
+- (void)removeMarker {
+  self.marker.map = nil;
+}
 
 - (void)setAlpha:(float)alpha {
-  _marker.opacity = alpha;
+  self.marker.opacity = alpha;
 }
+
 - (void)setAnchor:(CGPoint)anchor {
-  _marker.groundAnchor = anchor;
+  self.marker.groundAnchor = anchor;
 }
-- (void)setConsumeTapEvents:(BOOL)consumes {
-  _consumeTapEvents = consumes;
-}
+
 - (void)setDraggable:(BOOL)draggable {
-  _marker.draggable = draggable;
+  self.marker.draggable = draggable;
 }
+
 - (void)setFlat:(BOOL)flat {
-  _marker.flat = flat;
+  self.marker.flat = flat;
 }
+
 - (void)setIcon:(UIImage *)icon {
-  _marker.icon = icon;
+  self.marker.icon = icon;
 }
+
 - (void)setInfoWindowAnchor:(CGPoint)anchor {
-  _marker.infoWindowAnchor = anchor;
+  self.marker.infoWindowAnchor = anchor;
 }
+
 - (void)setInfoWindowTitle:(NSString *)title snippet:(NSString *)snippet {
-  _marker.title = title;
-  _marker.snippet = snippet;
+  self.marker.title = title;
+  self.marker.snippet = snippet;
 }
+
 - (void)setPosition:(CLLocationCoordinate2D)position {
-  _marker.position = position;
+  self.marker.position = position;
 }
+
 - (void)setRotation:(CLLocationDegrees)rotation {
-  _marker.rotation = rotation;
+  self.marker.rotation = rotation;
 }
+
 - (void)setVisible:(BOOL)visible {
-  _marker.map = visible ? _mapView : nil;
+  self.marker.map = visible ? self.mapView : nil;
 }
+
 - (void)setZIndex:(int)zIndex {
-  _marker.zIndex = zIndex;
-}
-@end
-
-static double ToDouble(NSNumber *data) { return [FLTGoogleMapJsonConversions toDouble:data]; }
-
-static float ToFloat(NSNumber *data) { return [FLTGoogleMapJsonConversions toFloat:data]; }
-
-static CLLocationCoordinate2D ToLocation(NSArray *data) {
-  return [FLTGoogleMapJsonConversions toLocation:data];
+  self.marker.zIndex = zIndex;
 }
 
-static int ToInt(NSNumber *data) { return [FLTGoogleMapJsonConversions toInt:data]; }
-
-static BOOL ToBool(NSNumber *data) { return [FLTGoogleMapJsonConversions toBool:data]; }
-
-static CGPoint ToPoint(NSArray *data) { return [FLTGoogleMapJsonConversions toPoint:data]; }
-
-static NSArray *PositionToJson(CLLocationCoordinate2D data) {
-  return [FLTGoogleMapJsonConversions positionToJson:data];
-}
-
-static void InterpretMarkerOptions(NSDictionary *data, id<FLTGoogleMapMarkerOptionsSink> sink,
-                                   NSObject<FlutterPluginRegistrar> *registrar) {
+- (void)interpretMarkerOptions:(NSDictionary *)data
+                     registrar:(NSObject<FlutterPluginRegistrar> *)registrar {
   NSNumber *alpha = data[@"alpha"];
-  if (alpha != nil) {
-    [sink setAlpha:ToFloat(alpha)];
+  if (alpha && alpha != (id)[NSNull null]) {
+    [self setAlpha:[alpha floatValue]];
   }
   NSArray *anchor = data[@"anchor"];
-  if (anchor) {
-    [sink setAnchor:ToPoint(anchor)];
+  if (anchor && anchor != (id)[NSNull null]) {
+    [self setAnchor:[FLTGoogleMapJSONConversions pointFromArray:anchor]];
   }
   NSNumber *draggable = data[@"draggable"];
-  if (draggable != nil) {
-    [sink setDraggable:ToBool(draggable)];
+  if (draggable && draggable != (id)[NSNull null]) {
+    [self setDraggable:[draggable boolValue]];
   }
   NSArray *icon = data[@"icon"];
-  if (icon) {
-    UIImage *image = ExtractIcon(registrar, icon);
-    [sink setIcon:image];
+  if (icon && icon != (id)[NSNull null]) {
+    UIImage *image = [self extractIconFromData:icon registrar:registrar];
+    [self setIcon:image];
   }
   NSNumber *flat = data[@"flat"];
-  if (flat != nil) {
-    [sink setFlat:ToBool(flat)];
+  if (flat && flat != (id)[NSNull null]) {
+    [self setFlat:[flat boolValue]];
   }
   NSNumber *consumeTapEvents = data[@"consumeTapEvents"];
-  if (consumeTapEvents != nil) {
-    [sink setConsumeTapEvents:ToBool(consumeTapEvents)];
+  if (consumeTapEvents && consumeTapEvents != (id)[NSNull null]) {
+    [self setConsumeTapEvents:[consumeTapEvents boolValue]];
   }
-  InterpretInfoWindow(sink, data);
+  [self interpretInfoWindow:data];
   NSArray *position = data[@"position"];
-  if (position) {
-    [sink setPosition:ToLocation(position)];
+  if (position && position != (id)[NSNull null]) {
+    [self setPosition:[FLTGoogleMapJSONConversions locationFromLatLong:position]];
   }
   NSNumber *rotation = data[@"rotation"];
-  if (rotation != nil) {
-    [sink setRotation:ToDouble(rotation)];
+  if (rotation && rotation != (id)[NSNull null]) {
+    [self setRotation:[rotation doubleValue]];
   }
   NSNumber *visible = data[@"visible"];
-  if (visible != nil) {
-    [sink setVisible:ToBool(visible)];
+  if (visible && visible != (id)[NSNull null]) {
+    [self setVisible:[visible boolValue]];
   }
   NSNumber *zIndex = data[@"zIndex"];
-  if (zIndex != nil) {
-    [sink setZIndex:ToInt(zIndex)];
+  if (zIndex && zIndex != (id)[NSNull null]) {
+    [self setZIndex:[zIndex intValue]];
   }
 }
 
-static void InterpretInfoWindow(id<FLTGoogleMapMarkerOptionsSink> sink, NSDictionary *data) {
+- (void)interpretInfoWindow:(NSDictionary *)data {
   NSDictionary *infoWindow = data[@"infoWindow"];
-  if (infoWindow) {
+  if (infoWindow && infoWindow != (id)[NSNull null]) {
     NSString *title = infoWindow[@"title"];
     NSString *snippet = infoWindow[@"snippet"];
-    if (title) {
-      [sink setInfoWindowTitle:title snippet:snippet];
+    if (title && title != (id)[NSNull null]) {
+      [self setInfoWindowTitle:title snippet:snippet];
     }
     NSArray *infoWindowAnchor = infoWindow[@"infoWindowAnchor"];
-    if (infoWindowAnchor) {
-      [sink setInfoWindowAnchor:ToPoint(infoWindowAnchor)];
+    if (infoWindowAnchor && infoWindowAnchor != (id)[NSNull null]) {
+      [self setInfoWindowAnchor:[FLTGoogleMapJSONConversions pointFromArray:infoWindowAnchor]];
     }
   }
 }
 
-static UIImage *scaleImage(UIImage *image, NSNumber *scaleParam) {
-  double scale = 1.0;
-  if ([scaleParam isKindOfClass:[NSNumber class]]) {
-    scale = scaleParam.doubleValue;
-  }
-  if (fabs(scale - 1) > 1e-3) {
-    return [UIImage imageWithCGImage:[image CGImage]
-                               scale:(image.scale * scale)
-                         orientation:(image.imageOrientation)];
-  }
-  return image;
-}
-
-static UIImage *ExtractIcon(NSObject<FlutterPluginRegistrar> *registrar, NSArray *iconData) {
+- (UIImage *)extractIconFromData:(NSArray *)iconData
+                       registrar:(NSObject<FlutterPluginRegistrar> *)registrar {
   UIImage *image;
   if ([iconData.firstObject isEqualToString:@"defaultMarker"]) {
-    CGFloat hue = (iconData.count == 1) ? 0.0f : ToDouble(iconData[1]);
+    CGFloat hue = (iconData.count == 1) ? 0.0f : [iconData[1] doubleValue];
     image = [GMSMarker markerImageWithColor:[UIColor colorWithHue:hue / 360.0
                                                        saturation:1.0
                                                        brightness:0.7
@@ -195,8 +170,8 @@
   } else if ([iconData.firstObject isEqualToString:@"fromAssetImage"]) {
     if (iconData.count == 3) {
       image = [UIImage imageNamed:[registrar lookupKeyForAsset:iconData[1]]];
-      NSNumber *scaleParam = iconData[2];
-      image = scaleImage(image, scaleParam);
+      id scaleParam = iconData[2];
+      image = [self scaleImage:image by:scaleParam];
     } else {
       NSString *error =
           [NSString stringWithFormat:@"'fromAssetImage' should have exactly 3 arguments. Got: %lu",
@@ -231,110 +206,145 @@
   return image;
 }
 
-@implementation FLTMarkersController {
-  NSMutableDictionary *_markerIdToController;
-  FlutterMethodChannel *_methodChannel;
-  NSObject<FlutterPluginRegistrar> *_registrar;
-  GMSMapView *_mapView;
+- (UIImage *)scaleImage:(UIImage *)image by:(id)scaleParam {
+  double scale = 1.0;
+  if ([scaleParam isKindOfClass:[NSNumber class]]) {
+    scale = [scaleParam doubleValue];
+  }
+  if (fabs(scale - 1) > 1e-3) {
+    return [UIImage imageWithCGImage:[image CGImage]
+                               scale:(image.scale * scale)
+                         orientation:(image.imageOrientation)];
+  }
+  return image;
 }
-- (instancetype)init:(FlutterMethodChannel *)methodChannel
-             mapView:(GMSMapView *)mapView
-           registrar:(NSObject<FlutterPluginRegistrar> *)registrar {
+
+@end
+
+@interface FLTMarkersController ()
+
+@property(strong, nonatomic) NSMutableDictionary *markerIdentifierToController;
+@property(strong, nonatomic) FlutterMethodChannel *methodChannel;
+@property(weak, nonatomic) NSObject<FlutterPluginRegistrar> *registrar;
+@property(weak, nonatomic) GMSMapView *mapView;
+
+@end
+
+@implementation FLTMarkersController
+
+- (instancetype)initWithMethodChannel:(FlutterMethodChannel *)methodChannel
+                              mapView:(GMSMapView *)mapView
+                            registrar:(NSObject<FlutterPluginRegistrar> *)registrar {
   self = [super init];
   if (self) {
     _methodChannel = methodChannel;
     _mapView = mapView;
-    _markerIdToController = [NSMutableDictionary dictionaryWithCapacity:1];
+    _markerIdentifierToController = [[NSMutableDictionary alloc] init];
     _registrar = registrar;
   }
   return self;
 }
+
 - (void)addMarkers:(NSArray *)markersToAdd {
   for (NSDictionary *marker in markersToAdd) {
     CLLocationCoordinate2D position = [FLTMarkersController getPosition:marker];
-    NSString *markerId = [FLTMarkersController getMarkerId:marker];
+    NSString *identifier = marker[@"markerId"];
     FLTGoogleMapMarkerController *controller =
         [[FLTGoogleMapMarkerController alloc] initMarkerWithPosition:position
-                                                            markerId:markerId
-                                                             mapView:_mapView];
-    InterpretMarkerOptions(marker, controller, _registrar);
-    _markerIdToController[markerId] = controller;
+                                                          identifier:identifier
+                                                             mapView:self.mapView];
+    [controller interpretMarkerOptions:marker registrar:self.registrar];
+    self.markerIdentifierToController[identifier] = controller;
   }
 }
+
 - (void)changeMarkers:(NSArray *)markersToChange {
   for (NSDictionary *marker in markersToChange) {
-    NSString *markerId = [FLTMarkersController getMarkerId:marker];
-    FLTGoogleMapMarkerController *controller = _markerIdToController[markerId];
+    NSString *identifier = marker[@"markerId"];
+    FLTGoogleMapMarkerController *controller = self.markerIdentifierToController[identifier];
     if (!controller) {
       continue;
     }
-    InterpretMarkerOptions(marker, controller, _registrar);
+    [controller interpretMarkerOptions:marker registrar:self.registrar];
   }
 }
-- (void)removeMarkerIds:(NSArray *)markerIdsToRemove {
-  for (NSString *markerId in markerIdsToRemove) {
-    if (!markerId) {
-      continue;
-    }
-    FLTGoogleMapMarkerController *controller = _markerIdToController[markerId];
+
+- (void)removeMarkersWithIdentifiers:(NSArray *)identifiers {
+  for (NSString *identifier in identifiers) {
+    FLTGoogleMapMarkerController *controller = self.markerIdentifierToController[identifier];
     if (!controller) {
       continue;
     }
     [controller removeMarker];
-    [_markerIdToController removeObjectForKey:markerId];
+    [self.markerIdentifierToController removeObjectForKey:identifier];
   }
 }
-- (BOOL)onMarkerTap:(NSString *)markerId {
-  if (!markerId) {
+
+- (BOOL)didTapMarkerWithIdentifier:(NSString *)identifier {
+  if (!identifier) {
     return NO;
   }
-  FLTGoogleMapMarkerController *controller = _markerIdToController[markerId];
+  FLTGoogleMapMarkerController *controller = self.markerIdentifierToController[identifier];
   if (!controller) {
     return NO;
   }
-  [_methodChannel invokeMethod:@"marker#onTap" arguments:@{@"markerId" : markerId}];
+  [self.methodChannel invokeMethod:@"marker#onTap" arguments:@{@"markerId" : identifier}];
   return controller.consumeTapEvents;
 }
-- (void)onMarkerDragStart:(NSString *)markerId coordinate:(CLLocationCoordinate2D)coordinate {
-  if (!markerId) {
+
+- (void)didStartDraggingMarkerWithIdentifier:(NSString *)identifier
+                                    location:(CLLocationCoordinate2D)location {
+  if (!identifier) {
     return;
   }
-  FLTGoogleMapMarkerController *controller = _markerIdToController[markerId];
+  FLTGoogleMapMarkerController *controller = self.markerIdentifierToController[identifier];
   if (!controller) {
     return;
   }
-  [_methodChannel invokeMethod:@"marker#onDragStart"
-                     arguments:@{@"markerId" : markerId, @"position" : PositionToJson(coordinate)}];
+  [self.methodChannel invokeMethod:@"marker#onDragStart"
+                         arguments:@{
+                           @"markerId" : identifier,
+                           @"position" : [FLTGoogleMapJSONConversions arrayFromLocation:location]
+                         }];
 }
-- (void)onMarkerDrag:(NSString *)markerId coordinate:(CLLocationCoordinate2D)coordinate {
-  if (!markerId) {
+
+- (void)didDragMarkerWithIdentifier:(NSString *)identifier
+                           location:(CLLocationCoordinate2D)location {
+  if (!identifier) {
     return;
   }
-  FLTGoogleMapMarkerController *controller = _markerIdToController[markerId];
+  FLTGoogleMapMarkerController *controller = self.markerIdentifierToController[identifier];
   if (!controller) {
     return;
   }
-  [_methodChannel invokeMethod:@"marker#onDrag"
-                     arguments:@{@"markerId" : markerId, @"position" : PositionToJson(coordinate)}];
+  [self.methodChannel invokeMethod:@"marker#onDrag"
+                         arguments:@{
+                           @"markerId" : identifier,
+                           @"position" : [FLTGoogleMapJSONConversions arrayFromLocation:location]
+                         }];
 }
-- (void)onMarkerDragEnd:(NSString *)markerId coordinate:(CLLocationCoordinate2D)coordinate {
-  if (!markerId) {
-    return;
-  }
-  FLTGoogleMapMarkerController *controller = _markerIdToController[markerId];
+
+- (void)didEndDraggingMarkerWithIdentifier:(NSString *)identifier
+                                  location:(CLLocationCoordinate2D)location {
+  FLTGoogleMapMarkerController *controller = self.markerIdentifierToController[identifier];
   if (!controller) {
     return;
   }
-  [_methodChannel invokeMethod:@"marker#onDragEnd"
-                     arguments:@{@"markerId" : markerId, @"position" : PositionToJson(coordinate)}];
+  [self.methodChannel invokeMethod:@"marker#onDragEnd"
+                         arguments:@{
+                           @"markerId" : identifier,
+                           @"position" : [FLTGoogleMapJSONConversions arrayFromLocation:location]
+                         }];
 }
-- (void)onInfoWindowTap:(NSString *)markerId {
-  if (markerId && _markerIdToController[markerId]) {
-    [_methodChannel invokeMethod:@"infoWindow#onTap" arguments:@{@"markerId" : markerId}];
+
+- (void)didTapInfoWindowOfMarkerWithIdentifier:(NSString *)identifier {
+  if (identifier && self.markerIdentifierToController[identifier]) {
+    [self.methodChannel invokeMethod:@"infoWindow#onTap" arguments:@{@"markerId" : identifier}];
   }
 }
-- (void)showMarkerInfoWindow:(NSString *)markerId result:(FlutterResult)result {
-  FLTGoogleMapMarkerController *controller = _markerIdToController[markerId];
+
+- (void)showMarkerInfoWindowWithIdentifier:(NSString *)identifier result:(FlutterResult)result {
+  FLTGoogleMapMarkerController *controller = self.markerIdentifierToController[identifier];
   if (controller) {
     [controller showInfoWindow];
     result(nil);
@@ -344,8 +354,9 @@
                                details:nil]);
   }
 }
-- (void)hideMarkerInfoWindow:(NSString *)markerId result:(FlutterResult)result {
-  FLTGoogleMapMarkerController *controller = _markerIdToController[markerId];
+
+- (void)hideMarkerInfoWindowWithIdentifier:(NSString *)identifier result:(FlutterResult)result {
+  FLTGoogleMapMarkerController *controller = self.markerIdentifierToController[identifier];
   if (controller) {
     [controller hideInfoWindow];
     result(nil);
@@ -355,8 +366,10 @@
                                details:nil]);
   }
 }
-- (void)isMarkerInfoWindowShown:(NSString *)markerId result:(FlutterResult)result {
-  FLTGoogleMapMarkerController *controller = _markerIdToController[markerId];
+
+- (void)isInfoWindowShownForMarkerWithIdentifier:(NSString *)identifier
+                                          result:(FlutterResult)result {
+  FLTGoogleMapMarkerController *controller = self.markerIdentifierToController[identifier];
   if (controller) {
     result(@([controller isInfoWindowShown]));
   } else {
@@ -368,9 +381,7 @@
 
 + (CLLocationCoordinate2D)getPosition:(NSDictionary *)marker {
   NSArray *position = marker[@"position"];
-  return ToLocation(position);
+  return [FLTGoogleMapJSONConversions locationFromLatLong:position];
 }
-+ (NSString *)getMarkerId:(NSDictionary *)marker {
-  return marker[@"markerId"];
-}
+
 @end
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 bdc5dd4..bd0c911 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
@@ -5,23 +5,10 @@
 #import <Flutter/Flutter.h>
 #import <GoogleMaps/GoogleMaps.h>
 
-// Defines polygon UI options writable from Flutter.
-@protocol FLTGoogleMapPolygonOptionsSink
-- (void)setConsumeTapEvents:(BOOL)consume;
-- (void)setVisible:(BOOL)visible;
-- (void)setFillColor:(UIColor *)color;
-- (void)setStrokeColor:(UIColor *)color;
-- (void)setStrokeWidth:(CGFloat)width;
-- (void)setPoints:(NSArray<CLLocation *> *)points;
-- (void)setHoles:(NSArray<NSArray<CLLocation *> *> *)holes;
-- (void)setZIndex:(int)zIndex;
-@end
-
 // Defines polygon controllable by Flutter.
-@interface FLTGoogleMapPolygonController : NSObject <FLTGoogleMapPolygonOptionsSink>
-@property(atomic, readonly) NSString *polygonId;
+@interface FLTGoogleMapPolygonController : NSObject
 - (instancetype)initPolygonWithPath:(GMSMutablePath *)path
-                          polygonId:(NSString *)polygonId
+                         identifier:(NSString *)identifier
                             mapView:(GMSMapView *)mapView;
 - (void)removePolygon;
 @end
@@ -32,7 +19,7 @@
            registrar:(NSObject<FlutterPluginRegistrar> *)registrar;
 - (void)addPolygons:(NSArray *)polygonsToAdd;
 - (void)changePolygons:(NSArray *)polygonsToChange;
-- (void)removePolygonIds:(NSArray *)polygonIdsToRemove;
-- (void)onPolygonTap:(NSString *)polygonId;
-- (bool)hasPolygonWithId:(NSString *)polygonId;
+- (void)removePolygonWithIdentifiers:(NSArray *)identifiers;
+- (void)didTapPolygonWithIdentifier:(NSString *)identifier;
+- (bool)hasPolygonWithIdentifier:(NSString *)identifier;
 @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 649ba98..398adfc 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
@@ -3,39 +3,41 @@
 // found in the LICENSE file.
 
 #import "GoogleMapPolygonController.h"
-#import "JsonConversions.h"
+#import "FLTGoogleMapJSONConversions.h"
 
-@implementation FLTGoogleMapPolygonController {
-  GMSPolygon *_polygon;
-  GMSMapView *_mapView;
-}
+@interface FLTGoogleMapPolygonController ()
+
+@property(strong, nonatomic) GMSPolygon *polygon;
+@property(weak, nonatomic) GMSMapView *mapView;
+
+@end
+
+@implementation FLTGoogleMapPolygonController
+
 - (instancetype)initPolygonWithPath:(GMSMutablePath *)path
-                          polygonId:(NSString *)polygonId
+                         identifier:(NSString *)identifier
                             mapView:(GMSMapView *)mapView {
   self = [super init];
   if (self) {
     _polygon = [GMSPolygon polygonWithPath:path];
     _mapView = mapView;
-    _polygonId = polygonId;
-    _polygon.userData = @[ polygonId ];
+    _polygon.userData = @[ identifier ];
   }
   return self;
 }
 
 - (void)removePolygon {
-  _polygon.map = nil;
+  self.polygon.map = nil;
 }
 
-#pragma mark - FLTGoogleMapPolygonOptionsSink methods
-
 - (void)setConsumeTapEvents:(BOOL)consumes {
-  _polygon.tappable = consumes;
+  self.polygon.tappable = consumes;
 }
 - (void)setVisible:(BOOL)visible {
-  _polygon.map = visible ? _mapView : nil;
+  self.polygon.map = visible ? self.mapView : nil;
 }
 - (void)setZIndex:(int)zIndex {
-  _polygon.zIndex = zIndex;
+  self.polygon.zIndex = zIndex;
 }
 - (void)setPoints:(NSArray<CLLocation *> *)points {
   GMSMutablePath *path = [GMSMutablePath path];
@@ -43,7 +45,7 @@
   for (CLLocation *location in points) {
     [path addCoordinate:location.coordinate];
   }
-  _polygon.path = path;
+  self.polygon.path = path;
 }
 - (void)setHoles:(NSArray<NSArray<CLLocation *> *> *)rawHoles {
   NSMutableArray<GMSMutablePath *> *holes = [[NSMutableArray<GMSMutablePath *> alloc] init];
@@ -56,83 +58,75 @@
     [holes addObject:path];
   }
 
-  _polygon.holes = holes;
+  self.polygon.holes = holes;
 }
 
 - (void)setFillColor:(UIColor *)color {
-  _polygon.fillColor = color;
+  self.polygon.fillColor = color;
 }
 - (void)setStrokeColor:(UIColor *)color {
-  _polygon.strokeColor = color;
+  self.polygon.strokeColor = color;
 }
 - (void)setStrokeWidth:(CGFloat)width {
-  _polygon.strokeWidth = width;
-}
-@end
-
-static int ToInt(NSNumber *data) { return [FLTGoogleMapJsonConversions toInt:data]; }
-
-static BOOL ToBool(NSNumber *data) { return [FLTGoogleMapJsonConversions toBool:data]; }
-
-static NSArray<CLLocation *> *ToPoints(NSArray *data) {
-  return [FLTGoogleMapJsonConversions toPoints:data];
+  self.polygon.strokeWidth = width;
 }
 
-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,
-                                    NSObject<FlutterPluginRegistrar> *registrar) {
+- (void)interpretPolygonOptions:(NSDictionary *)data
+                      registrar:(NSObject<FlutterPluginRegistrar> *)registrar {
   NSNumber *consumeTapEvents = data[@"consumeTapEvents"];
-  if (consumeTapEvents != nil) {
-    [sink setConsumeTapEvents:ToBool(consumeTapEvents)];
+  if (consumeTapEvents && consumeTapEvents != (id)[NSNull null]) {
+    [self setConsumeTapEvents:[consumeTapEvents boolValue]];
   }
 
   NSNumber *visible = data[@"visible"];
-  if (visible != nil) {
-    [sink setVisible:ToBool(visible)];
+  if (visible && visible != (id)[NSNull null]) {
+    [self setVisible:[visible boolValue]];
   }
 
   NSNumber *zIndex = data[@"zIndex"];
-  if (zIndex != nil) {
-    [sink setZIndex:ToInt(zIndex)];
+  if (zIndex && zIndex != (id)[NSNull null]) {
+    [self setZIndex:[zIndex intValue]];
   }
 
   NSArray *points = data[@"points"];
-  if (points) {
-    [sink setPoints:ToPoints(points)];
+  if (points && points != (id)[NSNull null]) {
+    [self setPoints:[FLTGoogleMapJSONConversions pointsFromLatLongs:points]];
   }
 
   NSArray *holes = data[@"holes"];
-  if (holes) {
-    [sink setHoles:ToHoles(holes)];
+  if (holes && holes != (id)[NSNull null]) {
+    [self setHoles:[FLTGoogleMapJSONConversions holesFromPointsArray:holes]];
   }
 
   NSNumber *fillColor = data[@"fillColor"];
-  if (fillColor != nil) {
-    [sink setFillColor:ToColor(fillColor)];
+  if (fillColor && fillColor != (id)[NSNull null]) {
+    [self setFillColor:[FLTGoogleMapJSONConversions colorFromRGBA:fillColor]];
   }
 
   NSNumber *strokeColor = data[@"strokeColor"];
-  if (strokeColor != nil) {
-    [sink setStrokeColor:ToColor(strokeColor)];
+  if (strokeColor && strokeColor != (id)[NSNull null]) {
+    [self setStrokeColor:[FLTGoogleMapJSONConversions colorFromRGBA:strokeColor]];
   }
 
   NSNumber *strokeWidth = data[@"strokeWidth"];
-  if (strokeWidth != nil) {
-    [sink setStrokeWidth:ToInt(strokeWidth)];
+  if (strokeWidth && strokeWidth != (id)[NSNull null]) {
+    [self setStrokeWidth:[strokeWidth intValue]];
   }
 }
 
-@implementation FLTPolygonsController {
-  NSMutableDictionary *_polygonIdToController;
-  FlutterMethodChannel *_methodChannel;
-  NSObject<FlutterPluginRegistrar> *_registrar;
-  GMSMapView *_mapView;
-}
+@end
+
+@interface FLTPolygonsController ()
+
+@property(strong, nonatomic) NSMutableDictionary *polygonIdentifierToController;
+@property(strong, nonatomic) FlutterMethodChannel *methodChannel;
+@property(weak, nonatomic) NSObject<FlutterPluginRegistrar> *registrar;
+@property(weak, nonatomic) GMSMapView *mapView;
+
+@end
+
+@implementation FLTPolygonsController
+
 - (instancetype)init:(FlutterMethodChannel *)methodChannel
              mapView:(GMSMapView *)mapView
            registrar:(NSObject<FlutterPluginRegistrar> *)registrar {
@@ -140,72 +134,73 @@
   if (self) {
     _methodChannel = methodChannel;
     _mapView = mapView;
-    _polygonIdToController = [NSMutableDictionary dictionaryWithCapacity:1];
+    _polygonIdentifierToController = [NSMutableDictionary dictionaryWithCapacity:1];
     _registrar = registrar;
   }
   return self;
 }
+
 - (void)addPolygons:(NSArray *)polygonsToAdd {
   for (NSDictionary *polygon in polygonsToAdd) {
     GMSMutablePath *path = [FLTPolygonsController getPath:polygon];
-    NSString *polygonId = [FLTPolygonsController getPolygonId:polygon];
+    NSString *identifier = polygon[@"polygonId"];
     FLTGoogleMapPolygonController *controller =
         [[FLTGoogleMapPolygonController alloc] initPolygonWithPath:path
-                                                         polygonId:polygonId
-                                                           mapView:_mapView];
-    InterpretPolygonOptions(polygon, controller, _registrar);
-    _polygonIdToController[polygonId] = controller;
+                                                        identifier:identifier
+                                                           mapView:self.mapView];
+    [controller interpretPolygonOptions:polygon registrar:self.registrar];
+    self.polygonIdentifierToController[identifier] = controller;
   }
 }
+
 - (void)changePolygons:(NSArray *)polygonsToChange {
   for (NSDictionary *polygon in polygonsToChange) {
-    NSString *polygonId = [FLTPolygonsController getPolygonId:polygon];
-    FLTGoogleMapPolygonController *controller = _polygonIdToController[polygonId];
+    NSString *identifier = polygon[@"polygonId"];
+    FLTGoogleMapPolygonController *controller = self.polygonIdentifierToController[identifier];
     if (!controller) {
       continue;
     }
-    InterpretPolygonOptions(polygon, controller, _registrar);
+    [controller interpretPolygonOptions:polygon registrar:self.registrar];
   }
 }
-- (void)removePolygonIds:(NSArray *)polygonIdsToRemove {
-  for (NSString *polygonId in polygonIdsToRemove) {
-    if (!polygonId) {
-      continue;
-    }
-    FLTGoogleMapPolygonController *controller = _polygonIdToController[polygonId];
+
+- (void)removePolygonWithIdentifiers:(NSArray *)identifiers {
+  for (NSString *identifier in identifiers) {
+    FLTGoogleMapPolygonController *controller = self.polygonIdentifierToController[identifier];
     if (!controller) {
       continue;
     }
     [controller removePolygon];
-    [_polygonIdToController removeObjectForKey:polygonId];
+    [self.polygonIdentifierToController removeObjectForKey:identifier];
   }
 }
-- (void)onPolygonTap:(NSString *)polygonId {
-  if (!polygonId) {
+
+- (void)didTapPolygonWithIdentifier:(NSString *)identifier {
+  if (!identifier) {
     return;
   }
-  FLTGoogleMapPolygonController *controller = _polygonIdToController[polygonId];
+  FLTGoogleMapPolygonController *controller = self.polygonIdentifierToController[identifier];
   if (!controller) {
     return;
   }
-  [_methodChannel invokeMethod:@"polygon#onTap" arguments:@{@"polygonId" : polygonId}];
+  [self.methodChannel invokeMethod:@"polygon#onTap" arguments:@{@"polygonId" : identifier}];
 }
-- (bool)hasPolygonWithId:(NSString *)polygonId {
-  if (!polygonId) {
+
+- (bool)hasPolygonWithIdentifier:(NSString *)identifier {
+  if (!identifier) {
     return false;
   }
-  return _polygonIdToController[polygonId] != nil;
+  return self.polygonIdentifierToController[identifier] != nil;
 }
+
 + (GMSMutablePath *)getPath:(NSDictionary *)polygon {
   NSArray *pointArray = polygon[@"points"];
-  NSArray<CLLocation *> *points = ToPoints(pointArray);
+  NSArray<CLLocation *> *points = [FLTGoogleMapJSONConversions pointsFromLatLongs:pointArray];
   GMSMutablePath *path = [GMSMutablePath path];
   for (CLLocation *location in points) {
     [path addCoordinate:location.coordinate];
   }
   return path;
 }
-+ (NSString *)getPolygonId:(NSDictionary *)polygon {
-  return polygon[@"polygonId"];
-}
+
 @end
diff --git a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapPolylineController.h b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapPolylineController.h
index 0e614ee..f85d1a3 100644
--- a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapPolylineController.h
+++ b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapPolylineController.h
@@ -5,22 +5,10 @@
 #import <Flutter/Flutter.h>
 #import <GoogleMaps/GoogleMaps.h>
 
-// Defines polyline UI options writable from Flutter.
-@protocol FLTGoogleMapPolylineOptionsSink
-- (void)setConsumeTapEvents:(BOOL)consume;
-- (void)setVisible:(BOOL)visible;
-- (void)setColor:(UIColor *)color;
-- (void)setStrokeWidth:(CGFloat)width;
-- (void)setPoints:(NSArray<CLLocation *> *)points;
-- (void)setZIndex:(int)zIndex;
-- (void)setGeodesic:(BOOL)isGeodesic;
-@end
-
 // Defines polyline controllable by Flutter.
-@interface FLTGoogleMapPolylineController : NSObject <FLTGoogleMapPolylineOptionsSink>
-@property(atomic, readonly) NSString *polylineId;
+@interface FLTGoogleMapPolylineController : NSObject
 - (instancetype)initPolylineWithPath:(GMSMutablePath *)path
-                          polylineId:(NSString *)polylineId
+                          identifier:(NSString *)identifier
                              mapView:(GMSMapView *)mapView;
 - (void)removePolyline;
 @end
@@ -31,7 +19,7 @@
            registrar:(NSObject<FlutterPluginRegistrar> *)registrar;
 - (void)addPolylines:(NSArray *)polylinesToAdd;
 - (void)changePolylines:(NSArray *)polylinesToChange;
-- (void)removePolylineIds:(NSArray *)polylineIdsToRemove;
-- (void)onPolylineTap:(NSString *)polylineId;
-- (bool)hasPolylineWithId:(NSString *)polylineId;
+- (void)removePolylineWithIdentifiers:(NSArray *)identifiers;
+- (void)didTapPolylineWithIdentifier:(NSString *)identifier;
+- (bool)hasPolylineWithIdentifier:(NSString *)identifier;
 @end
diff --git a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapPolylineController.m b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapPolylineController.m
index f366051..77601d4 100644
--- a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapPolylineController.m
+++ b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapPolylineController.m
@@ -3,39 +3,41 @@
 // found in the LICENSE file.
 
 #import "GoogleMapPolylineController.h"
-#import "JsonConversions.h"
+#import "FLTGoogleMapJSONConversions.h"
 
-@implementation FLTGoogleMapPolylineController {
-  GMSPolyline *_polyline;
-  GMSMapView *_mapView;
-}
+@interface FLTGoogleMapPolylineController ()
+
+@property(strong, nonatomic) GMSPolyline *polyline;
+@property(weak, nonatomic) GMSMapView *mapView;
+
+@end
+
+@implementation FLTGoogleMapPolylineController
+
 - (instancetype)initPolylineWithPath:(GMSMutablePath *)path
-                          polylineId:(NSString *)polylineId
+                          identifier:(NSString *)identifier
                              mapView:(GMSMapView *)mapView {
   self = [super init];
   if (self) {
     _polyline = [GMSPolyline polylineWithPath:path];
     _mapView = mapView;
-    _polylineId = polylineId;
-    _polyline.userData = @[ polylineId ];
+    _polyline.userData = @[ identifier ];
   }
   return self;
 }
 
 - (void)removePolyline {
-  _polyline.map = nil;
+  self.polyline.map = nil;
 }
 
-#pragma mark - FLTGoogleMapPolylineOptionsSink methods
-
 - (void)setConsumeTapEvents:(BOOL)consumes {
-  _polyline.tappable = consumes;
+  self.polyline.tappable = consumes;
 }
 - (void)setVisible:(BOOL)visible {
-  _polyline.map = visible ? _mapView : nil;
+  self.polyline.map = visible ? self.mapView : nil;
 }
 - (void)setZIndex:(int)zIndex {
-  _polyline.zIndex = zIndex;
+  self.polyline.zIndex = zIndex;
 }
 - (void)setPoints:(NSArray<CLLocation *> *)points {
   GMSMutablePath *path = [GMSMutablePath path];
@@ -43,75 +45,72 @@
   for (CLLocation *location in points) {
     [path addCoordinate:location.coordinate];
   }
-  _polyline.path = path;
+  self.polyline.path = path;
 }
 
 - (void)setColor:(UIColor *)color {
-  _polyline.strokeColor = color;
+  self.polyline.strokeColor = color;
 }
 - (void)setStrokeWidth:(CGFloat)width {
-  _polyline.strokeWidth = width;
+  self.polyline.strokeWidth = width;
 }
 
 - (void)setGeodesic:(BOOL)isGeodesic {
-  _polyline.geodesic = isGeodesic;
-}
-@end
-
-static int ToInt(NSNumber *data) { return [FLTGoogleMapJsonConversions toInt:data]; }
-
-static BOOL ToBool(NSNumber *data) { return [FLTGoogleMapJsonConversions toBool:data]; }
-
-static NSArray<CLLocation *> *ToPoints(NSArray *data) {
-  return [FLTGoogleMapJsonConversions toPoints:data];
+  self.polyline.geodesic = isGeodesic;
 }
 
-static UIColor *ToColor(NSNumber *data) { return [FLTGoogleMapJsonConversions toColor:data]; }
-
-static void InterpretPolylineOptions(NSDictionary *data, id<FLTGoogleMapPolylineOptionsSink> sink,
-                                     NSObject<FlutterPluginRegistrar> *registrar) {
+- (void)interpretPolylineOptions:(NSDictionary *)data
+                       registrar:(NSObject<FlutterPluginRegistrar> *)registrar {
   NSNumber *consumeTapEvents = data[@"consumeTapEvents"];
-  if (consumeTapEvents != nil) {
-    [sink setConsumeTapEvents:ToBool(consumeTapEvents)];
+  if (consumeTapEvents && consumeTapEvents != (id)[NSNull null]) {
+    [self setConsumeTapEvents:[consumeTapEvents boolValue]];
   }
 
   NSNumber *visible = data[@"visible"];
-  if (visible != nil) {
-    [sink setVisible:ToBool(visible)];
+  if (visible && visible != (id)[NSNull null]) {
+    [self setVisible:[visible boolValue]];
   }
 
   NSNumber *zIndex = data[@"zIndex"];
-  if (zIndex != nil) {
-    [sink setZIndex:ToInt(zIndex)];
+  if (zIndex && zIndex != (id)[NSNull null]) {
+    [self setZIndex:[zIndex intValue]];
   }
 
   NSArray *points = data[@"points"];
-  if (points) {
-    [sink setPoints:ToPoints(points)];
+  if (points && points != (id)[NSNull null]) {
+    [self setPoints:[FLTGoogleMapJSONConversions pointsFromLatLongs:points]];
   }
 
   NSNumber *strokeColor = data[@"color"];
-  if (strokeColor != nil) {
-    [sink setColor:ToColor(strokeColor)];
+  if (strokeColor && strokeColor != (id)[NSNull null]) {
+    [self setColor:[FLTGoogleMapJSONConversions colorFromRGBA:strokeColor]];
   }
 
   NSNumber *strokeWidth = data[@"width"];
-  if (strokeWidth != nil) {
-    [sink setStrokeWidth:ToInt(strokeWidth)];
+  if (strokeWidth && strokeWidth != (id)[NSNull null]) {
+    [self setStrokeWidth:[strokeWidth intValue]];
   }
 
   NSNumber *geodesic = data[@"geodesic"];
-  if (geodesic != nil) {
-    [sink setGeodesic:geodesic.boolValue];
+  if (geodesic && geodesic != (id)[NSNull null]) {
+    [self setGeodesic:geodesic.boolValue];
   }
 }
 
-@implementation FLTPolylinesController {
-  NSMutableDictionary *_polylineIdToController;
-  FlutterMethodChannel *_methodChannel;
-  NSObject<FlutterPluginRegistrar> *_registrar;
-  GMSMapView *_mapView;
-}
+@end
+
+@interface FLTPolylinesController ()
+
+@property(strong, nonatomic) NSMutableDictionary *polylineIdentifierToController;
+@property(strong, nonatomic) FlutterMethodChannel *methodChannel;
+@property(weak, nonatomic) NSObject<FlutterPluginRegistrar> *registrar;
+@property(weak, nonatomic) GMSMapView *mapView;
+
+@end
+;
+
+@implementation FLTPolylinesController
+
 - (instancetype)init:(FlutterMethodChannel *)methodChannel
              mapView:(GMSMapView *)mapView
            registrar:(NSObject<FlutterPluginRegistrar> *)registrar {
@@ -119,7 +118,7 @@
   if (self) {
     _methodChannel = methodChannel;
     _mapView = mapView;
-    _polylineIdToController = [NSMutableDictionary dictionaryWithCapacity:1];
+    _polylineIdentifierToController = [NSMutableDictionary dictionaryWithCapacity:1];
     _registrar = registrar;
   }
   return self;
@@ -127,64 +126,59 @@
 - (void)addPolylines:(NSArray *)polylinesToAdd {
   for (NSDictionary *polyline in polylinesToAdd) {
     GMSMutablePath *path = [FLTPolylinesController getPath:polyline];
-    NSString *polylineId = [FLTPolylinesController getPolylineId:polyline];
+    NSString *identifier = polyline[@"polylineId"];
     FLTGoogleMapPolylineController *controller =
         [[FLTGoogleMapPolylineController alloc] initPolylineWithPath:path
-                                                          polylineId:polylineId
-                                                             mapView:_mapView];
-    InterpretPolylineOptions(polyline, controller, _registrar);
-    _polylineIdToController[polylineId] = controller;
+                                                          identifier:identifier
+                                                             mapView:self.mapView];
+    [controller interpretPolylineOptions:polyline registrar:self.registrar];
+    self.polylineIdentifierToController[identifier] = controller;
   }
 }
 - (void)changePolylines:(NSArray *)polylinesToChange {
   for (NSDictionary *polyline in polylinesToChange) {
-    NSString *polylineId = [FLTPolylinesController getPolylineId:polyline];
-    FLTGoogleMapPolylineController *controller = _polylineIdToController[polylineId];
+    NSString *identifier = polyline[@"polylineId"];
+    FLTGoogleMapPolylineController *controller = self.polylineIdentifierToController[identifier];
     if (!controller) {
       continue;
     }
-    InterpretPolylineOptions(polyline, controller, _registrar);
+    [controller interpretPolylineOptions:polyline registrar:self.registrar];
   }
 }
-- (void)removePolylineIds:(NSArray *)polylineIdsToRemove {
-  for (NSString *polylineId in polylineIdsToRemove) {
-    if (!polylineId) {
-      continue;
-    }
-    FLTGoogleMapPolylineController *controller = _polylineIdToController[polylineId];
+- (void)removePolylineWithIdentifiers:(NSArray *)identifiers {
+  for (NSString *identifier in identifiers) {
+    FLTGoogleMapPolylineController *controller = self.polylineIdentifierToController[identifier];
     if (!controller) {
       continue;
     }
     [controller removePolyline];
-    [_polylineIdToController removeObjectForKey:polylineId];
+    [self.polylineIdentifierToController removeObjectForKey:identifier];
   }
 }
-- (void)onPolylineTap:(NSString *)polylineId {
-  if (!polylineId) {
+- (void)didTapPolylineWithIdentifier:(NSString *)identifier {
+  if (!identifier) {
     return;
   }
-  FLTGoogleMapPolylineController *controller = _polylineIdToController[polylineId];
+  FLTGoogleMapPolylineController *controller = self.polylineIdentifierToController[identifier];
   if (!controller) {
     return;
   }
-  [_methodChannel invokeMethod:@"polyline#onTap" arguments:@{@"polylineId" : polylineId}];
+  [self.methodChannel invokeMethod:@"polyline#onTap" arguments:@{@"polylineId" : identifier}];
 }
-- (bool)hasPolylineWithId:(NSString *)polylineId {
-  if (!polylineId) {
+- (bool)hasPolylineWithIdentifier:(NSString *)identifier {
+  if (!identifier) {
     return false;
   }
-  return _polylineIdToController[polylineId] != nil;
+  return self.polylineIdentifierToController[identifier] != nil;
 }
 + (GMSMutablePath *)getPath:(NSDictionary *)polyline {
   NSArray *pointArray = polyline[@"points"];
-  NSArray<CLLocation *> *points = ToPoints(pointArray);
+  NSArray<CLLocation *> *points = [FLTGoogleMapJSONConversions pointsFromLatLongs:pointArray];
   GMSMutablePath *path = [GMSMutablePath path];
   for (CLLocation *location in points) {
     [path addCoordinate:location.coordinate];
   }
   return path;
 }
-+ (NSString *)getPolylineId:(NSDictionary *)polyline {
-  return polyline[@"polylineId"];
-}
+
 @end
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
deleted file mode 100644
index c0f673e..0000000
--- a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/JsonConversions.h
+++ /dev/null
@@ -1,19 +0,0 @@
-// Copyright 2013 The Flutter Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#import <Flutter/Flutter.h>
-#import <GoogleMaps/GoogleMaps.h>
-
-@interface FLTGoogleMapJsonConversions : NSObject
-+ (bool)toBool:(NSNumber *)data;
-+ (int)toInt:(NSNumber *)data;
-+ (double)toDouble:(NSNumber *)data;
-+ (float)toFloat:(NSNumber *)data;
-+ (CLLocationCoordinate2D)toLocation:(NSArray *)data;
-+ (CGPoint)toPoint:(NSArray *)data;
-+ (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
deleted file mode 100644
index 0e88d47..0000000
--- a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/JsonConversions.m
+++ /dev/null
@@ -1,71 +0,0 @@
-// Copyright 2013 The Flutter Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
-// found in the LICENSE file.
-
-#import "JsonConversions.h"
-
-@implementation FLTGoogleMapJsonConversions
-
-+ (bool)toBool:(NSNumber *)data {
-  return data.boolValue;
-}
-
-+ (int)toInt:(NSNumber *)data {
-  return data.intValue;
-}
-
-+ (double)toDouble:(NSNumber *)data {
-  return data.doubleValue;
-}
-
-+ (float)toFloat:(NSNumber *)data {
-  return data.floatValue;
-}
-
-+ (CLLocationCoordinate2D)toLocation:(NSArray *)data {
-  return CLLocationCoordinate2DMake([FLTGoogleMapJsonConversions toDouble:data[0]],
-                                    [FLTGoogleMapJsonConversions toDouble:data[1]]);
-}
-
-+ (CGPoint)toPoint:(NSArray *)data {
-  return CGPointMake([FLTGoogleMapJsonConversions toDouble:data[0]],
-                     [FLTGoogleMapJsonConversions toDouble:data[1]]);
-}
-
-+ (NSArray *)positionToJson:(CLLocationCoordinate2D)position {
-  return @[ @(position.latitude), @(position.longitude) ];
-}
-
-+ (UIColor *)toColor:(NSNumber *)numberColor {
-  unsigned long value = [numberColor unsignedLongValue];
-  return [UIColor colorWithRed:((float)((value & 0xFF0000) >> 16)) / 255.0
-                         green:((float)((value & 0xFF00) >> 8)) / 255.0
-                          blue:((float)(value & 0xFF)) / 255.0
-                         alpha:((float)((value & 0xFF000000) >> 24)) / 255.0];
-}
-
-+ (NSArray<CLLocation *> *)toPoints:(NSArray *)data {
-  NSMutableArray *points = [[NSMutableArray alloc] init];
-  for (unsigned i = 0; i < [data count]; i++) {
-    NSNumber *latitude = data[i][0];
-    NSNumber *longitude = data[i][1];
-    CLLocation *point =
-        [[CLLocation alloc] initWithLatitude:[FLTGoogleMapJsonConversions toDouble:latitude]
-                                   longitude:[FLTGoogleMapJsonConversions toDouble:longitude]];
-    [points addObject:point];
-  }
-
-  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/ios/Classes/google_maps_flutter-umbrella.h b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/google_maps_flutter-umbrella.h
index 50880a2..9969e71 100644
--- a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/google_maps_flutter-umbrella.h
+++ b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/google_maps_flutter-umbrella.h
@@ -3,9 +3,9 @@
 // found in the LICENSE file.
 
 #import <Foundation/Foundation.h>
+#import <google_maps_flutter/FLTGoogleMapJSONConversions.h>
 #import <google_maps_flutter/FLTGoogleMapTileOverlayController.h>
 #import <google_maps_flutter/FLTGoogleMapsPlugin.h>
-#import <google_maps_flutter/JsonConversions.h>
 
 FOUNDATION_EXPORT double google_maps_flutterVersionNumber;
 FOUNDATION_EXPORT const unsigned char google_maps_flutterVersionString[];
diff --git a/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml
index 59ee23d..215a930 100644
--- a/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml
+++ b/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml
@@ -2,7 +2,7 @@
 description: A Flutter plugin for integrating Google Maps in iOS and Android applications.
 repository: https://github.com/flutter/plugins/tree/main/packages/google_maps_flutter/google_maps_flutter
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+maps%22
-version: 2.1.6
+version: 2.1.7
 
 environment:
   sdk: ">=2.14.0 <3.0.0"