[google_maps_flutter] Federate mobile implementations (#6171)

diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 33094a2..d1b3525 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -57,7 +57,7 @@
     open-pull-requests-limit: 10
 
   - package-ecosystem: "gradle"
-    directory: "/packages/google_maps_flutter/google_maps_flutter/android"
+    directory: "/packages/google_maps_flutter/google_maps_flutter/example/android/app"
     commit-message:
       prefix: "[google_maps]"
     schedule:
@@ -65,7 +65,15 @@
     open-pull-requests-limit: 10
 
   - package-ecosystem: "gradle"
-    directory: "/packages/google_maps_flutter/google_maps_flutter/example/android/app"
+    directory: "/packages/google_maps_flutter/google_maps_flutter_android/android"
+    commit-message:
+      prefix: "[google_maps]"
+    schedule:
+      interval: "weekly"
+    open-pull-requests-limit: 10
+
+  - package-ecosystem: "gradle"
+    directory: "/packages/google_maps_flutter/google_maps_flutter_android/example/android/app"
     commit-message:
       prefix: "[google_maps]"
     schedule:
diff --git a/CODEOWNERS b/CODEOWNERS
index 9efe4a5..db8187d 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -30,7 +30,7 @@
 packages/camera/camera_android/**                        @camsim99
 packages/espresso/**                                     @GaryQian
 packages/flutter_plugin_android_lifecycle/**             @GaryQian
-packages/google_maps_flutter/google_maps_flutter/android/**  @GaryQian
+packages/google_maps_flutter/google_maps_flutter_android/**  @GaryQian
 packages/google_sign_in/google_sign_in_android/**        @camsim99
 packages/image_picker/image_picker_android/**            @GaryQian
 packages/in_app_purchase/in_app_purchase_android/**      @GaryQian
@@ -42,7 +42,7 @@
 
 # - iOS
 packages/camera/camera_avfoundation/**                   @hellohuanlin
-packages/google_maps_flutter/google_maps_flutter/ios/**  @cyanglaz
+packages/google_maps_flutter/google_maps_flutter_ios/**  @cyanglaz
 packages/google_sign_in/google_sign_in_ios/**            @jmagman
 packages/image_picker/image_picker_ios/**                @cyanglaz
 packages/in_app_purchase/in_app_purchase_storekit/**     @cyanglaz
diff --git a/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md
index 9d7d542..86cefdd 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 @@
+## NEXT
+
+* Moves Android and iOS implementations to federated packages.
+
 ## 2.1.10
 
 * Avoids map shift when scrolling on iOS.
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/settings.gradle b/packages/google_maps_flutter/google_maps_flutter/android/settings.gradle
deleted file mode 100644
index dbceadf..0000000
--- a/packages/google_maps_flutter/google_maps_flutter/android/settings.gradle
+++ /dev/null
@@ -1 +0,0 @@
-rootProject.name = 'google_maps_flutter'
diff --git a/packages/google_maps_flutter/google_maps_flutter/example/integration_test/google_maps_test.dart b/packages/google_maps_flutter/google_maps_flutter/example/integration_test/google_maps_test.dart
index d82a582..1d7494c 100644
--- a/packages/google_maps_flutter/google_maps_flutter/example/integration_test/google_maps_test.dart
+++ b/packages/google_maps_flutter/google_maps_flutter/example/integration_test/google_maps_test.dart
@@ -13,8 +13,6 @@
 import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
 import 'package:integration_test/integration_test.dart';
 
-import 'google_map_inspector.dart';
-
 const LatLng _kInitialMapCenter = LatLng(0, 0);
 const double _kInitialZoomLevel = 5;
 const CameraPosition _kInitialCameraPosition =
@@ -22,16 +20,7 @@
 
 void main() {
   IntegrationTestWidgetsFlutterBinding.ensureInitialized();
-
-  // TODO(stuartmorgan): Remove this once mobile implementations are federated
-  // and registering their own inpector implementations, and just call
-  // enableDebugInspection.
-  if (GoogleMapsFlutterPlatform.instance is MethodChannelGoogleMapsFlutter) {
-    GoogleMapsInspectorPlatform.instance = MethodChannelGoogleMapsInspector(
-        GoogleMapsFlutterPlatform.instance as MethodChannelGoogleMapsFlutter);
-  } else {
-    GoogleMapsFlutterPlatform.instance.enableDebugInspection();
-  }
+  GoogleMapsFlutterPlatform.instance.enableDebugInspection();
 
   testWidgets('testCompassToggle', (WidgetTester tester) async {
     final Key key = GlobalKey();
@@ -474,7 +463,6 @@
               .round());
     }
     await tester.binding.setSurfaceSize(null);
-    AndroidGoogleMapsFlutter.useAndroidViewSurface = false;
   });
 
   testWidgets('testGetVisibleRegion', (WidgetTester tester) async {
diff --git a/packages/google_maps_flutter/google_maps_flutter/example/ios/Podfile b/packages/google_maps_flutter/google_maps_flutter/example/ios/Podfile
index 14b4bdc..8df8fef 100644
--- a/packages/google_maps_flutter/google_maps_flutter/example/ios/Podfile
+++ b/packages/google_maps_flutter/google_maps_flutter/example/ios/Podfile
@@ -29,14 +29,6 @@
 
 target 'Runner' do
   flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
-  target 'RunnerTests' do
-    inherit! :search_paths
-
-    pod 'OCMock', '~> 3.9.1'
-  end
-  target 'RunnerUITests' do
-    inherit! :search_paths
-  end
 end
 
 post_install do |installer|
diff --git a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/google_maps_flutter.modulemap b/packages/google_maps_flutter/google_maps_flutter/ios/Classes/google_maps_flutter.modulemap
deleted file mode 100644
index 19513f4..0000000
--- a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/google_maps_flutter.modulemap
+++ /dev/null
@@ -1,10 +0,0 @@
-framework module google_maps_flutter {
-  umbrella header "google_maps_flutter-umbrella.h"
-
-  export *
-  module * { export * }
-
-  explicit module Test {
-    header "GoogleMapController_Test.h"
-  }
-}
diff --git a/packages/google_maps_flutter/google_maps_flutter/lib/google_maps_flutter.dart b/packages/google_maps_flutter/google_maps_flutter/lib/google_maps_flutter.dart
index 751930a..a4be120 100644
--- a/packages/google_maps_flutter/google_maps_flutter/lib/google_maps_flutter.dart
+++ b/packages/google_maps_flutter/google_maps_flutter/lib/google_maps_flutter.dart
@@ -13,6 +13,7 @@
 import 'package:flutter/foundation.dart';
 import 'package:flutter/gestures.dart';
 import 'package:flutter/material.dart';
+import 'package:google_maps_flutter_android/google_maps_flutter_android.dart';
 import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
 
 export 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart'
diff --git a/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart b/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart
index b76d103..e037d7e 100644
--- a/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart
+++ b/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart
@@ -40,6 +40,9 @@
 }
 
 /// Android specific settings for [GoogleMap].
+// TODO(stuartmorgan): Deprecate this in favor of pointing people who want to
+// change this to using the Android implementation Dart class directly. This
+// should be done as part of switching the default to hybrid composition.
 class AndroidGoogleMapsFlutter {
   AndroidGoogleMapsFlutter._();
 
@@ -55,7 +58,7 @@
   static bool get useAndroidViewSurface {
     final GoogleMapsFlutterPlatform platform =
         GoogleMapsFlutterPlatform.instance;
-    if (platform is MethodChannelGoogleMapsFlutter) {
+    if (platform is GoogleMapsFlutterAndroid) {
       return platform.useAndroidViewSurface;
     }
     return false;
@@ -73,7 +76,7 @@
   static set useAndroidViewSurface(bool useAndroidViewSurface) {
     final GoogleMapsFlutterPlatform platform =
         GoogleMapsFlutterPlatform.instance;
-    if (platform is MethodChannelGoogleMapsFlutter) {
+    if (platform is GoogleMapsFlutterAndroid) {
       platform.useAndroidViewSurface = useAndroidViewSurface;
     }
   }
diff --git a/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml
index b0cff92..8d4e49f 100644
--- a/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml
+++ b/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml
@@ -3,6 +3,9 @@
 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.10
+# Temporarily disable publishing to allow moving Android and iOS
+# implementations.
+publish_to: none
 
 environment:
   sdk: ">=2.14.0 <3.0.0"
@@ -12,15 +15,18 @@
   plugin:
     platforms:
       android:
-        package: io.flutter.plugins.googlemaps
-        pluginClass: GoogleMapsPlugin
+        default_package: google_maps_flutter_android
       ios:
-        pluginClass: FLTGoogleMapsPlugin
+        default_package: google_maps_flutter_ios
 
 dependencies:
   flutter:
     sdk: flutter
-  flutter_plugin_android_lifecycle: ^2.0.1
+  # Temporary path dependencies to allow moving Android and iOS implementations.
+  google_maps_flutter_android:
+    path: ../google_maps_flutter_android
+  google_maps_flutter_ios:
+    path: ../google_maps_flutter_ios
   google_maps_flutter_platform_interface: ^2.2.1
 
 dev_dependencies:
diff --git a/packages/google_maps_flutter/google_maps_flutter/test/google_map_test.dart b/packages/google_maps_flutter/google_maps_flutter/test/google_map_test.dart
index 003ae06..2b754af 100644
--- a/packages/google_maps_flutter/google_maps_flutter/test/google_map_test.dart
+++ b/packages/google_maps_flutter/google_maps_flutter/test/google_map_test.dart
@@ -6,7 +6,6 @@
 import 'package:flutter/widgets.dart';
 import 'package:flutter_test/flutter_test.dart';
 import 'package:google_maps_flutter/google_maps_flutter.dart';
-import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
 
 import 'fake_maps_controllers.dart';
 
@@ -588,38 +587,4 @@
 
     expect(platformGoogleMap.buildingsEnabled, true);
   });
-
-  testWidgets(
-    'Default Android widget is AndroidView',
-    (WidgetTester tester) async {
-      await tester.pumpWidget(
-        const Directionality(
-          textDirection: TextDirection.ltr,
-          child: GoogleMap(
-            initialCameraPosition: CameraPosition(target: LatLng(10.0, 15.0)),
-          ),
-        ),
-      );
-
-      expect(find.byType(AndroidView), findsOneWidget);
-    },
-  );
-
-  testWidgets('Use PlatformViewLink on Android', (WidgetTester tester) async {
-    final MethodChannelGoogleMapsFlutter platform =
-        GoogleMapsFlutterPlatform.instance as MethodChannelGoogleMapsFlutter;
-    platform.useAndroidViewSurface = true;
-
-    await tester.pumpWidget(
-      const Directionality(
-        textDirection: TextDirection.ltr,
-        child: GoogleMap(
-          initialCameraPosition: CameraPosition(target: LatLng(10.0, 15.0)),
-        ),
-      ),
-    );
-
-    expect(find.byType(PlatformViewLink), findsOneWidget);
-    platform.useAndroidViewSurface = false;
-  });
 }
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/AUTHORS b/packages/google_maps_flutter/google_maps_flutter_android/AUTHORS
new file mode 100644
index 0000000..9f1b53e
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/AUTHORS
@@ -0,0 +1,67 @@
+# Below is a list of people and organizations that have contributed
+# to the Flutter project. Names should be added to the list like so:
+#
+#   Name/Organization <email address>
+
+Google Inc.
+The Chromium Authors
+German Saprykin <saprykin.h@gmail.com>
+Benjamin Sauer <sauer.benjamin@gmail.com>
+larsenthomasj@gmail.com
+Ali Bitek <alibitek@protonmail.ch>
+Pol Batlló <pol.batllo@gmail.com>
+Anatoly Pulyaevskiy
+Hayden Flinner <haydenflinner@gmail.com>
+Stefano Rodriguez <hlsroddy@gmail.com>
+Salvatore Giordano <salvatoregiordanoo@gmail.com>
+Brian Armstrong <brian@flutter.institute>
+Paul DeMarco <paulmdemarco@gmail.com>
+Fabricio Nogueira <feufeu@gmail.com>
+Simon Lightfoot <simon@devangels.london>
+Ashton Thomas <ashton@acrinta.com>
+Thomas Danner <thmsdnnr@gmail.com>
+Diego Velásquez <diego.velasquez.lopez@gmail.com>
+Hajime Nakamura <nkmrhj@gmail.com>
+Tuyển Vũ Xuân <netsoft1985@gmail.com>
+Miguel Ruivo <miguel@miguelruivo.com>
+Sarthak Verma <sarthak@artiosys.com>
+Mike Diarmid <mike@invertase.io>
+Invertase <oss@invertase.io>
+Elliot Hesp <elliot@invertase.io>
+Vince Varga <vince.varga@smaho.com>
+Aawaz Gyawali <awazgyawali@gmail.com>
+EUI Limited <ian.evans3@admiralgroup.co.uk>
+Katarina Sheremet <katarina@sheremet.ch>
+Thomas Stockx <thomas@stockxit.com>
+Sarbagya Dhaubanjar <sarbagyastha@gmail.com>
+Ozkan Eksi <ozeksi@gmail.com>
+Rishab Nayak <rishab@bu.edu>
+ko2ic <ko2ic.dev@gmail.com>
+Jonathan Younger <jonathan@daikini.com>
+Jose Sanchez <josesm82@gmail.com>
+Debkanchan Samadder <debu.samadder@gmail.com>
+Audrius Karosevicius <audrius.karosevicius@gmail.com>
+Lukasz Piliszczuk <lukasz@intheloup.io>
+SoundReply Solutions GmbH <ch@soundreply.com>
+Rafal Wachol <rwachol@gmail.com>
+Pau Picas <pau.picas@gmail.com>
+Christian Weder <chrstian.weder@yapeal.ch>
+Alexandru Tuca <salexandru.tuca@outlook.com>
+Christian Weder <chrstian.weder@yapeal.ch>
+Rhodes Davis Jr. <rody.davis.jr@gmail.com>
+Luigi Agosti <luigi@tengio.com>
+Quentin Le Guennec <quentin@tengio.com>
+Koushik Ravikumar <koushik@tengio.com>
+Nissim Dsilva <nissim@tengio.com>
+Giancarlo Rocha <giancarloiff@gmail.com>
+Ryo Miyake <ryo@miyake.id>
+Théo Champion <contact.theochampion@gmail.com>
+Kazuki Yamaguchi <y.kazuki0614n@gmail.com>
+Eitan Schwartz <eshvartz@gmail.com>
+Chris Rutkowski <chrisrutkowski89@gmail.com>
+Juan Alvarez <juan.alvarez@resideo.com>
+Aleksandr Yurkovskiy <sanekyy@gmail.com>
+Anton Borries <mail@antonborri.es>
+Alex Li <google@alexv525.com>
+Rahul Raj <64.rahulraj@gmail.com>
+Taha Tesser <tesser@gmail.com>
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter_android/CHANGELOG.md
new file mode 100644
index 0000000..bd2f99c
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/CHANGELOG.md
@@ -0,0 +1,4 @@
+## 2.1.10
+
+* Splits Android implementation out of `google_maps_flutter` as a federated
+  implementation.
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/LICENSE b/packages/google_maps_flutter/google_maps_flutter_android/LICENSE
new file mode 100644
index 0000000..c6823b8
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/LICENSE
@@ -0,0 +1,25 @@
+Copyright 2013 The Flutter Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+    * Neither the name of Google Inc. nor the names of its
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/README.md b/packages/google_maps_flutter/google_maps_flutter_android/README.md
new file mode 100644
index 0000000..5ac1df0
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/README.md
@@ -0,0 +1,12 @@
+# google\_maps\_flutter\_android
+
+The Android implementation of [`google_maps_flutter`][1].
+
+## Usage
+
+This package is [endorsed][2], which means you can simply use
+`google_maps_flutter` normally. This package will be automatically included in
+your app when you do.
+
+[1]: https://pub.dev/packages/google_maps_flutter
+[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/build.gradle b/packages/google_maps_flutter/google_maps_flutter_android/android/build.gradle
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/android/build.gradle
rename to packages/google_maps_flutter/google_maps_flutter_android/android/build.gradle
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/settings.gradle b/packages/google_maps_flutter/google_maps_flutter_android/android/settings.gradle
new file mode 100644
index 0000000..d873c7a
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/android/settings.gradle
@@ -0,0 +1 @@
+rootProject.name = 'google_maps_flutter_android'
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/AndroidManifest.xml b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/AndroidManifest.xml
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/android/src/main/AndroidManifest.xml
rename to packages/google_maps_flutter/google_maps_flutter_android/android/src/main/AndroidManifest.xml
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/CircleBuilder.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/CircleBuilder.java
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/CircleBuilder.java
rename to packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/CircleBuilder.java
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/CircleController.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/CircleController.java
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/CircleController.java
rename to packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/CircleController.java
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/CircleOptionsSink.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/CircleOptionsSink.java
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/CircleOptionsSink.java
rename to packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/CircleOptionsSink.java
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/CirclesController.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/CirclesController.java
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/CirclesController.java
rename to packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/CirclesController.java
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java
rename to packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapBuilder.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapBuilder.java
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapBuilder.java
rename to packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapBuilder.java
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java
similarity index 99%
rename from packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java
rename to packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java
index 2c2287c..66d3e28 100644
--- a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java
+++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java
@@ -96,7 +96,8 @@
     this.options = options;
     this.mapView = new MapView(context, options);
     this.density = context.getResources().getDisplayMetrics().density;
-    methodChannel = new MethodChannel(binaryMessenger, "plugins.flutter.io/google_maps_" + id);
+    methodChannel =
+        new MethodChannel(binaryMessenger, "plugins.flutter.dev/google_maps_android_" + id);
     methodChannel.setMethodCallHandler(this);
     this.lifecycleProvider = lifecycleProvider;
     this.markersController = new MarkersController(methodChannel);
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapFactory.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapFactory.java
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapFactory.java
rename to packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapFactory.java
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapListener.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapListener.java
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapListener.java
rename to packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapListener.java
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapOptionsSink.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapOptionsSink.java
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapOptionsSink.java
rename to packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapOptionsSink.java
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapsPlugin.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapsPlugin.java
similarity index 98%
rename from packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapsPlugin.java
rename to packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapsPlugin.java
index 763cd9e..715b357 100644
--- a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapsPlugin.java
+++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapsPlugin.java
@@ -28,7 +28,7 @@
 
   @Nullable private Lifecycle lifecycle;
 
-  private static final String VIEW_TYPE = "plugins.flutter.io/google_maps";
+  private static final String VIEW_TYPE = "plugins.flutter.dev/google_maps_android";
 
   @SuppressWarnings("deprecation")
   public static void registerWith(
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/LifecycleProvider.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/LifecycleProvider.java
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/LifecycleProvider.java
rename to packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/LifecycleProvider.java
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/MarkerBuilder.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/MarkerBuilder.java
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/MarkerBuilder.java
rename to packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/MarkerBuilder.java
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/MarkerController.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/MarkerController.java
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/MarkerController.java
rename to packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/MarkerController.java
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/MarkerOptionsSink.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/MarkerOptionsSink.java
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/MarkerOptionsSink.java
rename to packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/MarkerOptionsSink.java
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/MarkersController.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/MarkersController.java
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/MarkersController.java
rename to packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/MarkersController.java
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/PolygonBuilder.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/PolygonBuilder.java
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/PolygonBuilder.java
rename to packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/PolygonBuilder.java
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/PolygonController.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/PolygonController.java
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/PolygonController.java
rename to packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/PolygonController.java
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/PolygonOptionsSink.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/PolygonOptionsSink.java
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/PolygonOptionsSink.java
rename to packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/PolygonOptionsSink.java
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/PolygonsController.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/PolygonsController.java
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/PolygonsController.java
rename to packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/PolygonsController.java
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/PolylineBuilder.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/PolylineBuilder.java
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/PolylineBuilder.java
rename to packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/PolylineBuilder.java
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/PolylineController.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/PolylineController.java
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/PolylineController.java
rename to packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/PolylineController.java
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/PolylineOptionsSink.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/PolylineOptionsSink.java
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/PolylineOptionsSink.java
rename to packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/PolylineOptionsSink.java
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/PolylinesController.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/PolylinesController.java
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/PolylinesController.java
rename to packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/PolylinesController.java
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlayBuilder.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlayBuilder.java
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlayBuilder.java
rename to packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlayBuilder.java
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlayController.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlayController.java
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlayController.java
rename to packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlayController.java
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlaySink.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlaySink.java
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlaySink.java
rename to packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlaySink.java
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlaysController.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlaysController.java
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlaysController.java
rename to packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/TileOverlaysController.java
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/TileProviderController.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/TileProviderController.java
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/TileProviderController.java
rename to packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/TileProviderController.java
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/test/java/io/flutter/plugins/googlemaps/CircleBuilderTest.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/CircleBuilderTest.java
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/android/src/test/java/io/flutter/plugins/googlemaps/CircleBuilderTest.java
rename to packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/CircleBuilderTest.java
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/test/java/io/flutter/plugins/googlemaps/CircleControllerTest.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/CircleControllerTest.java
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/android/src/test/java/io/flutter/plugins/googlemaps/CircleControllerTest.java
rename to packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/CircleControllerTest.java
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/test/java/io/flutter/plugins/googlemaps/GoogleMapControllerTest.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/GoogleMapControllerTest.java
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/android/src/test/java/io/flutter/plugins/googlemaps/GoogleMapControllerTest.java
rename to packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/GoogleMapControllerTest.java
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/test/java/io/flutter/plugins/googlemaps/MarkersControllerTest.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/MarkersControllerTest.java
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/android/src/test/java/io/flutter/plugins/googlemaps/MarkersControllerTest.java
rename to packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/MarkersControllerTest.java
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/test/java/io/flutter/plugins/googlemaps/PolygonBuilderTest.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/PolygonBuilderTest.java
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/android/src/test/java/io/flutter/plugins/googlemaps/PolygonBuilderTest.java
rename to packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/PolygonBuilderTest.java
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/test/java/io/flutter/plugins/googlemaps/PolygonControllerTest.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/PolygonControllerTest.java
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/android/src/test/java/io/flutter/plugins/googlemaps/PolygonControllerTest.java
rename to packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/PolygonControllerTest.java
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/test/java/io/flutter/plugins/googlemaps/PolylineBuilderTest.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/PolylineBuilderTest.java
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/android/src/test/java/io/flutter/plugins/googlemaps/PolylineBuilderTest.java
rename to packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/PolylineBuilderTest.java
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/test/java/io/flutter/plugins/googlemaps/PolylineControllerTest.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/PolylineControllerTest.java
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/android/src/test/java/io/flutter/plugins/googlemaps/PolylineControllerTest.java
rename to packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/PolylineControllerTest.java
diff --git a/packages/google_maps_flutter/google_maps_flutter/android/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/packages/google_maps_flutter/google_maps_flutter_android/android/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/android/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
rename to packages/google_maps_flutter/google_maps_flutter_android/android/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/.metadata b/packages/google_maps_flutter/google_maps_flutter_android/example/.metadata
new file mode 100644
index 0000000..46e884c
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/example/.metadata
@@ -0,0 +1,8 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled and should not be manually edited.
+
+version:
+  revision: 3ea4d06340a97a1e9d7cae97567c64e0569dcaa2
+  channel: beta
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/README.md b/packages/google_maps_flutter/google_maps_flutter_android/example/README.md
new file mode 100644
index 0000000..c885264
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/example/README.md
@@ -0,0 +1,3 @@
+# google_maps_flutter_example
+
+Demonstrates how to use the google_maps_flutter plugin.
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/build.gradle b/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/build.gradle
new file mode 100644
index 0000000..f6d29f6
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/build.gradle
@@ -0,0 +1,73 @@
+def localProperties = new Properties()
+def localPropertiesFile = rootProject.file('local.properties')
+if (localPropertiesFile.exists()) {
+    localPropertiesFile.withReader('UTF-8') { reader ->
+        localProperties.load(reader)
+    }
+}
+
+def flutterRoot = localProperties.getProperty('flutter.sdk')
+if (flutterRoot == null) {
+    throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
+}
+
+def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
+if (flutterVersionCode == null) {
+    flutterVersionCode = '1'
+}
+
+def flutterVersionName = localProperties.getProperty('flutter.versionName')
+if (flutterVersionName == null) {
+    flutterVersionName = '1.0'
+}
+
+apply plugin: 'com.android.application'
+apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
+
+android {
+    compileSdkVersion 31
+
+    lintOptions {
+        disable 'InvalidPackage'
+    }
+
+    defaultConfig {
+        applicationId "io.flutter.plugins.googlemapsexample"
+        minSdkVersion 20
+        targetSdkVersion 28
+        multiDexEnabled true
+        versionCode flutterVersionCode.toInteger()
+        versionName flutterVersionName
+        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+    }
+
+    defaultConfig {
+        manifestPlaceholders = [mapsApiKey: "$System.env.MAPS_API_KEY"]
+    }
+
+    buildTypes {
+        release {
+            // TODO: Add your own signing config for the release build.
+            // Signing with the debug keys for now, so `flutter run --release` works.
+            signingConfig signingConfigs.debug
+        }
+    }
+
+    testOptions {
+        unitTests {
+        includeAndroidResources = true
+        }
+    }
+
+    dependencies {
+        testImplementation 'junit:junit:4.13.2'
+        androidTestImplementation 'androidx.test:runner:1.2.0'
+        androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
+        api 'androidx.test:core:1.2.0'
+        testImplementation 'com.google.android.gms:play-services-maps:17.0.0'
+    }
+}
+
+flutter {
+    source '../..'
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/gradle/wrapper/gradle-wrapper.properties b/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..9a4163a
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java b/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java
new file mode 100644
index 0000000..0f4298d
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/src/androidTest/java/io/flutter/plugins/DartIntegrationTest.java
@@ -0,0 +1,14 @@
+// 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.
+
+package io.flutter.plugins;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface DartIntegrationTest {}
diff --git a/packages/google_maps_flutter/google_maps_flutter/example/android/app/src/androidTest/java/io/flutter/plugins/googlemapsexample/GoogleMapsTest.java b/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/src/androidTest/java/io/flutter/plugins/googlemapsexample/GoogleMapsTest.java
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/example/android/app/src/androidTest/java/io/flutter/plugins/googlemapsexample/GoogleMapsTest.java
rename to packages/google_maps_flutter/google_maps_flutter_android/example/android/app/src/androidTest/java/io/flutter/plugins/googlemapsexample/GoogleMapsTest.java
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/src/androidTest/java/io/flutter/plugins/googlemapsexample/MainActivityTest.java b/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/src/androidTest/java/io/flutter/plugins/googlemapsexample/MainActivityTest.java
new file mode 100644
index 0000000..244a22b
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/src/androidTest/java/io/flutter/plugins/googlemapsexample/MainActivityTest.java
@@ -0,0 +1,19 @@
+// 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.
+
+package io.flutter.plugins.googlemaps;
+
+import androidx.test.rule.ActivityTestRule;
+import dev.flutter.plugins.integration_test.FlutterTestRunner;
+import io.flutter.embedding.android.FlutterActivity;
+import io.flutter.plugins.DartIntegrationTest;
+import org.junit.Rule;
+import org.junit.runner.RunWith;
+
+@DartIntegrationTest
+@RunWith(FlutterTestRunner.class)
+public class MainActivityTest {
+  @Rule
+  public ActivityTestRule<FlutterActivity> rule = new ActivityTestRule<>(FlutterActivity.class);
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/src/debug/AndroidManifest.xml b/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/src/debug/AndroidManifest.xml
new file mode 100644
index 0000000..9c1f83d
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/src/debug/AndroidManifest.xml
@@ -0,0 +1,17 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="io.flutter.plugins.googlemapsexample">
+    <!-- Flutter needs internet permission to communicate with the running application
+         to allow setting breakpoints, to provide hot reload, etc.
+    -->
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <application android:usesCleartextTraffic="true">
+        <activity
+            android:name=".GoogleMapsTestActivity"
+            android:launchMode="singleTop"
+            android:theme="@style/LaunchTheme"
+            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
+            android:hardwareAccelerated="true"
+            android:windowSoftInputMode="adjustResize">
+        </activity>
+    </application>
+</manifest>
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/src/debug/java/io/flutter/plugins/googlemapsexample/GoogleMapsTestActivity.java b/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/src/debug/java/io/flutter/plugins/googlemapsexample/GoogleMapsTestActivity.java
new file mode 100644
index 0000000..e183a7c
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/src/debug/java/io/flutter/plugins/googlemapsexample/GoogleMapsTestActivity.java
@@ -0,0 +1,20 @@
+// 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.
+
+package io.flutter.plugins.googlemapsexample;
+
+import androidx.annotation.NonNull;
+import io.flutter.embedding.android.FlutterActivity;
+import io.flutter.embedding.engine.FlutterEngine;
+
+// Makes the FlutterEngine accessible for testing.
+public class GoogleMapsTestActivity extends FlutterActivity {
+  public FlutterEngine engine;
+
+  @Override
+  public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
+    super.configureFlutterEngine(flutterEngine);
+    engine = flutterEngine;
+  }
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/src/main/AndroidManifest.xml b/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..815074b
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="io.flutter.plugins.googlemapsexample">
+
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+
+    <application android:label="google_maps_flutter_example" android:icon="@mipmap/ic_launcher">
+      <meta-data
+            android:name="com.google.android.gms.version"
+            android:value="@integer/google_play_services_version" />
+        <!-- Update this value to your google maps api key. -->
+        <meta-data
+            android:name="com.google.android.geo.API_KEY"
+            android:value="${mapsApiKey}" />
+        <activity android:name="io.flutter.embedding.android.FlutterActivity"
+            android:theme="@style/LaunchTheme"
+            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale"
+            android:hardwareAccelerated="true"
+            android:windowSoftInputMode="adjustResize">
+            <meta-data
+                android:name="io.flutter.app.android.SplashScreenUntilFirstFrame"
+                android:value="true" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN"/>
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+        <meta-data android:name="flutterEmbedding" android:value="2"/>
+    </application>
+</manifest>
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/src/main/res/drawable/launch_background.xml b/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/src/main/res/drawable/launch_background.xml
new file mode 100644
index 0000000..304732f
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/src/main/res/drawable/launch_background.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Modify this file to customize your launch splash screen -->
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@android:color/white" />
+
+    <!-- You can insert your own image assets here -->
+    <!-- <item>
+        <bitmap
+            android:gravity="center"
+            android:src="@mipmap/launch_image" />
+    </item> -->
+</layer-list>
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..db77bb4
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Binary files differ
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..17987b7
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Binary files differ
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..09d4391
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Binary files differ
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..d5f1c8d
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Binary files differ
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..4d6372e
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Binary files differ
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/src/main/res/values/styles.xml b/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..00fa441
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/example/android/app/src/main/res/values/styles.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
+        <!-- Show a splash screen on the activity. Automatically removed when
+             Flutter draws its first frame -->
+        <item name="android:windowBackground">@drawable/launch_background</item>
+    </style>
+</resources>
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/android/build.gradle b/packages/google_maps_flutter/google_maps_flutter_android/example/android/build.gradle
new file mode 100644
index 0000000..4d8d45d
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/example/android/build.gradle
@@ -0,0 +1,29 @@
+buildscript {
+    repositories {
+        google()
+        mavenCentral()
+    }
+
+    dependencies {
+        classpath 'com.android.tools.build:gradle:3.5.4'
+    }
+}
+
+allprojects {
+    repositories {
+        google()
+        mavenCentral()
+    }
+}
+
+rootProject.buildDir = '../build'
+subprojects {
+    project.buildDir = "${rootProject.buildDir}/${project.name}"
+}
+subprojects {
+    project.evaluationDependsOn(':app')
+}
+
+task clean(type: Delete) {
+    delete rootProject.buildDir
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/android/gradle.properties b/packages/google_maps_flutter/google_maps_flutter_android/example/android/gradle.properties
new file mode 100644
index 0000000..207beb6
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/example/android/gradle.properties
@@ -0,0 +1,5 @@
+org.gradle.jvmargs=-Xmx1536M
+android.enableR8=true
+android.useAndroidX=true
+android.enableJetifier=true
+
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/google_maps_flutter/google_maps_flutter_android/example/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..9ec7236
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/example/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Fri Jun 23 08:50:38 CEST 2017
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.1-all.zip
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/android/settings.gradle b/packages/google_maps_flutter/google_maps_flutter_android/example/android/settings.gradle
new file mode 100644
index 0000000..5a2f14f
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/example/android/settings.gradle
@@ -0,0 +1,15 @@
+include ':app'
+
+def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
+
+def plugins = new Properties()
+def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
+if (pluginsFile.exists()) {
+    pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
+}
+
+plugins.each { name, path ->
+    def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
+    include ":$name"
+    project(":$name").projectDir = pluginDirectory
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/assets/2.0x/red_square.png b/packages/google_maps_flutter/google_maps_flutter_android/example/assets/2.0x/red_square.png
new file mode 100644
index 0000000..0f82237
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/example/assets/2.0x/red_square.png
Binary files differ
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/assets/3.0x/red_square.png b/packages/google_maps_flutter/google_maps_flutter_android/example/assets/3.0x/red_square.png
new file mode 100644
index 0000000..7e27399
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/example/assets/3.0x/red_square.png
Binary files differ
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/assets/night_mode.json b/packages/google_maps_flutter/google_maps_flutter_android/example/assets/night_mode.json
new file mode 100644
index 0000000..1f16e00
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/example/assets/night_mode.json
@@ -0,0 +1,162 @@
+[
+  {
+    "elementType": "geometry",
+    "stylers": [
+      {
+        "color": "#242f3e"
+      }
+    ]
+  },
+  {
+    "elementType": "labels.text.fill",
+    "stylers": [
+      {
+        "color": "#746855"
+      }
+    ]
+  },
+  {
+    "elementType": "labels.text.stroke",
+    "stylers": [
+      {
+        "color": "#242f3e"
+      }
+    ]
+  },
+  {
+    "featureType": "administrative.locality",
+    "elementType": "labels.text.fill",
+    "stylers": [
+      {
+        "color": "#d59563"
+      }
+    ]
+  },
+  {
+    "featureType": "poi",
+    "elementType": "labels.text.fill",
+    "stylers": [
+      {
+        "color": "#d59563"
+      }
+    ]
+  },
+  {
+    "featureType": "poi.park",
+    "elementType": "geometry",
+    "stylers": [
+      {
+        "color": "#263c3f"
+      }
+    ]
+  },
+  {
+    "featureType": "poi.park",
+    "elementType": "labels.text.fill",
+    "stylers": [
+      {
+        "color": "#6b9a76"
+      }
+    ]
+  },
+  {
+    "featureType": "road",
+    "elementType": "geometry",
+    "stylers": [
+      {
+        "color": "#38414e"
+      }
+    ]
+  },
+  {
+    "featureType": "road",
+    "elementType": "geometry.stroke",
+    "stylers": [
+      {
+        "color": "#212a37"
+      }
+    ]
+  },
+  {
+    "featureType": "road",
+    "elementType": "labels.text.fill",
+    "stylers": [
+      {
+        "color": "#9ca5b3"
+      }
+    ]
+  },
+  {
+    "featureType": "road.highway",
+    "elementType": "geometry",
+    "stylers": [
+      {
+        "color": "#746855"
+      }
+    ]
+  },
+  {
+    "featureType": "road.highway",
+    "elementType": "geometry.stroke",
+    "stylers": [
+      {
+        "color": "#1f2835"
+      }
+    ]
+  },
+  {
+    "featureType": "road.highway",
+    "elementType": "labels.text.fill",
+    "stylers": [
+      {
+        "color": "#f3d19c"
+      }
+    ]
+  },
+  {
+    "featureType": "transit",
+    "elementType": "geometry",
+    "stylers": [
+      {
+        "color": "#2f3948"
+      }
+    ]
+  },
+  {
+    "featureType": "transit.station",
+    "elementType": "labels.text.fill",
+    "stylers": [
+      {
+        "color": "#d59563"
+      }
+    ]
+  },
+  {
+    "featureType": "water",
+    "elementType": "geometry",
+    "stylers": [
+      {
+        "color": "#17263c"
+      }
+    ]
+  },
+  {
+    "featureType": "water",
+    "elementType": "labels.text.fill",
+    "stylers": [
+      {
+        "color": "#515c6d"
+      }
+    ]
+  },
+  {
+    "featureType": "water",
+    "elementType": "labels.text.stroke",
+    "stylers": [
+      {
+        "color": "#17263c"
+      }
+    ]
+  }
+]
+
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/assets/red_square.png b/packages/google_maps_flutter/google_maps_flutter_android/example/assets/red_square.png
new file mode 100644
index 0000000..650a2de
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/example/assets/red_square.png
Binary files differ
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/integration_test/google_maps_test.dart b/packages/google_maps_flutter/google_maps_flutter_android/example/integration_test/google_maps_test.dart
new file mode 100644
index 0000000..00c51eb
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/example/integration_test/google_maps_test.dart
@@ -0,0 +1,1189 @@
+// 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 'dart:async';
+import 'dart:io';
+import 'dart:typed_data';
+import 'dart:ui' as ui;
+
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:google_maps_flutter_example/example_google_map.dart';
+import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
+import 'package:integration_test/integration_test.dart';
+
+const LatLng _kInitialMapCenter = LatLng(0, 0);
+const double _kInitialZoomLevel = 5;
+const CameraPosition _kInitialCameraPosition =
+    CameraPosition(target: _kInitialMapCenter, zoom: _kInitialZoomLevel);
+
+void main() {
+  IntegrationTestWidgetsFlutterBinding.ensureInitialized();
+  GoogleMapsFlutterPlatform.instance.enableDebugInspection();
+
+  testWidgets('testCompassToggle', (WidgetTester tester) async {
+    final Key key = GlobalKey();
+    final Completer<int> mapIdCompleter = Completer<int>();
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        compassEnabled: false,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          mapIdCompleter.complete(controller.mapId);
+        },
+      ),
+    ));
+
+    final int mapId = await mapIdCompleter.future;
+    final GoogleMapsInspectorPlatform inspector =
+        GoogleMapsInspectorPlatform.instance!;
+    bool compassEnabled = await inspector.isCompassEnabled(mapId: mapId);
+    expect(compassEnabled, false);
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        compassEnabled: true,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          fail('OnMapCreated should get called only once.');
+        },
+      ),
+    ));
+
+    compassEnabled = await inspector.isCompassEnabled(mapId: mapId);
+    expect(compassEnabled, true);
+  });
+
+  testWidgets('testMapToolbarToggle', (WidgetTester tester) async {
+    final Key key = GlobalKey();
+    final Completer<int> mapIdCompleter = Completer<int>();
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        mapToolbarEnabled: false,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          mapIdCompleter.complete(controller.mapId);
+        },
+      ),
+    ));
+
+    final int mapId = await mapIdCompleter.future;
+    final GoogleMapsInspectorPlatform inspector =
+        GoogleMapsInspectorPlatform.instance!;
+    bool mapToolbarEnabled = await inspector.isMapToolbarEnabled(mapId: mapId);
+    expect(mapToolbarEnabled, false);
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        mapToolbarEnabled: true,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          fail('OnMapCreated should get called only once.');
+        },
+      ),
+    ));
+
+    mapToolbarEnabled = await inspector.isMapToolbarEnabled(mapId: mapId);
+    expect(mapToolbarEnabled, true);
+  });
+
+  testWidgets('updateMinMaxZoomLevels', (WidgetTester tester) async {
+    final Key key = GlobalKey();
+    final Completer<ExampleGoogleMapController> controllerCompleter =
+        Completer<ExampleGoogleMapController>();
+
+    const MinMaxZoomPreference initialZoomLevel = MinMaxZoomPreference(4, 8);
+    const MinMaxZoomPreference finalZoomLevel = MinMaxZoomPreference(6, 10);
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        minMaxZoomPreference: initialZoomLevel,
+        onMapCreated: (ExampleGoogleMapController c) async {
+          controllerCompleter.complete(c);
+        },
+      ),
+    ));
+
+    final ExampleGoogleMapController controller =
+        await controllerCompleter.future;
+
+    // On Android, zooming with zoomTo is constrained by the min/max.
+    await controller.moveCamera(CameraUpdate.zoomTo(15));
+    await tester.pumpAndSettle();
+    double? zoomLevel = await controller.getZoomLevel();
+    expect(zoomLevel, equals(initialZoomLevel.maxZoom));
+
+    await controller.moveCamera(CameraUpdate.zoomTo(1));
+    await tester.pumpAndSettle();
+    zoomLevel = await controller.getZoomLevel();
+    expect(zoomLevel, equals(initialZoomLevel.minZoom));
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        minMaxZoomPreference: finalZoomLevel,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          fail('OnMapCreated should get called only once.');
+        },
+      ),
+    ));
+
+    await controller.moveCamera(CameraUpdate.zoomTo(15));
+    await tester.pumpAndSettle();
+    zoomLevel = await controller.getZoomLevel();
+    expect(zoomLevel, equals(finalZoomLevel.maxZoom));
+
+    await controller.moveCamera(CameraUpdate.zoomTo(1));
+    await tester.pumpAndSettle();
+    zoomLevel = await controller.getZoomLevel();
+    expect(zoomLevel, equals(finalZoomLevel.minZoom));
+  });
+
+  testWidgets('testZoomGesturesEnabled', (WidgetTester tester) async {
+    final Key key = GlobalKey();
+    final Completer<int> mapIdCompleter = Completer<int>();
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        zoomGesturesEnabled: false,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          mapIdCompleter.complete(controller.mapId);
+        },
+      ),
+    ));
+
+    final int mapId = await mapIdCompleter.future;
+    final GoogleMapsInspectorPlatform inspector =
+        GoogleMapsInspectorPlatform.instance!;
+    bool zoomGesturesEnabled =
+        await inspector.areZoomGesturesEnabled(mapId: mapId);
+    expect(zoomGesturesEnabled, false);
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        zoomGesturesEnabled: true,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          fail('OnMapCreated should get called only once.');
+        },
+      ),
+    ));
+
+    zoomGesturesEnabled = await inspector.areZoomGesturesEnabled(mapId: mapId);
+    expect(zoomGesturesEnabled, true);
+  });
+
+  testWidgets('testZoomControlsEnabled', (WidgetTester tester) async {
+    final Key key = GlobalKey();
+    final Completer<int> mapIdCompleter = Completer<int>();
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          mapIdCompleter.complete(controller.mapId);
+        },
+      ),
+    ));
+
+    final int mapId = await mapIdCompleter.future;
+    final GoogleMapsInspectorPlatform inspector =
+        GoogleMapsInspectorPlatform.instance!;
+    bool zoomControlsEnabled =
+        await inspector.areZoomControlsEnabled(mapId: mapId);
+    expect(zoomControlsEnabled, true);
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        zoomControlsEnabled: false,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          fail('OnMapCreated should get called only once.');
+        },
+      ),
+    ));
+
+    zoomControlsEnabled = await inspector.areZoomControlsEnabled(mapId: mapId);
+    expect(zoomControlsEnabled, false);
+  });
+
+  testWidgets('testLiteModeEnabled', (WidgetTester tester) async {
+    final Key key = GlobalKey();
+    final Completer<int> mapIdCompleter = Completer<int>();
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        liteModeEnabled: false,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          mapIdCompleter.complete(controller.mapId);
+        },
+      ),
+    ));
+
+    final int mapId = await mapIdCompleter.future;
+    final GoogleMapsInspectorPlatform inspector =
+        GoogleMapsInspectorPlatform.instance!;
+    bool liteModeEnabled = await inspector.isLiteModeEnabled(mapId: mapId);
+    expect(liteModeEnabled, false);
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        liteModeEnabled: true,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          fail('OnMapCreated should get called only once.');
+        },
+      ),
+    ));
+
+    liteModeEnabled = await inspector.isLiteModeEnabled(mapId: mapId);
+    expect(liteModeEnabled, true);
+  });
+
+  testWidgets('testRotateGesturesEnabled', (WidgetTester tester) async {
+    final Key key = GlobalKey();
+    final Completer<int> mapIdCompleter = Completer<int>();
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        rotateGesturesEnabled: false,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          mapIdCompleter.complete(controller.mapId);
+        },
+      ),
+    ));
+
+    final int mapId = await mapIdCompleter.future;
+    final GoogleMapsInspectorPlatform inspector =
+        GoogleMapsInspectorPlatform.instance!;
+    bool rotateGesturesEnabled =
+        await inspector.areRotateGesturesEnabled(mapId: mapId);
+    expect(rotateGesturesEnabled, false);
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        rotateGesturesEnabled: true,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          fail('OnMapCreated should get called only once.');
+        },
+      ),
+    ));
+
+    rotateGesturesEnabled =
+        await inspector.areRotateGesturesEnabled(mapId: mapId);
+    expect(rotateGesturesEnabled, true);
+  });
+
+  testWidgets('testTiltGesturesEnabled', (WidgetTester tester) async {
+    final Key key = GlobalKey();
+    final Completer<int> mapIdCompleter = Completer<int>();
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        tiltGesturesEnabled: false,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          mapIdCompleter.complete(controller.mapId);
+        },
+      ),
+    ));
+
+    final int mapId = await mapIdCompleter.future;
+    final GoogleMapsInspectorPlatform inspector =
+        GoogleMapsInspectorPlatform.instance!;
+    bool tiltGesturesEnabled =
+        await inspector.areTiltGesturesEnabled(mapId: mapId);
+    expect(tiltGesturesEnabled, false);
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        tiltGesturesEnabled: true,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          fail('OnMapCreated should get called only once.');
+        },
+      ),
+    ));
+
+    tiltGesturesEnabled = await inspector.areTiltGesturesEnabled(mapId: mapId);
+    expect(tiltGesturesEnabled, true);
+  });
+
+  testWidgets('testScrollGesturesEnabled', (WidgetTester tester) async {
+    final Key key = GlobalKey();
+    final Completer<int> mapIdCompleter = Completer<int>();
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        scrollGesturesEnabled: false,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          mapIdCompleter.complete(controller.mapId);
+        },
+      ),
+    ));
+
+    final int mapId = await mapIdCompleter.future;
+    final GoogleMapsInspectorPlatform inspector =
+        GoogleMapsInspectorPlatform.instance!;
+    bool scrollGesturesEnabled =
+        await inspector.areScrollGesturesEnabled(mapId: mapId);
+    expect(scrollGesturesEnabled, false);
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        scrollGesturesEnabled: true,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          fail('OnMapCreated should get called only once.');
+        },
+      ),
+    ));
+
+    scrollGesturesEnabled =
+        await inspector.areScrollGesturesEnabled(mapId: mapId);
+    expect(scrollGesturesEnabled, true);
+  });
+
+  testWidgets('testInitialCenterLocationAtCenter', (WidgetTester tester) async {
+    await tester.binding.setSurfaceSize(const Size(800, 600));
+
+    final Completer<ExampleGoogleMapController> mapControllerCompleter =
+        Completer<ExampleGoogleMapController>();
+    final Key key = GlobalKey();
+    await tester.pumpWidget(
+      Directionality(
+        textDirection: TextDirection.ltr,
+        child: ExampleGoogleMap(
+          key: key,
+          initialCameraPosition: _kInitialCameraPosition,
+          onMapCreated: (ExampleGoogleMapController controller) {
+            mapControllerCompleter.complete(controller);
+          },
+        ),
+      ),
+    );
+    final ExampleGoogleMapController mapController =
+        await mapControllerCompleter.future;
+
+    await tester.pumpAndSettle();
+
+    // TODO(cyanglaz): Remove this after we added `mapRendered` callback, and
+    // `mapControllerCompleter.complete(controller)` above should happen in
+    // `mapRendered`.
+    // https://github.com/flutter/flutter/issues/54758
+    await Future<void>.delayed(const Duration(seconds: 1));
+
+    final ScreenCoordinate coordinate =
+        await mapController.getScreenCoordinate(_kInitialCameraPosition.target);
+    final Rect rect = tester.getRect(find.byKey(key));
+    expect(
+        coordinate.x,
+        ((rect.center.dx - rect.topLeft.dx) *
+                tester.binding.window.devicePixelRatio)
+            .round());
+    expect(
+        coordinate.y,
+        ((rect.center.dy - rect.topLeft.dy) *
+                tester.binding.window.devicePixelRatio)
+            .round());
+    await tester.binding.setSurfaceSize(null);
+  });
+
+  testWidgets('testGetVisibleRegion', (WidgetTester tester) async {
+    final Key key = GlobalKey();
+    final LatLngBounds zeroLatLngBounds = LatLngBounds(
+        southwest: const LatLng(0, 0), northeast: const LatLng(0, 0));
+
+    final Completer<ExampleGoogleMapController> mapControllerCompleter =
+        Completer<ExampleGoogleMapController>();
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          mapControllerCompleter.complete(controller);
+        },
+      ),
+    ));
+    await tester.pumpAndSettle();
+
+    final ExampleGoogleMapController mapController =
+        await mapControllerCompleter.future;
+
+    final LatLngBounds firstVisibleRegion =
+        await mapController.getVisibleRegion();
+
+    expect(firstVisibleRegion, isNotNull);
+    expect(firstVisibleRegion.southwest, isNotNull);
+    expect(firstVisibleRegion.northeast, isNotNull);
+    expect(firstVisibleRegion, isNot(zeroLatLngBounds));
+    expect(firstVisibleRegion.contains(_kInitialMapCenter), isTrue);
+
+    // Making a new `LatLngBounds` about (10, 10) distance south west to the `firstVisibleRegion`.
+    // The size of the `LatLngBounds` is 10 by 10.
+    final LatLng southWest = LatLng(firstVisibleRegion.southwest.latitude - 20,
+        firstVisibleRegion.southwest.longitude - 20);
+    final LatLng northEast = LatLng(firstVisibleRegion.southwest.latitude - 10,
+        firstVisibleRegion.southwest.longitude - 10);
+    final LatLng newCenter = LatLng(
+      (northEast.latitude + southWest.latitude) / 2,
+      (northEast.longitude + southWest.longitude) / 2,
+    );
+
+    expect(firstVisibleRegion.contains(northEast), isFalse);
+    expect(firstVisibleRegion.contains(southWest), isFalse);
+
+    final LatLngBounds latLngBounds =
+        LatLngBounds(southwest: southWest, northeast: northEast);
+
+    // TODO(iskakaushik): non-zero padding is needed for some device configurations
+    // https://github.com/flutter/flutter/issues/30575
+    const double padding = 0;
+    await mapController
+        .moveCamera(CameraUpdate.newLatLngBounds(latLngBounds, padding));
+    await tester.pumpAndSettle(const Duration(seconds: 3));
+
+    final LatLngBounds secondVisibleRegion =
+        await mapController.getVisibleRegion();
+
+    expect(secondVisibleRegion, isNotNull);
+    expect(secondVisibleRegion.southwest, isNotNull);
+    expect(secondVisibleRegion.northeast, isNotNull);
+    expect(secondVisibleRegion, isNot(zeroLatLngBounds));
+
+    expect(firstVisibleRegion, isNot(secondVisibleRegion));
+    expect(secondVisibleRegion.contains(newCenter), isTrue);
+  });
+
+  testWidgets('testTraffic', (WidgetTester tester) async {
+    final Key key = GlobalKey();
+    final Completer<int> mapIdCompleter = Completer<int>();
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        trafficEnabled: true,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          mapIdCompleter.complete(controller.mapId);
+        },
+      ),
+    ));
+
+    final int mapId = await mapIdCompleter.future;
+    final GoogleMapsInspectorPlatform inspector =
+        GoogleMapsInspectorPlatform.instance!;
+    bool isTrafficEnabled = await inspector.isTrafficEnabled(mapId: mapId);
+    expect(isTrafficEnabled, true);
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        trafficEnabled: false,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          fail('OnMapCreated should get called only once.');
+        },
+      ),
+    ));
+
+    isTrafficEnabled = await inspector.isTrafficEnabled(mapId: mapId);
+    expect(isTrafficEnabled, false);
+  });
+
+  testWidgets('testBuildings', (WidgetTester tester) async {
+    final Key key = GlobalKey();
+    final Completer<int> mapIdCompleter = Completer<int>();
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        buildingsEnabled: true,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          mapIdCompleter.complete(controller.mapId);
+        },
+      ),
+    ));
+
+    final int mapId = await mapIdCompleter.future;
+    final GoogleMapsInspectorPlatform inspector =
+        GoogleMapsInspectorPlatform.instance!;
+    final bool isBuildingsEnabled =
+        await inspector.areBuildingsEnabled(mapId: mapId);
+    expect(isBuildingsEnabled, true);
+  });
+
+  testWidgets('testMyLocationButtonToggle', (WidgetTester tester) async {
+    final Key key = GlobalKey();
+    final Completer<int> mapIdCompleter = Completer<int>();
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        myLocationButtonEnabled: true,
+        myLocationEnabled: false,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          mapIdCompleter.complete(controller.mapId);
+        },
+      ),
+    ));
+
+    final int mapId = await mapIdCompleter.future;
+    final GoogleMapsInspectorPlatform inspector =
+        GoogleMapsInspectorPlatform.instance!;
+    bool myLocationButtonEnabled =
+        await inspector.isMyLocationButtonEnabled(mapId: mapId);
+    expect(myLocationButtonEnabled, true);
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        myLocationButtonEnabled: false,
+        myLocationEnabled: false,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          fail('OnMapCreated should get called only once.');
+        },
+      ),
+    ));
+
+    myLocationButtonEnabled =
+        await inspector.isMyLocationButtonEnabled(mapId: mapId);
+    expect(myLocationButtonEnabled, false);
+  },
+      // Location button tests are skipped in Android because we don't have location permission to test.
+      skip: true);
+
+  testWidgets('testMyLocationButton initial value false',
+      (WidgetTester tester) async {
+    final Key key = GlobalKey();
+    final Completer<int> mapIdCompleter = Completer<int>();
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        myLocationButtonEnabled: false,
+        myLocationEnabled: false,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          mapIdCompleter.complete(controller.mapId);
+        },
+      ),
+    ));
+
+    final int mapId = await mapIdCompleter.future;
+    final GoogleMapsInspectorPlatform inspector =
+        GoogleMapsInspectorPlatform.instance!;
+    final bool myLocationButtonEnabled =
+        await inspector.isMyLocationButtonEnabled(mapId: mapId);
+    expect(myLocationButtonEnabled, false);
+  },
+      // Location button tests are skipped in Android because we don't have location permission to test.
+      skip: true);
+
+  testWidgets('testMyLocationButton initial value true',
+      (WidgetTester tester) async {
+    final Key key = GlobalKey();
+    final Completer<int> mapIdCompleter = Completer<int>();
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        myLocationButtonEnabled: true,
+        myLocationEnabled: false,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          mapIdCompleter.complete(controller.mapId);
+        },
+      ),
+    ));
+
+    final int mapId = await mapIdCompleter.future;
+    final GoogleMapsInspectorPlatform inspector =
+        GoogleMapsInspectorPlatform.instance!;
+    final bool myLocationButtonEnabled =
+        await inspector.isMyLocationButtonEnabled(mapId: mapId);
+    expect(myLocationButtonEnabled, true);
+  },
+      // Location button tests are skipped in Android because we don't have location permission to test.
+      skip: true);
+
+  testWidgets('testSetMapStyle valid Json String', (WidgetTester tester) async {
+    final Key key = GlobalKey();
+    final Completer<ExampleGoogleMapController> controllerCompleter =
+        Completer<ExampleGoogleMapController>();
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          controllerCompleter.complete(controller);
+        },
+      ),
+    ));
+
+    final ExampleGoogleMapController controller =
+        await controllerCompleter.future;
+    const String mapStyle =
+        '[{"elementType":"geometry","stylers":[{"color":"#242f3e"}]}]';
+    await controller.setMapStyle(mapStyle);
+  });
+
+  testWidgets('testSetMapStyle invalid Json String',
+      (WidgetTester tester) async {
+    final Key key = GlobalKey();
+    final Completer<ExampleGoogleMapController> controllerCompleter =
+        Completer<ExampleGoogleMapController>();
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          controllerCompleter.complete(controller);
+        },
+      ),
+    ));
+
+    final ExampleGoogleMapController controller =
+        await controllerCompleter.future;
+
+    try {
+      await controller.setMapStyle('invalid_value');
+      fail('expected MapStyleException');
+    } on MapStyleException catch (e) {
+      expect(e.cause, isNotNull);
+    }
+  });
+
+  testWidgets('testSetMapStyle null string', (WidgetTester tester) async {
+    final Key key = GlobalKey();
+    final Completer<ExampleGoogleMapController> controllerCompleter =
+        Completer<ExampleGoogleMapController>();
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          controllerCompleter.complete(controller);
+        },
+      ),
+    ));
+
+    final ExampleGoogleMapController controller =
+        await controllerCompleter.future;
+    await controller.setMapStyle(null);
+  });
+
+  testWidgets('testGetLatLng', (WidgetTester tester) async {
+    final Key key = GlobalKey();
+    final Completer<ExampleGoogleMapController> controllerCompleter =
+        Completer<ExampleGoogleMapController>();
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          controllerCompleter.complete(controller);
+        },
+      ),
+    ));
+
+    final ExampleGoogleMapController controller =
+        await controllerCompleter.future;
+
+    await tester.pumpAndSettle();
+    // TODO(cyanglaz): Remove this after we added `mapRendered` callback, and `mapControllerCompleter.complete(controller)` above should happen
+    // in `mapRendered`.
+    // https://github.com/flutter/flutter/issues/54758
+    await Future<void>.delayed(const Duration(seconds: 1));
+
+    final LatLngBounds visibleRegion = await controller.getVisibleRegion();
+    final LatLng topLeft =
+        await controller.getLatLng(const ScreenCoordinate(x: 0, y: 0));
+    final LatLng northWest = LatLng(
+      visibleRegion.northeast.latitude,
+      visibleRegion.southwest.longitude,
+    );
+
+    expect(topLeft, northWest);
+  });
+
+  testWidgets('testGetZoomLevel', (WidgetTester tester) async {
+    final Key key = GlobalKey();
+    final Completer<ExampleGoogleMapController> controllerCompleter =
+        Completer<ExampleGoogleMapController>();
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          controllerCompleter.complete(controller);
+        },
+      ),
+    ));
+
+    final ExampleGoogleMapController controller =
+        await controllerCompleter.future;
+
+    await tester.pumpAndSettle();
+    // TODO(cyanglaz): Remove this after we added `mapRendered` callback, and `mapControllerCompleter.complete(controller)` above should happen
+    // in `mapRendered`.
+    // https://github.com/flutter/flutter/issues/54758
+    await Future<void>.delayed(const Duration(seconds: 1));
+
+    double zoom = await controller.getZoomLevel();
+    expect(zoom, _kInitialZoomLevel);
+
+    await controller.moveCamera(CameraUpdate.zoomTo(7));
+    await tester.pumpAndSettle();
+    zoom = await controller.getZoomLevel();
+    expect(zoom, equals(7));
+  });
+
+  testWidgets('testScreenCoordinate', (WidgetTester tester) async {
+    final Key key = GlobalKey();
+    final Completer<ExampleGoogleMapController> controllerCompleter =
+        Completer<ExampleGoogleMapController>();
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          controllerCompleter.complete(controller);
+        },
+      ),
+    ));
+    final ExampleGoogleMapController controller =
+        await controllerCompleter.future;
+
+    await tester.pumpAndSettle();
+    // TODO(cyanglaz): Remove this after we added `mapRendered` callback, and `mapControllerCompleter.complete(controller)` above should happen
+    // in `mapRendered`.
+    // https://github.com/flutter/flutter/issues/54758
+    await Future<void>.delayed(const Duration(seconds: 1));
+
+    final LatLngBounds visibleRegion = await controller.getVisibleRegion();
+    final LatLng northWest = LatLng(
+      visibleRegion.northeast.latitude,
+      visibleRegion.southwest.longitude,
+    );
+    final ScreenCoordinate topLeft =
+        await controller.getScreenCoordinate(northWest);
+    expect(topLeft, const ScreenCoordinate(x: 0, y: 0));
+  });
+
+  testWidgets('testResizeWidget', (WidgetTester tester) async {
+    final Completer<ExampleGoogleMapController> controllerCompleter =
+        Completer<ExampleGoogleMapController>();
+    final ExampleGoogleMap map = ExampleGoogleMap(
+      initialCameraPosition: _kInitialCameraPosition,
+      onMapCreated: (ExampleGoogleMapController controller) async {
+        controllerCompleter.complete(controller);
+      },
+    );
+    await tester.pumpWidget(Directionality(
+        textDirection: TextDirection.ltr,
+        child: MaterialApp(
+            home: Scaffold(
+                body: SizedBox(height: 100, width: 100, child: map)))));
+    final ExampleGoogleMapController controller =
+        await controllerCompleter.future;
+
+    await tester.pumpWidget(Directionality(
+        textDirection: TextDirection.ltr,
+        child: MaterialApp(
+            home: Scaffold(
+                body: SizedBox(height: 400, width: 400, child: map)))));
+
+    await tester.pumpAndSettle();
+    // TODO(cyanglaz): Remove this after we added `mapRendered` callback, and `mapControllerCompleter.complete(controller)` above should happen
+    // in `mapRendered`.
+    // https://github.com/flutter/flutter/issues/54758
+    await Future<void>.delayed(const Duration(seconds: 1));
+
+    // Simple call to make sure that the app hasn't crashed.
+    final LatLngBounds bounds1 = await controller.getVisibleRegion();
+    final LatLngBounds bounds2 = await controller.getVisibleRegion();
+    expect(bounds1, bounds2);
+  });
+
+  testWidgets('testToggleInfoWindow', (WidgetTester tester) async {
+    const Marker marker = Marker(
+        markerId: MarkerId('marker'),
+        infoWindow: InfoWindow(title: 'InfoWindow'));
+    final Set<Marker> markers = <Marker>{marker};
+
+    final Completer<ExampleGoogleMapController> controllerCompleter =
+        Completer<ExampleGoogleMapController>();
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        initialCameraPosition: const CameraPosition(target: LatLng(10.0, 15.0)),
+        markers: markers,
+        onMapCreated: (ExampleGoogleMapController googleMapController) {
+          controllerCompleter.complete(googleMapController);
+        },
+      ),
+    ));
+
+    final ExampleGoogleMapController controller =
+        await controllerCompleter.future;
+
+    bool iwVisibleStatus =
+        await controller.isMarkerInfoWindowShown(marker.markerId);
+    expect(iwVisibleStatus, false);
+
+    await controller.showMarkerInfoWindow(marker.markerId);
+    iwVisibleStatus = await controller.isMarkerInfoWindowShown(marker.markerId);
+    expect(iwVisibleStatus, true);
+
+    await controller.hideMarkerInfoWindow(marker.markerId);
+    iwVisibleStatus = await controller.isMarkerInfoWindowShown(marker.markerId);
+    expect(iwVisibleStatus, false);
+  });
+
+  testWidgets('fromAssetImage', (WidgetTester tester) async {
+    const double pixelRatio = 2;
+    const ImageConfiguration imageConfiguration =
+        ImageConfiguration(devicePixelRatio: pixelRatio);
+    final BitmapDescriptor mip = await BitmapDescriptor.fromAssetImage(
+        imageConfiguration, 'red_square.png');
+    final BitmapDescriptor scaled = await BitmapDescriptor.fromAssetImage(
+        imageConfiguration, 'red_square.png',
+        mipmaps: false);
+    expect((mip.toJson() as List<dynamic>)[2], 1);
+    expect((scaled.toJson() as List<dynamic>)[2], 2);
+  });
+
+  testWidgets('testTakeSnapshot', (WidgetTester tester) async {
+    final Completer<ExampleGoogleMapController> controllerCompleter =
+        Completer<ExampleGoogleMapController>();
+
+    await tester.pumpWidget(
+      Directionality(
+        textDirection: TextDirection.ltr,
+        child: ExampleGoogleMap(
+          initialCameraPosition: _kInitialCameraPosition,
+          onMapCreated: (ExampleGoogleMapController controller) {
+            controllerCompleter.complete(controller);
+          },
+        ),
+      ),
+    );
+
+    await tester.pumpAndSettle(const Duration(seconds: 3));
+
+    final ExampleGoogleMapController controller =
+        await controllerCompleter.future;
+    final Uint8List? bytes = await controller.takeSnapshot();
+    expect(bytes?.isNotEmpty, true);
+  },
+      // TODO(cyanglaz): un-skip the test when we can test this on CI with API key enabled.
+      // https://github.com/flutter/flutter/issues/57057
+      skip: Platform.isAndroid);
+
+  testWidgets(
+    'set tileOverlay correctly',
+    (WidgetTester tester) async {
+      final Completer<int> mapIdCompleter = Completer<int>();
+      final TileOverlay tileOverlay1 = TileOverlay(
+        tileOverlayId: const TileOverlayId('tile_overlay_1'),
+        tileProvider: _DebugTileProvider(),
+        zIndex: 2,
+        visible: true,
+        transparency: 0.2,
+        fadeIn: true,
+      );
+
+      final TileOverlay tileOverlay2 = TileOverlay(
+        tileOverlayId: const TileOverlayId('tile_overlay_2'),
+        tileProvider: _DebugTileProvider(),
+        zIndex: 1,
+        visible: false,
+        transparency: 0.3,
+        fadeIn: false,
+      );
+      await tester.pumpWidget(
+        Directionality(
+          textDirection: TextDirection.ltr,
+          child: ExampleGoogleMap(
+            initialCameraPosition: _kInitialCameraPosition,
+            tileOverlays: <TileOverlay>{tileOverlay1, tileOverlay2},
+            onMapCreated: (ExampleGoogleMapController controller) {
+              mapIdCompleter.complete(controller.mapId);
+            },
+          ),
+        ),
+      );
+      await tester.pumpAndSettle(const Duration(seconds: 3));
+
+      final int mapId = await mapIdCompleter.future;
+      final GoogleMapsInspectorPlatform inspector =
+          GoogleMapsInspectorPlatform.instance!;
+
+      final TileOverlay tileOverlayInfo1 = (await inspector
+          .getTileOverlayInfo(tileOverlay1.mapsId, mapId: mapId))!;
+      final TileOverlay tileOverlayInfo2 = (await inspector
+          .getTileOverlayInfo(tileOverlay2.mapsId, mapId: mapId))!;
+
+      expect(tileOverlayInfo1.visible, isTrue);
+      expect(tileOverlayInfo1.fadeIn, isTrue);
+      expect(
+          tileOverlayInfo1.transparency, moreOrLessEquals(0.2, epsilon: 0.001));
+      expect(tileOverlayInfo1.zIndex, 2);
+
+      expect(tileOverlayInfo2.visible, isFalse);
+      expect(tileOverlayInfo2.fadeIn, isFalse);
+      expect(
+          tileOverlayInfo2.transparency, moreOrLessEquals(0.3, epsilon: 0.001));
+      expect(tileOverlayInfo2.zIndex, 1);
+    },
+  );
+
+  testWidgets(
+    'update tileOverlays correctly',
+    (WidgetTester tester) async {
+      final Completer<int> mapIdCompleter = Completer<int>();
+      final Key key = GlobalKey();
+      final TileOverlay tileOverlay1 = TileOverlay(
+        tileOverlayId: const TileOverlayId('tile_overlay_1'),
+        tileProvider: _DebugTileProvider(),
+        zIndex: 2,
+        visible: true,
+        transparency: 0.2,
+        fadeIn: true,
+      );
+
+      final TileOverlay tileOverlay2 = TileOverlay(
+        tileOverlayId: const TileOverlayId('tile_overlay_2'),
+        tileProvider: _DebugTileProvider(),
+        zIndex: 3,
+        visible: true,
+        transparency: 0.5,
+        fadeIn: true,
+      );
+      await tester.pumpWidget(
+        Directionality(
+          textDirection: TextDirection.ltr,
+          child: ExampleGoogleMap(
+            key: key,
+            initialCameraPosition: _kInitialCameraPosition,
+            tileOverlays: <TileOverlay>{tileOverlay1, tileOverlay2},
+            onMapCreated: (ExampleGoogleMapController controller) {
+              mapIdCompleter.complete(controller.mapId);
+            },
+          ),
+        ),
+      );
+
+      final int mapId = await mapIdCompleter.future;
+      final GoogleMapsInspectorPlatform inspector =
+          GoogleMapsInspectorPlatform.instance!;
+
+      final TileOverlay tileOverlay1New = TileOverlay(
+        tileOverlayId: const TileOverlayId('tile_overlay_1'),
+        tileProvider: _DebugTileProvider(),
+        zIndex: 1,
+        visible: false,
+        transparency: 0.3,
+        fadeIn: false,
+      );
+
+      await tester.pumpWidget(
+        Directionality(
+          textDirection: TextDirection.ltr,
+          child: ExampleGoogleMap(
+            key: key,
+            initialCameraPosition: _kInitialCameraPosition,
+            tileOverlays: <TileOverlay>{tileOverlay1New},
+            onMapCreated: (ExampleGoogleMapController controller) {
+              fail('update: OnMapCreated should get called only once.');
+            },
+          ),
+        ),
+      );
+
+      await tester.pumpAndSettle(const Duration(seconds: 3));
+
+      final TileOverlay tileOverlayInfo1 = (await inspector
+          .getTileOverlayInfo(tileOverlay1.mapsId, mapId: mapId))!;
+      final TileOverlay? tileOverlayInfo2 =
+          await inspector.getTileOverlayInfo(tileOverlay2.mapsId, mapId: mapId);
+
+      expect(tileOverlayInfo1.visible, isFalse);
+      expect(tileOverlayInfo1.fadeIn, isFalse);
+      expect(
+          tileOverlayInfo1.transparency, moreOrLessEquals(0.3, epsilon: 0.001));
+      expect(tileOverlayInfo1.zIndex, 1);
+
+      expect(tileOverlayInfo2, isNull);
+    },
+  );
+
+  testWidgets(
+    'remove tileOverlays correctly',
+    (WidgetTester tester) async {
+      final Completer<int> mapIdCompleter = Completer<int>();
+      final Key key = GlobalKey();
+      final TileOverlay tileOverlay1 = TileOverlay(
+        tileOverlayId: const TileOverlayId('tile_overlay_1'),
+        tileProvider: _DebugTileProvider(),
+        zIndex: 2,
+        visible: true,
+        transparency: 0.2,
+        fadeIn: true,
+      );
+
+      await tester.pumpWidget(
+        Directionality(
+          textDirection: TextDirection.ltr,
+          child: ExampleGoogleMap(
+            key: key,
+            initialCameraPosition: _kInitialCameraPosition,
+            tileOverlays: <TileOverlay>{tileOverlay1},
+            onMapCreated: (ExampleGoogleMapController controller) {
+              mapIdCompleter.complete(controller.mapId);
+            },
+          ),
+        ),
+      );
+
+      final int mapId = await mapIdCompleter.future;
+      final GoogleMapsInspectorPlatform inspector =
+          GoogleMapsInspectorPlatform.instance!;
+
+      await tester.pumpWidget(
+        Directionality(
+          textDirection: TextDirection.ltr,
+          child: ExampleGoogleMap(
+            key: key,
+            initialCameraPosition: _kInitialCameraPosition,
+            onMapCreated: (ExampleGoogleMapController controller) {
+              fail('OnMapCreated should get called only once.');
+            },
+          ),
+        ),
+      );
+
+      await tester.pumpAndSettle(const Duration(seconds: 3));
+      final TileOverlay? tileOverlayInfo1 =
+          await inspector.getTileOverlayInfo(tileOverlay1.mapsId, mapId: mapId);
+
+      expect(tileOverlayInfo1, isNull);
+    },
+  );
+}
+
+class _DebugTileProvider implements TileProvider {
+  _DebugTileProvider() {
+    boxPaint.isAntiAlias = true;
+    boxPaint.color = Colors.blue;
+    boxPaint.strokeWidth = 2.0;
+    boxPaint.style = PaintingStyle.stroke;
+  }
+
+  static const int width = 100;
+  static const int height = 100;
+  static final Paint boxPaint = Paint();
+  static const TextStyle textStyle = TextStyle(
+    color: Colors.red,
+    fontSize: 20,
+  );
+
+  @override
+  Future<Tile> getTile(int x, int y, int? zoom) async {
+    final ui.PictureRecorder recorder = ui.PictureRecorder();
+    final Canvas canvas = Canvas(recorder);
+    final TextSpan textSpan = TextSpan(
+      text: '$x,$y',
+      style: textStyle,
+    );
+    final TextPainter textPainter = TextPainter(
+      text: textSpan,
+      textDirection: TextDirection.ltr,
+    );
+    textPainter.layout(
+      minWidth: 0.0,
+      maxWidth: width.toDouble(),
+    );
+    const Offset offset = Offset(0, 0);
+    textPainter.paint(canvas, offset);
+    canvas.drawRect(
+        Rect.fromLTRB(0, 0, width.toDouble(), width.toDouble()), boxPaint);
+    final ui.Picture picture = recorder.endRecording();
+    final Uint8List byteData = await picture
+        .toImage(width, height)
+        .then((ui.Image image) =>
+            image.toByteData(format: ui.ImageByteFormat.png))
+        .then((ByteData? byteData) => byteData!.buffer.asUint8List());
+    return Tile(width, height, byteData);
+  }
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/lib/animate_camera.dart b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/animate_camera.dart
new file mode 100644
index 0000000..c34a3ba
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/animate_camera.dart
@@ -0,0 +1,171 @@
+// 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.
+
+// ignore_for_file: public_member_api_docs
+
+import 'package:flutter/material.dart';
+import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
+
+import 'example_google_map.dart';
+import 'page.dart';
+
+class AnimateCameraPage extends GoogleMapExampleAppPage {
+  const AnimateCameraPage({Key? key})
+      : super(const Icon(Icons.map), 'Camera control, animated', key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return const AnimateCamera();
+  }
+}
+
+class AnimateCamera extends StatefulWidget {
+  const AnimateCamera({Key? key}) : super(key: key);
+  @override
+  State createState() => AnimateCameraState();
+}
+
+class AnimateCameraState extends State<AnimateCamera> {
+  ExampleGoogleMapController? mapController;
+
+  // ignore: use_setters_to_change_properties
+  void _onMapCreated(ExampleGoogleMapController controller) {
+    mapController = controller;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Column(
+      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+      crossAxisAlignment: CrossAxisAlignment.stretch,
+      children: <Widget>[
+        Center(
+          child: SizedBox(
+            width: 300.0,
+            height: 200.0,
+            child: ExampleGoogleMap(
+              onMapCreated: _onMapCreated,
+              initialCameraPosition:
+                  const CameraPosition(target: LatLng(0.0, 0.0)),
+            ),
+          ),
+        ),
+        Row(
+          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+          children: <Widget>[
+            Column(
+              children: <Widget>[
+                TextButton(
+                  onPressed: () {
+                    mapController?.animateCamera(
+                      CameraUpdate.newCameraPosition(
+                        const CameraPosition(
+                          bearing: 270.0,
+                          target: LatLng(51.5160895, -0.1294527),
+                          tilt: 30.0,
+                          zoom: 17.0,
+                        ),
+                      ),
+                    );
+                  },
+                  child: const Text('newCameraPosition'),
+                ),
+                TextButton(
+                  onPressed: () {
+                    mapController?.animateCamera(
+                      CameraUpdate.newLatLng(
+                        const LatLng(56.1725505, 10.1850512),
+                      ),
+                    );
+                  },
+                  child: const Text('newLatLng'),
+                ),
+                TextButton(
+                  onPressed: () {
+                    mapController?.animateCamera(
+                      CameraUpdate.newLatLngBounds(
+                        LatLngBounds(
+                          southwest: const LatLng(-38.483935, 113.248673),
+                          northeast: const LatLng(-8.982446, 153.823821),
+                        ),
+                        10.0,
+                      ),
+                    );
+                  },
+                  child: const Text('newLatLngBounds'),
+                ),
+                TextButton(
+                  onPressed: () {
+                    mapController?.animateCamera(
+                      CameraUpdate.newLatLngZoom(
+                        const LatLng(37.4231613, -122.087159),
+                        11.0,
+                      ),
+                    );
+                  },
+                  child: const Text('newLatLngZoom'),
+                ),
+                TextButton(
+                  onPressed: () {
+                    mapController?.animateCamera(
+                      CameraUpdate.scrollBy(150.0, -225.0),
+                    );
+                  },
+                  child: const Text('scrollBy'),
+                ),
+              ],
+            ),
+            Column(
+              children: <Widget>[
+                TextButton(
+                  onPressed: () {
+                    mapController?.animateCamera(
+                      CameraUpdate.zoomBy(
+                        -0.5,
+                        const Offset(30.0, 20.0),
+                      ),
+                    );
+                  },
+                  child: const Text('zoomBy with focus'),
+                ),
+                TextButton(
+                  onPressed: () {
+                    mapController?.animateCamera(
+                      CameraUpdate.zoomBy(-0.5),
+                    );
+                  },
+                  child: const Text('zoomBy'),
+                ),
+                TextButton(
+                  onPressed: () {
+                    mapController?.animateCamera(
+                      CameraUpdate.zoomIn(),
+                    );
+                  },
+                  child: const Text('zoomIn'),
+                ),
+                TextButton(
+                  onPressed: () {
+                    mapController?.animateCamera(
+                      CameraUpdate.zoomOut(),
+                    );
+                  },
+                  child: const Text('zoomOut'),
+                ),
+                TextButton(
+                  onPressed: () {
+                    mapController?.animateCamera(
+                      CameraUpdate.zoomTo(16.0),
+                    );
+                  },
+                  child: const Text('zoomTo'),
+                ),
+              ],
+            ),
+          ],
+        )
+      ],
+    );
+  }
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/lib/example_google_map.dart b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/example_google_map.dart
new file mode 100644
index 0000000..e2c7131
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/example_google_map.dart
@@ -0,0 +1,538 @@
+// 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 'dart:async';
+// TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231)
+// ignore: unnecessary_import
+import 'dart:typed_data';
+
+import 'package:flutter/foundation.dart';
+import 'package:flutter/gestures.dart';
+import 'package:flutter/material.dart';
+import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
+
+// This is a pared down version of the Dart code from the app-facing package,
+// to allow running the same examples for package-local testing.
+// TODO(stuartmorgan): Consider extracting this to a shared package. See also
+// https://github.com/flutter/flutter/issues/46716.
+
+/// Controller for a single ExampleGoogleMap instance running on the host platform.
+class ExampleGoogleMapController {
+  ExampleGoogleMapController._(
+    this._googleMapState, {
+    required this.mapId,
+  }) {
+    _connectStreams(mapId);
+  }
+
+  /// The mapId for this controller
+  final int mapId;
+
+  /// Initialize control of a [ExampleGoogleMap] with [id].
+  ///
+  /// Mainly for internal use when instantiating a [ExampleGoogleMapController] passed
+  /// in [ExampleGoogleMap.onMapCreated] callback.
+  static Future<ExampleGoogleMapController> _init(
+    int id,
+    CameraPosition initialCameraPosition,
+    _ExampleGoogleMapState googleMapState,
+  ) async {
+    await GoogleMapsFlutterPlatform.instance.init(id);
+    return ExampleGoogleMapController._(
+      googleMapState,
+      mapId: id,
+    );
+  }
+
+  final _ExampleGoogleMapState _googleMapState;
+
+  void _connectStreams(int mapId) {
+    if (_googleMapState.widget.onCameraMoveStarted != null) {
+      GoogleMapsFlutterPlatform.instance
+          .onCameraMoveStarted(mapId: mapId)
+          .listen((_) => _googleMapState.widget.onCameraMoveStarted!());
+    }
+    if (_googleMapState.widget.onCameraMove != null) {
+      GoogleMapsFlutterPlatform.instance.onCameraMove(mapId: mapId).listen(
+          (CameraMoveEvent e) => _googleMapState.widget.onCameraMove!(e.value));
+    }
+    if (_googleMapState.widget.onCameraIdle != null) {
+      GoogleMapsFlutterPlatform.instance
+          .onCameraIdle(mapId: mapId)
+          .listen((_) => _googleMapState.widget.onCameraIdle!());
+    }
+    GoogleMapsFlutterPlatform.instance
+        .onMarkerTap(mapId: mapId)
+        .listen((MarkerTapEvent e) => _googleMapState.onMarkerTap(e.value));
+    GoogleMapsFlutterPlatform.instance.onMarkerDragStart(mapId: mapId).listen(
+        (MarkerDragStartEvent e) =>
+            _googleMapState.onMarkerDragStart(e.value, e.position));
+    GoogleMapsFlutterPlatform.instance.onMarkerDrag(mapId: mapId).listen(
+        (MarkerDragEvent e) =>
+            _googleMapState.onMarkerDrag(e.value, e.position));
+    GoogleMapsFlutterPlatform.instance.onMarkerDragEnd(mapId: mapId).listen(
+        (MarkerDragEndEvent e) =>
+            _googleMapState.onMarkerDragEnd(e.value, e.position));
+    GoogleMapsFlutterPlatform.instance.onInfoWindowTap(mapId: mapId).listen(
+        (InfoWindowTapEvent e) => _googleMapState.onInfoWindowTap(e.value));
+    GoogleMapsFlutterPlatform.instance
+        .onPolylineTap(mapId: mapId)
+        .listen((PolylineTapEvent e) => _googleMapState.onPolylineTap(e.value));
+    GoogleMapsFlutterPlatform.instance
+        .onPolygonTap(mapId: mapId)
+        .listen((PolygonTapEvent e) => _googleMapState.onPolygonTap(e.value));
+    GoogleMapsFlutterPlatform.instance
+        .onCircleTap(mapId: mapId)
+        .listen((CircleTapEvent e) => _googleMapState.onCircleTap(e.value));
+    GoogleMapsFlutterPlatform.instance
+        .onTap(mapId: mapId)
+        .listen((MapTapEvent e) => _googleMapState.onTap(e.position));
+    GoogleMapsFlutterPlatform.instance.onLongPress(mapId: mapId).listen(
+        (MapLongPressEvent e) => _googleMapState.onLongPress(e.position));
+  }
+
+  /// Updates configuration options of the map user interface.
+  Future<void> _updateMapConfiguration(MapConfiguration update) {
+    return GoogleMapsFlutterPlatform.instance
+        .updateMapConfiguration(update, mapId: mapId);
+  }
+
+  /// Updates marker configuration.
+  Future<void> _updateMarkers(MarkerUpdates markerUpdates) {
+    return GoogleMapsFlutterPlatform.instance
+        .updateMarkers(markerUpdates, mapId: mapId);
+  }
+
+  /// Updates polygon configuration.
+  Future<void> _updatePolygons(PolygonUpdates polygonUpdates) {
+    return GoogleMapsFlutterPlatform.instance
+        .updatePolygons(polygonUpdates, mapId: mapId);
+  }
+
+  /// Updates polyline configuration.
+  Future<void> _updatePolylines(PolylineUpdates polylineUpdates) {
+    return GoogleMapsFlutterPlatform.instance
+        .updatePolylines(polylineUpdates, mapId: mapId);
+  }
+
+  /// Updates circle configuration.
+  Future<void> _updateCircles(CircleUpdates circleUpdates) {
+    return GoogleMapsFlutterPlatform.instance
+        .updateCircles(circleUpdates, mapId: mapId);
+  }
+
+  /// Updates tile overlays configuration.
+  Future<void> _updateTileOverlays(Set<TileOverlay> newTileOverlays) {
+    return GoogleMapsFlutterPlatform.instance
+        .updateTileOverlays(newTileOverlays: newTileOverlays, mapId: mapId);
+  }
+
+  /// Clears the tile cache so that all tiles will be requested again from the
+  /// [TileProvider].
+  Future<void> clearTileCache(TileOverlayId tileOverlayId) async {
+    return GoogleMapsFlutterPlatform.instance
+        .clearTileCache(tileOverlayId, mapId: mapId);
+  }
+
+  /// Starts an animated change of the map camera position.
+  Future<void> animateCamera(CameraUpdate cameraUpdate) {
+    return GoogleMapsFlutterPlatform.instance
+        .animateCamera(cameraUpdate, mapId: mapId);
+  }
+
+  /// Changes the map camera position.
+  Future<void> moveCamera(CameraUpdate cameraUpdate) {
+    return GoogleMapsFlutterPlatform.instance
+        .moveCamera(cameraUpdate, mapId: mapId);
+  }
+
+  /// Sets the styling of the base map.
+  Future<void> setMapStyle(String? mapStyle) {
+    return GoogleMapsFlutterPlatform.instance
+        .setMapStyle(mapStyle, mapId: mapId);
+  }
+
+  /// Return [LatLngBounds] defining the region that is visible in a map.
+  Future<LatLngBounds> getVisibleRegion() {
+    return GoogleMapsFlutterPlatform.instance.getVisibleRegion(mapId: mapId);
+  }
+
+  /// Return [ScreenCoordinate] of the [LatLng] in the current map view.
+  Future<ScreenCoordinate> getScreenCoordinate(LatLng latLng) {
+    return GoogleMapsFlutterPlatform.instance
+        .getScreenCoordinate(latLng, mapId: mapId);
+  }
+
+  /// Returns [LatLng] corresponding to the [ScreenCoordinate] in the current map view.
+  Future<LatLng> getLatLng(ScreenCoordinate screenCoordinate) {
+    return GoogleMapsFlutterPlatform.instance
+        .getLatLng(screenCoordinate, mapId: mapId);
+  }
+
+  /// Programmatically show the Info Window for a [Marker].
+  Future<void> showMarkerInfoWindow(MarkerId markerId) {
+    return GoogleMapsFlutterPlatform.instance
+        .showMarkerInfoWindow(markerId, mapId: mapId);
+  }
+
+  /// Programmatically hide the Info Window for a [Marker].
+  Future<void> hideMarkerInfoWindow(MarkerId markerId) {
+    return GoogleMapsFlutterPlatform.instance
+        .hideMarkerInfoWindow(markerId, mapId: mapId);
+  }
+
+  /// Returns `true` when the [InfoWindow] is showing, `false` otherwise.
+  Future<bool> isMarkerInfoWindowShown(MarkerId markerId) {
+    return GoogleMapsFlutterPlatform.instance
+        .isMarkerInfoWindowShown(markerId, mapId: mapId);
+  }
+
+  /// Returns the current zoom level of the map
+  Future<double> getZoomLevel() {
+    return GoogleMapsFlutterPlatform.instance.getZoomLevel(mapId: mapId);
+  }
+
+  /// Returns the image bytes of the map
+  Future<Uint8List?> takeSnapshot() {
+    return GoogleMapsFlutterPlatform.instance.takeSnapshot(mapId: mapId);
+  }
+
+  /// Disposes of the platform resources
+  void dispose() {
+    GoogleMapsFlutterPlatform.instance.dispose(mapId: mapId);
+  }
+}
+
+// The next map ID to create.
+int _nextMapCreationId = 0;
+
+/// A widget which displays a map with data obtained from the Google Maps service.
+class ExampleGoogleMap extends StatefulWidget {
+  /// Creates a widget displaying data from Google Maps services.
+  ///
+  /// [AssertionError] will be thrown if [initialCameraPosition] is null;
+  const ExampleGoogleMap({
+    Key? key,
+    required this.initialCameraPosition,
+    this.onMapCreated,
+    this.gestureRecognizers = const <Factory<OneSequenceGestureRecognizer>>{},
+    this.compassEnabled = true,
+    this.mapToolbarEnabled = true,
+    this.cameraTargetBounds = CameraTargetBounds.unbounded,
+    this.mapType = MapType.normal,
+    this.minMaxZoomPreference = MinMaxZoomPreference.unbounded,
+    this.rotateGesturesEnabled = true,
+    this.scrollGesturesEnabled = true,
+    this.zoomControlsEnabled = true,
+    this.zoomGesturesEnabled = true,
+    this.liteModeEnabled = false,
+    this.tiltGesturesEnabled = true,
+    this.myLocationEnabled = false,
+    this.myLocationButtonEnabled = true,
+    this.layoutDirection,
+
+    /// If no padding is specified default padding will be 0.
+    this.padding = const EdgeInsets.all(0),
+    this.indoorViewEnabled = false,
+    this.trafficEnabled = false,
+    this.buildingsEnabled = true,
+    this.markers = const <Marker>{},
+    this.polygons = const <Polygon>{},
+    this.polylines = const <Polyline>{},
+    this.circles = const <Circle>{},
+    this.onCameraMoveStarted,
+    this.tileOverlays = const <TileOverlay>{},
+    this.onCameraMove,
+    this.onCameraIdle,
+    this.onTap,
+    this.onLongPress,
+  }) : super(key: key);
+
+  /// Callback method for when the map is ready to be used.
+  ///
+  /// Used to receive a [ExampleGoogleMapController] for this [ExampleGoogleMap].
+  final void Function(ExampleGoogleMapController controller)? onMapCreated;
+
+  /// The initial position of the map's camera.
+  final CameraPosition initialCameraPosition;
+
+  /// True if the map should show a compass when rotated.
+  final bool compassEnabled;
+
+  /// True if the map should show a toolbar when you interact with the map. Android only.
+  final bool mapToolbarEnabled;
+
+  /// Geographical bounding box for the camera target.
+  final CameraTargetBounds cameraTargetBounds;
+
+  /// Type of map tiles to be rendered.
+  final MapType mapType;
+
+  /// The layout direction to use for the embedded view.
+  final TextDirection? layoutDirection;
+
+  /// Preferred bounds for the camera zoom level.
+  ///
+  /// Actual bounds depend on map data and device.
+  final MinMaxZoomPreference minMaxZoomPreference;
+
+  /// True if the map view should respond to rotate gestures.
+  final bool rotateGesturesEnabled;
+
+  /// True if the map view should respond to scroll gestures.
+  final bool scrollGesturesEnabled;
+
+  /// True if the map view should show zoom controls. This includes two buttons
+  /// to zoom in and zoom out. The default value is to show zoom controls.
+  final bool zoomControlsEnabled;
+
+  /// True if the map view should respond to zoom gestures.
+  final bool zoomGesturesEnabled;
+
+  /// True if the map view should be in lite mode. Android only.
+  final bool liteModeEnabled;
+
+  /// True if the map view should respond to tilt gestures.
+  final bool tiltGesturesEnabled;
+
+  /// Padding to be set on map.
+  final EdgeInsets padding;
+
+  /// Markers to be placed on the map.
+  final Set<Marker> markers;
+
+  /// Polygons to be placed on the map.
+  final Set<Polygon> polygons;
+
+  /// Polylines to be placed on the map.
+  final Set<Polyline> polylines;
+
+  /// Circles to be placed on the map.
+  final Set<Circle> circles;
+
+  /// Tile overlays to be placed on the map.
+  final Set<TileOverlay> tileOverlays;
+
+  /// Called when the camera starts moving.
+  final VoidCallback? onCameraMoveStarted;
+
+  /// Called repeatedly as the camera continues to move after an
+  /// onCameraMoveStarted call.
+  final CameraPositionCallback? onCameraMove;
+
+  /// Called when camera movement has ended, there are no pending
+  /// animations and the user has stopped interacting with the map.
+  final VoidCallback? onCameraIdle;
+
+  /// Called every time a [ExampleGoogleMap] is tapped.
+  final ArgumentCallback<LatLng>? onTap;
+
+  /// Called every time a [ExampleGoogleMap] is long pressed.
+  final ArgumentCallback<LatLng>? onLongPress;
+
+  /// True if a "My Location" layer should be shown on the map.
+  final bool myLocationEnabled;
+
+  /// Enables or disables the my-location button.
+  final bool myLocationButtonEnabled;
+
+  /// Enables or disables the indoor view from the map
+  final bool indoorViewEnabled;
+
+  /// Enables or disables the traffic layer of the map
+  final bool trafficEnabled;
+
+  /// Enables or disables showing 3D buildings where available
+  final bool buildingsEnabled;
+
+  /// Which gestures should be consumed by the map.
+  final Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers;
+
+  /// Creates a [State] for this [ExampleGoogleMap].
+  @override
+  State createState() => _ExampleGoogleMapState();
+}
+
+class _ExampleGoogleMapState extends State<ExampleGoogleMap> {
+  final int _mapId = _nextMapCreationId++;
+
+  final Completer<ExampleGoogleMapController> _controller =
+      Completer<ExampleGoogleMapController>();
+
+  Map<MarkerId, Marker> _markers = <MarkerId, Marker>{};
+  Map<PolygonId, Polygon> _polygons = <PolygonId, Polygon>{};
+  Map<PolylineId, Polyline> _polylines = <PolylineId, Polyline>{};
+  Map<CircleId, Circle> _circles = <CircleId, Circle>{};
+  late MapConfiguration _mapConfiguration;
+
+  @override
+  Widget build(BuildContext context) {
+    return GoogleMapsFlutterPlatform.instance.buildViewWithConfiguration(
+      _mapId,
+      onPlatformViewCreated,
+      widgetConfiguration: MapWidgetConfiguration(
+        textDirection: widget.layoutDirection ??
+            Directionality.maybeOf(context) ??
+            TextDirection.ltr,
+        initialCameraPosition: widget.initialCameraPosition,
+        gestureRecognizers: widget.gestureRecognizers,
+      ),
+      mapObjects: MapObjects(
+        markers: widget.markers,
+        polygons: widget.polygons,
+        polylines: widget.polylines,
+        circles: widget.circles,
+      ),
+      mapConfiguration: _mapConfiguration,
+    );
+  }
+
+  @override
+  void initState() {
+    super.initState();
+    _mapConfiguration = _configurationFromMapWidget(widget);
+    _markers = keyByMarkerId(widget.markers);
+    _polygons = keyByPolygonId(widget.polygons);
+    _polylines = keyByPolylineId(widget.polylines);
+    _circles = keyByCircleId(widget.circles);
+  }
+
+  @override
+  void dispose() {
+    _controller.future
+        .then((ExampleGoogleMapController controller) => controller.dispose());
+    super.dispose();
+  }
+
+  @override
+  void didUpdateWidget(ExampleGoogleMap oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    _updateOptions();
+    _updateMarkers();
+    _updatePolygons();
+    _updatePolylines();
+    _updateCircles();
+    _updateTileOverlays();
+  }
+
+  Future<void> _updateOptions() async {
+    final MapConfiguration newConfig = _configurationFromMapWidget(widget);
+    final MapConfiguration updates = newConfig.diffFrom(_mapConfiguration);
+    if (updates.isEmpty) {
+      return;
+    }
+    final ExampleGoogleMapController controller = await _controller.future;
+    controller._updateMapConfiguration(updates);
+    _mapConfiguration = newConfig;
+  }
+
+  Future<void> _updateMarkers() async {
+    final ExampleGoogleMapController controller = await _controller.future;
+    controller._updateMarkers(
+        MarkerUpdates.from(_markers.values.toSet(), widget.markers));
+    _markers = keyByMarkerId(widget.markers);
+  }
+
+  Future<void> _updatePolygons() async {
+    final ExampleGoogleMapController controller = await _controller.future;
+    controller._updatePolygons(
+        PolygonUpdates.from(_polygons.values.toSet(), widget.polygons));
+    _polygons = keyByPolygonId(widget.polygons);
+  }
+
+  Future<void> _updatePolylines() async {
+    final ExampleGoogleMapController controller = await _controller.future;
+    controller._updatePolylines(
+        PolylineUpdates.from(_polylines.values.toSet(), widget.polylines));
+    _polylines = keyByPolylineId(widget.polylines);
+  }
+
+  Future<void> _updateCircles() async {
+    final ExampleGoogleMapController controller = await _controller.future;
+    controller._updateCircles(
+        CircleUpdates.from(_circles.values.toSet(), widget.circles));
+    _circles = keyByCircleId(widget.circles);
+  }
+
+  Future<void> _updateTileOverlays() async {
+    final ExampleGoogleMapController controller = await _controller.future;
+    controller._updateTileOverlays(widget.tileOverlays);
+  }
+
+  Future<void> onPlatformViewCreated(int id) async {
+    final ExampleGoogleMapController controller =
+        await ExampleGoogleMapController._init(
+      id,
+      widget.initialCameraPosition,
+      this,
+    );
+    _controller.complete(controller);
+    _updateTileOverlays();
+    widget.onMapCreated?.call(controller);
+  }
+
+  void onMarkerTap(MarkerId markerId) {
+    _markers[markerId]!.onTap?.call();
+  }
+
+  void onMarkerDragStart(MarkerId markerId, LatLng position) {
+    _markers[markerId]!.onDragStart?.call(position);
+  }
+
+  void onMarkerDrag(MarkerId markerId, LatLng position) {
+    _markers[markerId]!.onDrag?.call(position);
+  }
+
+  void onMarkerDragEnd(MarkerId markerId, LatLng position) {
+    _markers[markerId]!.onDragEnd?.call(position);
+  }
+
+  void onPolygonTap(PolygonId polygonId) {
+    _polygons[polygonId]!.onTap?.call();
+  }
+
+  void onPolylineTap(PolylineId polylineId) {
+    _polylines[polylineId]!.onTap?.call();
+  }
+
+  void onCircleTap(CircleId circleId) {
+    _circles[circleId]!.onTap?.call();
+  }
+
+  void onInfoWindowTap(MarkerId markerId) {
+    _markers[markerId]!.infoWindow.onTap?.call();
+  }
+
+  void onTap(LatLng position) {
+    widget.onTap?.call(position);
+  }
+
+  void onLongPress(LatLng position) {
+    widget.onLongPress?.call(position);
+  }
+}
+
+/// Builds a [MapConfiguration] from the given [map].
+MapConfiguration _configurationFromMapWidget(ExampleGoogleMap map) {
+  return MapConfiguration(
+    compassEnabled: map.compassEnabled,
+    mapToolbarEnabled: map.mapToolbarEnabled,
+    cameraTargetBounds: map.cameraTargetBounds,
+    mapType: map.mapType,
+    minMaxZoomPreference: map.minMaxZoomPreference,
+    rotateGesturesEnabled: map.rotateGesturesEnabled,
+    scrollGesturesEnabled: map.scrollGesturesEnabled,
+    tiltGesturesEnabled: map.tiltGesturesEnabled,
+    trackCameraPosition: map.onCameraMove != null,
+    zoomControlsEnabled: map.zoomControlsEnabled,
+    zoomGesturesEnabled: map.zoomGesturesEnabled,
+    liteModeEnabled: map.liteModeEnabled,
+    myLocationEnabled: map.myLocationEnabled,
+    myLocationButtonEnabled: map.myLocationButtonEnabled,
+    padding: map.padding,
+    indoorViewEnabled: map.indoorViewEnabled,
+    trafficEnabled: map.trafficEnabled,
+    buildingsEnabled: map.buildingsEnabled,
+  );
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/lib/lite_mode.dart b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/lite_mode.dart
new file mode 100644
index 0000000..f7bead9
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/lite_mode.dart
@@ -0,0 +1,47 @@
+// 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.
+
+// ignore_for_file: public_member_api_docs
+
+import 'package:flutter/material.dart';
+import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
+
+import 'example_google_map.dart';
+import 'page.dart';
+
+const CameraPosition _kInitialPosition =
+    CameraPosition(target: LatLng(-33.852, 151.211), zoom: 11.0);
+
+class LiteModePage extends GoogleMapExampleAppPage {
+  const LiteModePage({Key? key})
+      : super(const Icon(Icons.map), 'Lite mode', key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return const _LiteModeBody();
+  }
+}
+
+class _LiteModeBody extends StatelessWidget {
+  const _LiteModeBody();
+
+  @override
+  Widget build(BuildContext context) {
+    return const Card(
+      child: Padding(
+        padding: EdgeInsets.symmetric(vertical: 30.0),
+        child: Center(
+          child: SizedBox(
+            width: 300.0,
+            height: 300.0,
+            child: ExampleGoogleMap(
+              initialCameraPosition: _kInitialPosition,
+              liteModeEnabled: true,
+            ),
+          ),
+        ),
+      ),
+    );
+  }
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/lib/main.dart b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/main.dart
new file mode 100644
index 0000000..6b96e6f
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/main.dart
@@ -0,0 +1,78 @@
+// 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 'package:flutter/material.dart';
+import 'package:google_maps_flutter_android/google_maps_flutter_android.dart';
+import 'package:google_maps_flutter_example/lite_mode.dart';
+import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
+
+import 'animate_camera.dart';
+import 'map_click.dart';
+import 'map_coordinates.dart';
+import 'map_ui.dart';
+import 'marker_icons.dart';
+import 'move_camera.dart';
+import 'padding.dart';
+import 'page.dart';
+import 'place_circle.dart';
+import 'place_marker.dart';
+import 'place_polygon.dart';
+import 'place_polyline.dart';
+import 'scrolling_map.dart';
+import 'snapshot.dart';
+import 'tile_overlay.dart';
+
+final List<GoogleMapExampleAppPage> _allPages = <GoogleMapExampleAppPage>[
+  const MapUiPage(),
+  const MapCoordinatesPage(),
+  const MapClickPage(),
+  const AnimateCameraPage(),
+  const MoveCameraPage(),
+  const PlaceMarkerPage(),
+  const MarkerIconsPage(),
+  const ScrollingMapPage(),
+  const PlacePolylinePage(),
+  const PlacePolygonPage(),
+  const PlaceCirclePage(),
+  const PaddingPage(),
+  const SnapshotPage(),
+  const LiteModePage(),
+  const TileOverlayPage(),
+];
+
+/// MapsDemo is the Main Application.
+class MapsDemo extends StatelessWidget {
+  /// Default Constructor
+  const MapsDemo({Key? key}) : super(key: key);
+
+  void _pushPage(BuildContext context, GoogleMapExampleAppPage page) {
+    Navigator.of(context).push(MaterialPageRoute<void>(
+        builder: (_) => Scaffold(
+              appBar: AppBar(title: Text(page.title)),
+              body: page,
+            )));
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: AppBar(title: const Text('GoogleMaps examples')),
+      body: ListView.builder(
+        itemCount: _allPages.length,
+        itemBuilder: (_, int index) => ListTile(
+          leading: _allPages[index].leading,
+          title: Text(_allPages[index].title),
+          onTap: () => _pushPage(context, _allPages[index]),
+        ),
+      ),
+    );
+  }
+}
+
+void main() {
+  final GoogleMapsFlutterPlatform platform = GoogleMapsFlutterPlatform.instance;
+  // Default to Hybrid Composition for the example.
+  (platform as GoogleMapsFlutterAndroid).useAndroidViewSurface = true;
+  runApp(const MaterialApp(home: MapsDemo()));
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/lib/map_click.dart b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/map_click.dart
new file mode 100644
index 0000000..20718d9
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/map_click.dart
@@ -0,0 +1,106 @@
+// 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.
+
+// ignore_for_file: public_member_api_docs
+
+import 'package:flutter/material.dart';
+import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
+
+import 'example_google_map.dart';
+import 'page.dart';
+
+const CameraPosition _kInitialPosition =
+    CameraPosition(target: LatLng(-33.852, 151.211), zoom: 11.0);
+
+class MapClickPage extends GoogleMapExampleAppPage {
+  const MapClickPage({Key? key})
+      : super(const Icon(Icons.mouse), 'Map click', key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return const _MapClickBody();
+  }
+}
+
+class _MapClickBody extends StatefulWidget {
+  const _MapClickBody();
+
+  @override
+  State<StatefulWidget> createState() => _MapClickBodyState();
+}
+
+class _MapClickBodyState extends State<_MapClickBody> {
+  _MapClickBodyState();
+
+  ExampleGoogleMapController? mapController;
+  LatLng? _lastTap;
+  LatLng? _lastLongPress;
+
+  @override
+  Widget build(BuildContext context) {
+    final ExampleGoogleMap googleMap = ExampleGoogleMap(
+      onMapCreated: onMapCreated,
+      initialCameraPosition: _kInitialPosition,
+      onTap: (LatLng pos) {
+        setState(() {
+          _lastTap = pos;
+        });
+      },
+      onLongPress: (LatLng pos) {
+        setState(() {
+          _lastLongPress = pos;
+        });
+      },
+    );
+
+    final List<Widget> columnChildren = <Widget>[
+      Padding(
+        padding: const EdgeInsets.all(10.0),
+        child: Center(
+          child: SizedBox(
+            width: 300.0,
+            height: 200.0,
+            child: googleMap,
+          ),
+        ),
+      ),
+    ];
+
+    if (mapController != null) {
+      final String lastTap = 'Tap:\n${_lastTap ?? ""}\n';
+      final String lastLongPress = 'Long press:\n${_lastLongPress ?? ""}';
+      columnChildren.add(Center(
+          child: Text(
+        lastTap,
+        textAlign: TextAlign.center,
+      )));
+      columnChildren.add(Center(
+          child: Text(
+        _lastTap != null ? 'Tapped' : '',
+        textAlign: TextAlign.center,
+      )));
+      columnChildren.add(Center(
+          child: Text(
+        lastLongPress,
+        textAlign: TextAlign.center,
+      )));
+      columnChildren.add(Center(
+          child: Text(
+        _lastLongPress != null ? 'Long pressed' : '',
+        textAlign: TextAlign.center,
+      )));
+    }
+    return Column(
+      mainAxisAlignment: MainAxisAlignment.start,
+      crossAxisAlignment: CrossAxisAlignment.stretch,
+      children: columnChildren,
+    );
+  }
+
+  Future<void> onMapCreated(ExampleGoogleMapController controller) async {
+    setState(() {
+      mapController = controller;
+    });
+  }
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/lib/map_coordinates.dart b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/map_coordinates.dart
new file mode 100644
index 0000000..185a97e
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/map_coordinates.dart
@@ -0,0 +1,100 @@
+// 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.
+
+// ignore_for_file: public_member_api_docs
+
+import 'package:flutter/material.dart';
+import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
+
+import 'example_google_map.dart';
+import 'page.dart';
+
+const CameraPosition _kInitialPosition =
+    CameraPosition(target: LatLng(-33.852, 151.211), zoom: 11.0);
+
+class MapCoordinatesPage extends GoogleMapExampleAppPage {
+  const MapCoordinatesPage({Key? key})
+      : super(const Icon(Icons.map), 'Map coordinates', key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return const _MapCoordinatesBody();
+  }
+}
+
+class _MapCoordinatesBody extends StatefulWidget {
+  const _MapCoordinatesBody();
+
+  @override
+  State<StatefulWidget> createState() => _MapCoordinatesBodyState();
+}
+
+class _MapCoordinatesBodyState extends State<_MapCoordinatesBody> {
+  _MapCoordinatesBodyState();
+
+  ExampleGoogleMapController? mapController;
+  LatLngBounds _visibleRegion = LatLngBounds(
+    southwest: const LatLng(0, 0),
+    northeast: const LatLng(0, 0),
+  );
+
+  @override
+  Widget build(BuildContext context) {
+    final ExampleGoogleMap googleMap = ExampleGoogleMap(
+      onMapCreated: onMapCreated,
+      initialCameraPosition: _kInitialPosition,
+      onCameraIdle:
+          _updateVisibleRegion, // https://github.com/flutter/flutter/issues/54758
+    );
+
+    return NotificationListener<ScrollNotification>(
+      onNotification: (ScrollNotification scrollState) {
+        _updateVisibleRegion();
+        return true;
+      },
+      child: ListView(
+        children: <Widget>[
+          Padding(
+            padding: const EdgeInsets.all(10.0),
+            child: Center(
+              child: SizedBox(
+                width: 300.0,
+                height: 200.0,
+                child: googleMap,
+              ),
+            ),
+          ),
+          if (mapController != null)
+            Center(
+              child: Text('VisibleRegion:'
+                  '\nnortheast: ${_visibleRegion.northeast},'
+                  '\nsouthwest: ${_visibleRegion.southwest}'),
+            ),
+          // Add a block at the bottom of this list to allow validation that the visible region of the map
+          // does not change when scrolled under the safe view on iOS.
+          // https://github.com/flutter/flutter/issues/107913
+          Container(
+            width: 300,
+            height: 1000,
+          ),
+        ],
+      ),
+    );
+  }
+
+  Future<void> onMapCreated(ExampleGoogleMapController controller) async {
+    final LatLngBounds visibleRegion = await controller.getVisibleRegion();
+    setState(() {
+      mapController = controller;
+      _visibleRegion = visibleRegion;
+    });
+  }
+
+  Future<void> _updateVisibleRegion() async {
+    final LatLngBounds visibleRegion = await mapController!.getVisibleRegion();
+    setState(() {
+      _visibleRegion = visibleRegion;
+    });
+  }
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/lib/map_ui.dart b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/map_ui.dart
new file mode 100644
index 0000000..6c38e14
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/map_ui.dart
@@ -0,0 +1,358 @@
+// 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.
+
+// ignore_for_file: public_member_api_docs
+
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart' show rootBundle;
+import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
+
+import 'example_google_map.dart';
+import 'page.dart';
+
+final LatLngBounds sydneyBounds = LatLngBounds(
+  southwest: const LatLng(-34.022631, 150.620685),
+  northeast: const LatLng(-33.571835, 151.325952),
+);
+
+class MapUiPage extends GoogleMapExampleAppPage {
+  const MapUiPage({Key? key})
+      : super(const Icon(Icons.map), 'User interface', key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return const MapUiBody();
+  }
+}
+
+class MapUiBody extends StatefulWidget {
+  const MapUiBody({Key? key}) : super(key: key);
+
+  @override
+  State<StatefulWidget> createState() => MapUiBodyState();
+}
+
+class MapUiBodyState extends State<MapUiBody> {
+  MapUiBodyState();
+
+  static const CameraPosition _kInitialPosition = CameraPosition(
+    target: LatLng(-33.852, 151.211),
+    zoom: 11.0,
+  );
+
+  CameraPosition _position = _kInitialPosition;
+  bool _isMapCreated = false;
+  final bool _isMoving = false;
+  bool _compassEnabled = true;
+  bool _mapToolbarEnabled = true;
+  CameraTargetBounds _cameraTargetBounds = CameraTargetBounds.unbounded;
+  MinMaxZoomPreference _minMaxZoomPreference = MinMaxZoomPreference.unbounded;
+  MapType _mapType = MapType.normal;
+  bool _rotateGesturesEnabled = true;
+  bool _scrollGesturesEnabled = true;
+  bool _tiltGesturesEnabled = true;
+  bool _zoomControlsEnabled = false;
+  bool _zoomGesturesEnabled = true;
+  bool _indoorViewEnabled = true;
+  bool _myLocationEnabled = true;
+  bool _myTrafficEnabled = false;
+  bool _myLocationButtonEnabled = true;
+  late ExampleGoogleMapController _controller;
+  bool _nightMode = false;
+
+  @override
+  void initState() {
+    super.initState();
+  }
+
+  @override
+  void dispose() {
+    super.dispose();
+  }
+
+  Widget _compassToggler() {
+    return TextButton(
+      child: Text('${_compassEnabled ? 'disable' : 'enable'} compass'),
+      onPressed: () {
+        setState(() {
+          _compassEnabled = !_compassEnabled;
+        });
+      },
+    );
+  }
+
+  Widget _mapToolbarToggler() {
+    return TextButton(
+      child: Text('${_mapToolbarEnabled ? 'disable' : 'enable'} map toolbar'),
+      onPressed: () {
+        setState(() {
+          _mapToolbarEnabled = !_mapToolbarEnabled;
+        });
+      },
+    );
+  }
+
+  Widget _latLngBoundsToggler() {
+    return TextButton(
+      child: Text(
+        _cameraTargetBounds.bounds == null
+            ? 'bound camera target'
+            : 'release camera target',
+      ),
+      onPressed: () {
+        setState(() {
+          _cameraTargetBounds = _cameraTargetBounds.bounds == null
+              ? CameraTargetBounds(sydneyBounds)
+              : CameraTargetBounds.unbounded;
+        });
+      },
+    );
+  }
+
+  Widget _zoomBoundsToggler() {
+    return TextButton(
+      child: Text(_minMaxZoomPreference.minZoom == null
+          ? 'bound zoom'
+          : 'release zoom'),
+      onPressed: () {
+        setState(() {
+          _minMaxZoomPreference = _minMaxZoomPreference.minZoom == null
+              ? const MinMaxZoomPreference(12.0, 16.0)
+              : MinMaxZoomPreference.unbounded;
+        });
+      },
+    );
+  }
+
+  Widget _mapTypeCycler() {
+    final MapType nextType =
+        MapType.values[(_mapType.index + 1) % MapType.values.length];
+    return TextButton(
+      child: Text('change map type to $nextType'),
+      onPressed: () {
+        setState(() {
+          _mapType = nextType;
+        });
+      },
+    );
+  }
+
+  Widget _rotateToggler() {
+    return TextButton(
+      child: Text('${_rotateGesturesEnabled ? 'disable' : 'enable'} rotate'),
+      onPressed: () {
+        setState(() {
+          _rotateGesturesEnabled = !_rotateGesturesEnabled;
+        });
+      },
+    );
+  }
+
+  Widget _scrollToggler() {
+    return TextButton(
+      child: Text('${_scrollGesturesEnabled ? 'disable' : 'enable'} scroll'),
+      onPressed: () {
+        setState(() {
+          _scrollGesturesEnabled = !_scrollGesturesEnabled;
+        });
+      },
+    );
+  }
+
+  Widget _tiltToggler() {
+    return TextButton(
+      child: Text('${_tiltGesturesEnabled ? 'disable' : 'enable'} tilt'),
+      onPressed: () {
+        setState(() {
+          _tiltGesturesEnabled = !_tiltGesturesEnabled;
+        });
+      },
+    );
+  }
+
+  Widget _zoomToggler() {
+    return TextButton(
+      child: Text('${_zoomGesturesEnabled ? 'disable' : 'enable'} zoom'),
+      onPressed: () {
+        setState(() {
+          _zoomGesturesEnabled = !_zoomGesturesEnabled;
+        });
+      },
+    );
+  }
+
+  Widget _zoomControlsToggler() {
+    return TextButton(
+      child:
+          Text('${_zoomControlsEnabled ? 'disable' : 'enable'} zoom controls'),
+      onPressed: () {
+        setState(() {
+          _zoomControlsEnabled = !_zoomControlsEnabled;
+        });
+      },
+    );
+  }
+
+  Widget _indoorViewToggler() {
+    return TextButton(
+      child: Text('${_indoorViewEnabled ? 'disable' : 'enable'} indoor'),
+      onPressed: () {
+        setState(() {
+          _indoorViewEnabled = !_indoorViewEnabled;
+        });
+      },
+    );
+  }
+
+  Widget _myLocationToggler() {
+    return TextButton(
+      child: Text(
+          '${_myLocationEnabled ? 'disable' : 'enable'} my location marker'),
+      onPressed: () {
+        setState(() {
+          _myLocationEnabled = !_myLocationEnabled;
+        });
+      },
+    );
+  }
+
+  Widget _myLocationButtonToggler() {
+    return TextButton(
+      child: Text(
+          '${_myLocationButtonEnabled ? 'disable' : 'enable'} my location button'),
+      onPressed: () {
+        setState(() {
+          _myLocationButtonEnabled = !_myLocationButtonEnabled;
+        });
+      },
+    );
+  }
+
+  Widget _myTrafficToggler() {
+    return TextButton(
+      child: Text('${_myTrafficEnabled ? 'disable' : 'enable'} my traffic'),
+      onPressed: () {
+        setState(() {
+          _myTrafficEnabled = !_myTrafficEnabled;
+        });
+      },
+    );
+  }
+
+  Future<String> _getFileData(String path) async {
+    return await rootBundle.loadString(path);
+  }
+
+  void _setMapStyle(String mapStyle) {
+    setState(() {
+      _nightMode = true;
+      _controller.setMapStyle(mapStyle);
+    });
+  }
+
+  // Should only be called if _isMapCreated is true.
+  Widget _nightModeToggler() {
+    assert(_isMapCreated);
+    return TextButton(
+      child: Text('${_nightMode ? 'disable' : 'enable'} night mode'),
+      onPressed: () {
+        if (_nightMode) {
+          setState(() {
+            _nightMode = false;
+            _controller.setMapStyle(null);
+          });
+        } else {
+          _getFileData('assets/night_mode.json').then(_setMapStyle);
+        }
+      },
+    );
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final ExampleGoogleMap googleMap = ExampleGoogleMap(
+      onMapCreated: onMapCreated,
+      initialCameraPosition: _kInitialPosition,
+      compassEnabled: _compassEnabled,
+      mapToolbarEnabled: _mapToolbarEnabled,
+      cameraTargetBounds: _cameraTargetBounds,
+      minMaxZoomPreference: _minMaxZoomPreference,
+      mapType: _mapType,
+      rotateGesturesEnabled: _rotateGesturesEnabled,
+      scrollGesturesEnabled: _scrollGesturesEnabled,
+      tiltGesturesEnabled: _tiltGesturesEnabled,
+      zoomGesturesEnabled: _zoomGesturesEnabled,
+      zoomControlsEnabled: _zoomControlsEnabled,
+      indoorViewEnabled: _indoorViewEnabled,
+      myLocationEnabled: _myLocationEnabled,
+      myLocationButtonEnabled: _myLocationButtonEnabled,
+      trafficEnabled: _myTrafficEnabled,
+      onCameraMove: _updateCameraPosition,
+    );
+
+    final List<Widget> columnChildren = <Widget>[
+      Padding(
+        padding: const EdgeInsets.all(10.0),
+        child: Center(
+          child: SizedBox(
+            width: 300.0,
+            height: 200.0,
+            child: googleMap,
+          ),
+        ),
+      ),
+    ];
+
+    if (_isMapCreated) {
+      columnChildren.add(
+        Expanded(
+          child: ListView(
+            children: <Widget>[
+              Text('camera bearing: ${_position.bearing}'),
+              Text(
+                  'camera target: ${_position.target.latitude.toStringAsFixed(4)},'
+                  '${_position.target.longitude.toStringAsFixed(4)}'),
+              Text('camera zoom: ${_position.zoom}'),
+              Text('camera tilt: ${_position.tilt}'),
+              Text(_isMoving ? '(Camera moving)' : '(Camera idle)'),
+              _compassToggler(),
+              _mapToolbarToggler(),
+              _latLngBoundsToggler(),
+              _mapTypeCycler(),
+              _zoomBoundsToggler(),
+              _rotateToggler(),
+              _scrollToggler(),
+              _tiltToggler(),
+              _zoomToggler(),
+              _zoomControlsToggler(),
+              _indoorViewToggler(),
+              _myLocationToggler(),
+              _myLocationButtonToggler(),
+              _myTrafficToggler(),
+              _nightModeToggler(),
+            ],
+          ),
+        ),
+      );
+    }
+    return Column(
+      mainAxisAlignment: MainAxisAlignment.start,
+      crossAxisAlignment: CrossAxisAlignment.stretch,
+      children: columnChildren,
+    );
+  }
+
+  void _updateCameraPosition(CameraPosition position) {
+    setState(() {
+      _position = position;
+    });
+  }
+
+  void onMapCreated(ExampleGoogleMapController controller) {
+    setState(() {
+      _controller = controller;
+      _isMapCreated = true;
+    });
+  }
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/lib/marker_icons.dart b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/marker_icons.dart
new file mode 100644
index 0000000..fe28eb6
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/marker_icons.dart
@@ -0,0 +1,98 @@
+// 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.
+
+// ignore_for_file: public_member_api_docs
+// ignore_for_file: unawaited_futures
+
+import 'package:flutter/material.dart';
+import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
+
+import 'example_google_map.dart';
+import 'page.dart';
+
+class MarkerIconsPage extends GoogleMapExampleAppPage {
+  const MarkerIconsPage({Key? key})
+      : super(const Icon(Icons.image), 'Marker icons', key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return const MarkerIconsBody();
+  }
+}
+
+class MarkerIconsBody extends StatefulWidget {
+  const MarkerIconsBody({Key? key}) : super(key: key);
+
+  @override
+  State<StatefulWidget> createState() => MarkerIconsBodyState();
+}
+
+const LatLng _kMapCenter = LatLng(52.4478, -3.5402);
+
+class MarkerIconsBodyState extends State<MarkerIconsBody> {
+  ExampleGoogleMapController? controller;
+  BitmapDescriptor? _markerIcon;
+
+  @override
+  Widget build(BuildContext context) {
+    _createMarkerImageFromAsset(context);
+    return Column(
+      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+      crossAxisAlignment: CrossAxisAlignment.stretch,
+      children: <Widget>[
+        Center(
+          child: SizedBox(
+            width: 350.0,
+            height: 300.0,
+            child: ExampleGoogleMap(
+              initialCameraPosition: const CameraPosition(
+                target: _kMapCenter,
+                zoom: 7.0,
+              ),
+              markers: <Marker>{_createMarker()},
+              onMapCreated: _onMapCreated,
+            ),
+          ),
+        )
+      ],
+    );
+  }
+
+  Marker _createMarker() {
+    if (_markerIcon != null) {
+      return Marker(
+        markerId: const MarkerId('marker_1'),
+        position: _kMapCenter,
+        icon: _markerIcon!,
+      );
+    } else {
+      return const Marker(
+        markerId: MarkerId('marker_1'),
+        position: _kMapCenter,
+      );
+    }
+  }
+
+  Future<void> _createMarkerImageFromAsset(BuildContext context) async {
+    if (_markerIcon == null) {
+      final ImageConfiguration imageConfiguration =
+          createLocalImageConfiguration(context, size: const Size.square(48));
+      BitmapDescriptor.fromAssetImage(
+              imageConfiguration, 'assets/red_square.png')
+          .then(_updateBitmap);
+    }
+  }
+
+  void _updateBitmap(BitmapDescriptor bitmap) {
+    setState(() {
+      _markerIcon = bitmap;
+    });
+  }
+
+  void _onMapCreated(ExampleGoogleMapController controllerParam) {
+    setState(() {
+      controller = controllerParam;
+    });
+  }
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/lib/move_camera.dart b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/move_camera.dart
new file mode 100644
index 0000000..7f44d89
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/move_camera.dart
@@ -0,0 +1,171 @@
+// 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.
+
+// ignore_for_file: public_member_api_docs
+
+import 'package:flutter/material.dart';
+import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
+
+import 'example_google_map.dart';
+import 'page.dart';
+
+class MoveCameraPage extends GoogleMapExampleAppPage {
+  const MoveCameraPage({Key? key})
+      : super(const Icon(Icons.map), 'Camera control', key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return const MoveCamera();
+  }
+}
+
+class MoveCamera extends StatefulWidget {
+  const MoveCamera({Key? key}) : super(key: key);
+  @override
+  State createState() => MoveCameraState();
+}
+
+class MoveCameraState extends State<MoveCamera> {
+  ExampleGoogleMapController? mapController;
+
+  // ignore: use_setters_to_change_properties
+  void _onMapCreated(ExampleGoogleMapController controller) {
+    mapController = controller;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Column(
+      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+      crossAxisAlignment: CrossAxisAlignment.stretch,
+      children: <Widget>[
+        Center(
+          child: SizedBox(
+            width: 300.0,
+            height: 200.0,
+            child: ExampleGoogleMap(
+              onMapCreated: _onMapCreated,
+              initialCameraPosition:
+                  const CameraPosition(target: LatLng(0.0, 0.0)),
+            ),
+          ),
+        ),
+        Row(
+          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+          children: <Widget>[
+            Column(
+              children: <Widget>[
+                TextButton(
+                  onPressed: () {
+                    mapController?.moveCamera(
+                      CameraUpdate.newCameraPosition(
+                        const CameraPosition(
+                          bearing: 270.0,
+                          target: LatLng(51.5160895, -0.1294527),
+                          tilt: 30.0,
+                          zoom: 17.0,
+                        ),
+                      ),
+                    );
+                  },
+                  child: const Text('newCameraPosition'),
+                ),
+                TextButton(
+                  onPressed: () {
+                    mapController?.moveCamera(
+                      CameraUpdate.newLatLng(
+                        const LatLng(56.1725505, 10.1850512),
+                      ),
+                    );
+                  },
+                  child: const Text('newLatLng'),
+                ),
+                TextButton(
+                  onPressed: () {
+                    mapController?.moveCamera(
+                      CameraUpdate.newLatLngBounds(
+                        LatLngBounds(
+                          southwest: const LatLng(-38.483935, 113.248673),
+                          northeast: const LatLng(-8.982446, 153.823821),
+                        ),
+                        10.0,
+                      ),
+                    );
+                  },
+                  child: const Text('newLatLngBounds'),
+                ),
+                TextButton(
+                  onPressed: () {
+                    mapController?.moveCamera(
+                      CameraUpdate.newLatLngZoom(
+                        const LatLng(37.4231613, -122.087159),
+                        11.0,
+                      ),
+                    );
+                  },
+                  child: const Text('newLatLngZoom'),
+                ),
+                TextButton(
+                  onPressed: () {
+                    mapController?.moveCamera(
+                      CameraUpdate.scrollBy(150.0, -225.0),
+                    );
+                  },
+                  child: const Text('scrollBy'),
+                ),
+              ],
+            ),
+            Column(
+              children: <Widget>[
+                TextButton(
+                  onPressed: () {
+                    mapController?.moveCamera(
+                      CameraUpdate.zoomBy(
+                        -0.5,
+                        const Offset(30.0, 20.0),
+                      ),
+                    );
+                  },
+                  child: const Text('zoomBy with focus'),
+                ),
+                TextButton(
+                  onPressed: () {
+                    mapController?.moveCamera(
+                      CameraUpdate.zoomBy(-0.5),
+                    );
+                  },
+                  child: const Text('zoomBy'),
+                ),
+                TextButton(
+                  onPressed: () {
+                    mapController?.moveCamera(
+                      CameraUpdate.zoomIn(),
+                    );
+                  },
+                  child: const Text('zoomIn'),
+                ),
+                TextButton(
+                  onPressed: () {
+                    mapController?.moveCamera(
+                      CameraUpdate.zoomOut(),
+                    );
+                  },
+                  child: const Text('zoomOut'),
+                ),
+                TextButton(
+                  onPressed: () {
+                    mapController?.moveCamera(
+                      CameraUpdate.zoomTo(16.0),
+                    );
+                  },
+                  child: const Text('zoomTo'),
+                ),
+              ],
+            ),
+          ],
+        )
+      ],
+    );
+  }
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/lib/padding.dart b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/padding.dart
new file mode 100644
index 0000000..d346549
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/padding.dart
@@ -0,0 +1,181 @@
+// 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.
+
+// ignore_for_file: public_member_api_docs
+
+import 'package:flutter/material.dart';
+import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
+
+import 'example_google_map.dart';
+import 'page.dart';
+
+class PaddingPage extends GoogleMapExampleAppPage {
+  const PaddingPage({Key? key})
+      : super(const Icon(Icons.map), 'Add padding to the map', key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return const MarkerIconsBody();
+  }
+}
+
+class MarkerIconsBody extends StatefulWidget {
+  const MarkerIconsBody({Key? key}) : super(key: key);
+
+  @override
+  State<StatefulWidget> createState() => MarkerIconsBodyState();
+}
+
+const LatLng _kMapCenter = LatLng(52.4478, -3.5402);
+
+class MarkerIconsBodyState extends State<MarkerIconsBody> {
+  ExampleGoogleMapController? controller;
+
+  EdgeInsets _padding = const EdgeInsets.all(0);
+
+  @override
+  Widget build(BuildContext context) {
+    final ExampleGoogleMap googleMap = ExampleGoogleMap(
+      onMapCreated: _onMapCreated,
+      initialCameraPosition: const CameraPosition(
+        target: _kMapCenter,
+        zoom: 7.0,
+      ),
+      padding: _padding,
+    );
+
+    final List<Widget> columnChildren = <Widget>[
+      Padding(
+        padding: const EdgeInsets.all(10.0),
+        child: Center(
+          child: SizedBox(
+            width: 300.0,
+            height: 200.0,
+            child: googleMap,
+          ),
+        ),
+      ),
+      const Padding(
+        padding: EdgeInsets.only(top: 20),
+        child: Center(
+          child: Text(
+            'Enter Padding Below',
+            style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
+          ),
+        ),
+      ),
+    ];
+
+    columnChildren.addAll(<Widget>[_paddingInput(), _buttons()]);
+
+    return Column(
+      mainAxisAlignment: MainAxisAlignment.start,
+      crossAxisAlignment: CrossAxisAlignment.stretch,
+      children: columnChildren,
+    );
+  }
+
+  void _onMapCreated(ExampleGoogleMapController controllerParam) {
+    setState(() {
+      controller = controllerParam;
+    });
+  }
+
+  final TextEditingController _topController = TextEditingController();
+  final TextEditingController _bottomController = TextEditingController();
+  final TextEditingController _leftController = TextEditingController();
+  final TextEditingController _rightController = TextEditingController();
+
+  Widget _paddingInput() {
+    return Padding(
+      padding: const EdgeInsets.all(16.0),
+      child: Row(
+        children: <Widget>[
+          Flexible(
+            flex: 2,
+            child: TextField(
+              controller: _topController,
+              keyboardType: TextInputType.number,
+              textAlign: TextAlign.center,
+              decoration: const InputDecoration(
+                hintText: 'Top',
+              ),
+            ),
+          ),
+          const Spacer(),
+          Flexible(
+            flex: 2,
+            child: TextField(
+              controller: _bottomController,
+              keyboardType: TextInputType.number,
+              textAlign: TextAlign.center,
+              decoration: const InputDecoration(
+                hintText: 'Bottom',
+              ),
+            ),
+          ),
+          const Spacer(),
+          Flexible(
+            flex: 2,
+            child: TextField(
+              controller: _leftController,
+              keyboardType: TextInputType.number,
+              textAlign: TextAlign.center,
+              decoration: const InputDecoration(
+                hintText: 'Left',
+              ),
+            ),
+          ),
+          const Spacer(),
+          Flexible(
+            flex: 2,
+            child: TextField(
+              controller: _rightController,
+              keyboardType: TextInputType.number,
+              textAlign: TextAlign.center,
+              decoration: const InputDecoration(
+                hintText: 'Right',
+              ),
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+
+  Widget _buttons() {
+    return Padding(
+      padding: const EdgeInsets.symmetric(horizontal: 16),
+      child: Row(
+        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+        children: <Widget>[
+          TextButton(
+            child: const Text('Set Padding'),
+            onPressed: () {
+              setState(() {
+                _padding = EdgeInsets.fromLTRB(
+                    double.tryParse(_leftController.value.text) ?? 0,
+                    double.tryParse(_topController.value.text) ?? 0,
+                    double.tryParse(_rightController.value.text) ?? 0,
+                    double.tryParse(_bottomController.value.text) ?? 0);
+              });
+            },
+          ),
+          TextButton(
+            child: const Text('Reset Padding'),
+            onPressed: () {
+              setState(() {
+                _topController.clear();
+                _bottomController.clear();
+                _leftController.clear();
+                _rightController.clear();
+                _padding = const EdgeInsets.all(0);
+              });
+            },
+          )
+        ],
+      ),
+    );
+  }
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/lib/page.dart b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/page.dart
new file mode 100644
index 0000000..eb01ab0
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/page.dart
@@ -0,0 +1,15 @@
+// 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.
+
+// ignore_for_file: public_member_api_docs
+
+import 'package:flutter/material.dart';
+
+abstract class GoogleMapExampleAppPage extends StatelessWidget {
+  const GoogleMapExampleAppPage(this.leading, this.title, {Key? key})
+      : super(key: key);
+
+  final Widget leading;
+  final String title;
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/lib/place_circle.dart b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/place_circle.dart
new file mode 100644
index 0000000..9dc5760
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/place_circle.dart
@@ -0,0 +1,232 @@
+// 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.
+
+// ignore_for_file: public_member_api_docs
+
+import 'package:flutter/material.dart';
+import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
+
+import 'example_google_map.dart';
+import 'page.dart';
+
+class PlaceCirclePage extends GoogleMapExampleAppPage {
+  const PlaceCirclePage({Key? key})
+      : super(const Icon(Icons.linear_scale), 'Place circle', key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return const PlaceCircleBody();
+  }
+}
+
+class PlaceCircleBody extends StatefulWidget {
+  const PlaceCircleBody({Key? key}) : super(key: key);
+
+  @override
+  State<StatefulWidget> createState() => PlaceCircleBodyState();
+}
+
+class PlaceCircleBodyState extends State<PlaceCircleBody> {
+  PlaceCircleBodyState();
+
+  ExampleGoogleMapController? controller;
+  Map<CircleId, Circle> circles = <CircleId, Circle>{};
+  int _circleIdCounter = 1;
+  CircleId? selectedCircle;
+
+  // Values when toggling circle color
+  int fillColorsIndex = 0;
+  int strokeColorsIndex = 0;
+  List<Color> colors = <Color>[
+    Colors.purple,
+    Colors.red,
+    Colors.green,
+    Colors.pink,
+  ];
+
+  // Values when toggling circle stroke width
+  int widthsIndex = 0;
+  List<int> widths = <int>[10, 20, 5];
+
+  // ignore: use_setters_to_change_properties
+  void _onMapCreated(ExampleGoogleMapController controller) {
+    this.controller = controller;
+  }
+
+  @override
+  void dispose() {
+    super.dispose();
+  }
+
+  void _onCircleTapped(CircleId circleId) {
+    setState(() {
+      selectedCircle = circleId;
+    });
+  }
+
+  void _remove(CircleId circleId) {
+    setState(() {
+      if (circles.containsKey(circleId)) {
+        circles.remove(circleId);
+      }
+      if (circleId == selectedCircle) {
+        selectedCircle = null;
+      }
+    });
+  }
+
+  void _add() {
+    final int circleCount = circles.length;
+
+    if (circleCount == 12) {
+      return;
+    }
+
+    final String circleIdVal = 'circle_id_$_circleIdCounter';
+    _circleIdCounter++;
+    final CircleId circleId = CircleId(circleIdVal);
+
+    final Circle circle = Circle(
+      circleId: circleId,
+      consumeTapEvents: true,
+      strokeColor: Colors.orange,
+      fillColor: Colors.green,
+      strokeWidth: 5,
+      center: _createCenter(),
+      radius: 50000,
+      onTap: () {
+        _onCircleTapped(circleId);
+      },
+    );
+
+    setState(() {
+      circles[circleId] = circle;
+    });
+  }
+
+  void _toggleVisible(CircleId circleId) {
+    final Circle circle = circles[circleId]!;
+    setState(() {
+      circles[circleId] = circle.copyWith(
+        visibleParam: !circle.visible,
+      );
+    });
+  }
+
+  void _changeFillColor(CircleId circleId) {
+    final Circle circle = circles[circleId]!;
+    setState(() {
+      circles[circleId] = circle.copyWith(
+        fillColorParam: colors[++fillColorsIndex % colors.length],
+      );
+    });
+  }
+
+  void _changeStrokeColor(CircleId circleId) {
+    final Circle circle = circles[circleId]!;
+    setState(() {
+      circles[circleId] = circle.copyWith(
+        strokeColorParam: colors[++strokeColorsIndex % colors.length],
+      );
+    });
+  }
+
+  void _changeStrokeWidth(CircleId circleId) {
+    final Circle circle = circles[circleId]!;
+    setState(() {
+      circles[circleId] = circle.copyWith(
+        strokeWidthParam: widths[++widthsIndex % widths.length],
+      );
+    });
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final CircleId? selectedId = selectedCircle;
+    return Column(
+      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+      crossAxisAlignment: CrossAxisAlignment.stretch,
+      children: <Widget>[
+        Center(
+          child: SizedBox(
+            width: 350.0,
+            height: 300.0,
+            child: ExampleGoogleMap(
+              initialCameraPosition: const CameraPosition(
+                target: LatLng(52.4478, -3.5402),
+                zoom: 7.0,
+              ),
+              circles: Set<Circle>.of(circles.values),
+              onMapCreated: _onMapCreated,
+            ),
+          ),
+        ),
+        Expanded(
+          child: SingleChildScrollView(
+            child: Row(
+              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+              children: <Widget>[
+                Row(
+                  children: <Widget>[
+                    Column(
+                      children: <Widget>[
+                        TextButton(
+                          onPressed: _add,
+                          child: const Text('add'),
+                        ),
+                        TextButton(
+                          onPressed: (selectedId == null)
+                              ? null
+                              : () => _remove(selectedId),
+                          child: const Text('remove'),
+                        ),
+                        TextButton(
+                          onPressed: (selectedId == null)
+                              ? null
+                              : () => _toggleVisible(selectedId),
+                          child: const Text('toggle visible'),
+                        ),
+                      ],
+                    ),
+                    Column(
+                      children: <Widget>[
+                        TextButton(
+                          onPressed: (selectedId == null)
+                              ? null
+                              : () => _changeStrokeWidth(selectedId),
+                          child: const Text('change stroke width'),
+                        ),
+                        TextButton(
+                          onPressed: (selectedId == null)
+                              ? null
+                              : () => _changeStrokeColor(selectedId),
+                          child: const Text('change stroke color'),
+                        ),
+                        TextButton(
+                          onPressed: (selectedId == null)
+                              ? null
+                              : () => _changeFillColor(selectedId),
+                          child: const Text('change fill color'),
+                        ),
+                      ],
+                    )
+                  ],
+                )
+              ],
+            ),
+          ),
+        ),
+      ],
+    );
+  }
+
+  LatLng _createCenter() {
+    final double offset = _circleIdCounter.ceilToDouble();
+    return _createLatLng(51.4816 + offset * 0.2, -3.1791);
+  }
+
+  LatLng _createLatLng(double lat, double lng) {
+    return LatLng(lat, lng);
+  }
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/lib/place_marker.dart b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/place_marker.dart
new file mode 100644
index 0000000..b7d2a69
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/place_marker.dart
@@ -0,0 +1,422 @@
+// 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.
+
+// ignore_for_file: public_member_api_docs
+
+import 'dart:async';
+import 'dart:math';
+import 'dart:typed_data';
+import 'dart:ui';
+
+import 'package:flutter/material.dart';
+import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
+
+import 'example_google_map.dart';
+import 'page.dart';
+
+class PlaceMarkerPage extends GoogleMapExampleAppPage {
+  const PlaceMarkerPage({Key? key})
+      : super(const Icon(Icons.place), 'Place marker', key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return const PlaceMarkerBody();
+  }
+}
+
+class PlaceMarkerBody extends StatefulWidget {
+  const PlaceMarkerBody({Key? key}) : super(key: key);
+
+  @override
+  State<StatefulWidget> createState() => PlaceMarkerBodyState();
+}
+
+typedef MarkerUpdateAction = Marker Function(Marker marker);
+
+class PlaceMarkerBodyState extends State<PlaceMarkerBody> {
+  PlaceMarkerBodyState();
+  static const LatLng center = LatLng(-33.86711, 151.1947171);
+
+  ExampleGoogleMapController? controller;
+  Map<MarkerId, Marker> markers = <MarkerId, Marker>{};
+  MarkerId? selectedMarker;
+  int _markerIdCounter = 1;
+  LatLng? markerPosition;
+
+  // ignore: use_setters_to_change_properties
+  void _onMapCreated(ExampleGoogleMapController controller) {
+    this.controller = controller;
+  }
+
+  @override
+  void dispose() {
+    super.dispose();
+  }
+
+  void _onMarkerTapped(MarkerId markerId) {
+    final Marker? tappedMarker = markers[markerId];
+    if (tappedMarker != null) {
+      setState(() {
+        final MarkerId? previousMarkerId = selectedMarker;
+        if (previousMarkerId != null && markers.containsKey(previousMarkerId)) {
+          final Marker resetOld = markers[previousMarkerId]!
+              .copyWith(iconParam: BitmapDescriptor.defaultMarker);
+          markers[previousMarkerId] = resetOld;
+        }
+        selectedMarker = markerId;
+        final Marker newMarker = tappedMarker.copyWith(
+          iconParam: BitmapDescriptor.defaultMarkerWithHue(
+            BitmapDescriptor.hueGreen,
+          ),
+        );
+        markers[markerId] = newMarker;
+
+        markerPosition = null;
+      });
+    }
+  }
+
+  Future<void> _onMarkerDrag(MarkerId markerId, LatLng newPosition) async {
+    setState(() {
+      markerPosition = newPosition;
+    });
+  }
+
+  Future<void> _onMarkerDragEnd(MarkerId markerId, LatLng newPosition) async {
+    final Marker? tappedMarker = markers[markerId];
+    if (tappedMarker != null) {
+      setState(() {
+        markerPosition = null;
+      });
+      await showDialog<void>(
+          context: context,
+          builder: (BuildContext context) {
+            return AlertDialog(
+                actions: <Widget>[
+                  TextButton(
+                    child: const Text('OK'),
+                    onPressed: () => Navigator.of(context).pop(),
+                  )
+                ],
+                content: Padding(
+                    padding: const EdgeInsets.symmetric(vertical: 66),
+                    child: Column(
+                      mainAxisSize: MainAxisSize.min,
+                      children: <Widget>[
+                        Text('Old position: ${tappedMarker.position}'),
+                        Text('New position: $newPosition'),
+                      ],
+                    )));
+          });
+    }
+  }
+
+  void _add() {
+    final int markerCount = markers.length;
+
+    if (markerCount == 12) {
+      return;
+    }
+
+    final String markerIdVal = 'marker_id_$_markerIdCounter';
+    _markerIdCounter++;
+    final MarkerId markerId = MarkerId(markerIdVal);
+
+    final Marker marker = Marker(
+      markerId: markerId,
+      position: LatLng(
+        center.latitude + sin(_markerIdCounter * pi / 6.0) / 20.0,
+        center.longitude + cos(_markerIdCounter * pi / 6.0) / 20.0,
+      ),
+      infoWindow: InfoWindow(title: markerIdVal, snippet: '*'),
+      onTap: () => _onMarkerTapped(markerId),
+      onDragEnd: (LatLng position) => _onMarkerDragEnd(markerId, position),
+      onDrag: (LatLng position) => _onMarkerDrag(markerId, position),
+    );
+
+    setState(() {
+      markers[markerId] = marker;
+    });
+  }
+
+  void _remove(MarkerId markerId) {
+    setState(() {
+      if (markers.containsKey(markerId)) {
+        markers.remove(markerId);
+      }
+    });
+  }
+
+  void _changePosition(MarkerId markerId) {
+    final Marker marker = markers[markerId]!;
+    final LatLng current = marker.position;
+    final Offset offset = Offset(
+      center.latitude - current.latitude,
+      center.longitude - current.longitude,
+    );
+    setState(() {
+      markers[markerId] = marker.copyWith(
+        positionParam: LatLng(
+          center.latitude + offset.dy,
+          center.longitude + offset.dx,
+        ),
+      );
+    });
+  }
+
+  void _changeAnchor(MarkerId markerId) {
+    final Marker marker = markers[markerId]!;
+    final Offset currentAnchor = marker.anchor;
+    final Offset newAnchor = Offset(1.0 - currentAnchor.dy, currentAnchor.dx);
+    setState(() {
+      markers[markerId] = marker.copyWith(
+        anchorParam: newAnchor,
+      );
+    });
+  }
+
+  Future<void> _changeInfoAnchor(MarkerId markerId) async {
+    final Marker marker = markers[markerId]!;
+    final Offset currentAnchor = marker.infoWindow.anchor;
+    final Offset newAnchor = Offset(1.0 - currentAnchor.dy, currentAnchor.dx);
+    setState(() {
+      markers[markerId] = marker.copyWith(
+        infoWindowParam: marker.infoWindow.copyWith(
+          anchorParam: newAnchor,
+        ),
+      );
+    });
+  }
+
+  Future<void> _toggleDraggable(MarkerId markerId) async {
+    final Marker marker = markers[markerId]!;
+    setState(() {
+      markers[markerId] = marker.copyWith(
+        draggableParam: !marker.draggable,
+      );
+    });
+  }
+
+  Future<void> _toggleFlat(MarkerId markerId) async {
+    final Marker marker = markers[markerId]!;
+    setState(() {
+      markers[markerId] = marker.copyWith(
+        flatParam: !marker.flat,
+      );
+    });
+  }
+
+  Future<void> _changeInfo(MarkerId markerId) async {
+    final Marker marker = markers[markerId]!;
+    final String newSnippet = '${marker.infoWindow.snippet!}*';
+    setState(() {
+      markers[markerId] = marker.copyWith(
+        infoWindowParam: marker.infoWindow.copyWith(
+          snippetParam: newSnippet,
+        ),
+      );
+    });
+  }
+
+  Future<void> _changeAlpha(MarkerId markerId) async {
+    final Marker marker = markers[markerId]!;
+    final double current = marker.alpha;
+    setState(() {
+      markers[markerId] = marker.copyWith(
+        alphaParam: current < 0.1 ? 1.0 : current * 0.75,
+      );
+    });
+  }
+
+  Future<void> _changeRotation(MarkerId markerId) async {
+    final Marker marker = markers[markerId]!;
+    final double current = marker.rotation;
+    setState(() {
+      markers[markerId] = marker.copyWith(
+        rotationParam: current == 330.0 ? 0.0 : current + 30.0,
+      );
+    });
+  }
+
+  Future<void> _toggleVisible(MarkerId markerId) async {
+    final Marker marker = markers[markerId]!;
+    setState(() {
+      markers[markerId] = marker.copyWith(
+        visibleParam: !marker.visible,
+      );
+    });
+  }
+
+  Future<void> _changeZIndex(MarkerId markerId) async {
+    final Marker marker = markers[markerId]!;
+    final double current = marker.zIndex;
+    setState(() {
+      markers[markerId] = marker.copyWith(
+        zIndexParam: current == 12.0 ? 0.0 : current + 1.0,
+      );
+    });
+  }
+
+  void _setMarkerIcon(MarkerId markerId, BitmapDescriptor assetIcon) {
+    final Marker marker = markers[markerId]!;
+    setState(() {
+      markers[markerId] = marker.copyWith(
+        iconParam: assetIcon,
+      );
+    });
+  }
+
+  Future<BitmapDescriptor> _getAssetIcon(BuildContext context) async {
+    final Completer<BitmapDescriptor> bitmapIcon =
+        Completer<BitmapDescriptor>();
+    final ImageConfiguration config = createLocalImageConfiguration(context);
+
+    const AssetImage('assets/red_square.png')
+        .resolve(config)
+        .addListener(ImageStreamListener((ImageInfo image, bool sync) async {
+      final ByteData? bytes =
+          await image.image.toByteData(format: ImageByteFormat.png);
+      if (bytes == null) {
+        bitmapIcon.completeError(Exception('Unable to encode icon'));
+        return;
+      }
+      final BitmapDescriptor bitmap =
+          BitmapDescriptor.fromBytes(bytes.buffer.asUint8List());
+      bitmapIcon.complete(bitmap);
+    }));
+
+    return await bitmapIcon.future;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final MarkerId? selectedId = selectedMarker;
+    return Stack(children: <Widget>[
+      Column(
+        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+        crossAxisAlignment: CrossAxisAlignment.stretch,
+        children: <Widget>[
+          Expanded(
+            child: ExampleGoogleMap(
+              onMapCreated: _onMapCreated,
+              initialCameraPosition: const CameraPosition(
+                target: LatLng(-33.852, 151.211),
+                zoom: 11.0,
+              ),
+              markers: Set<Marker>.of(markers.values),
+            ),
+          ),
+          Row(
+            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+            children: <Widget>[
+              TextButton(
+                onPressed: _add,
+                child: const Text('Add'),
+              ),
+              TextButton(
+                onPressed:
+                    selectedId == null ? null : () => _remove(selectedId),
+                child: const Text('Remove'),
+              ),
+            ],
+          ),
+          Wrap(
+            alignment: WrapAlignment.spaceEvenly,
+            children: <Widget>[
+              TextButton(
+                onPressed:
+                    selectedId == null ? null : () => _changeInfo(selectedId),
+                child: const Text('change info'),
+              ),
+              TextButton(
+                onPressed: selectedId == null
+                    ? null
+                    : () => _changeInfoAnchor(selectedId),
+                child: const Text('change info anchor'),
+              ),
+              TextButton(
+                onPressed:
+                    selectedId == null ? null : () => _changeAlpha(selectedId),
+                child: const Text('change alpha'),
+              ),
+              TextButton(
+                onPressed:
+                    selectedId == null ? null : () => _changeAnchor(selectedId),
+                child: const Text('change anchor'),
+              ),
+              TextButton(
+                onPressed: selectedId == null
+                    ? null
+                    : () => _toggleDraggable(selectedId),
+                child: const Text('toggle draggable'),
+              ),
+              TextButton(
+                onPressed:
+                    selectedId == null ? null : () => _toggleFlat(selectedId),
+                child: const Text('toggle flat'),
+              ),
+              TextButton(
+                onPressed: selectedId == null
+                    ? null
+                    : () => _changePosition(selectedId),
+                child: const Text('change position'),
+              ),
+              TextButton(
+                onPressed: selectedId == null
+                    ? null
+                    : () => _changeRotation(selectedId),
+                child: const Text('change rotation'),
+              ),
+              TextButton(
+                onPressed: selectedId == null
+                    ? null
+                    : () => _toggleVisible(selectedId),
+                child: const Text('toggle visible'),
+              ),
+              TextButton(
+                onPressed:
+                    selectedId == null ? null : () => _changeZIndex(selectedId),
+                child: const Text('change zIndex'),
+              ),
+              TextButton(
+                onPressed: selectedId == null
+                    ? null
+                    : () {
+                        _getAssetIcon(context).then(
+                          (BitmapDescriptor icon) {
+                            _setMarkerIcon(selectedId, icon);
+                          },
+                        );
+                      },
+                child: const Text('set marker icon'),
+              ),
+            ],
+          ),
+        ],
+      ),
+      Visibility(
+        visible: markerPosition != null,
+        child: Container(
+          color: Colors.white70,
+          height: 30,
+          padding: const EdgeInsets.only(left: 12, right: 12),
+          child: Row(
+            mainAxisAlignment: MainAxisAlignment.spaceAround,
+            mainAxisSize: MainAxisSize.max,
+            children: <Widget>[
+              if (markerPosition == null)
+                Container()
+              else
+                Expanded(child: Text('lat: ${markerPosition!.latitude}')),
+              if (markerPosition == null)
+                Container()
+              else
+                Expanded(child: Text('lng: ${markerPosition!.longitude}')),
+            ],
+          ),
+        ),
+      ),
+    ]);
+  }
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/lib/place_polygon.dart b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/place_polygon.dart
new file mode 100644
index 0000000..b41cb5d
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/place_polygon.dart
@@ -0,0 +1,306 @@
+// 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.
+
+// ignore_for_file: public_member_api_docs
+
+import 'package:flutter/material.dart';
+import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
+
+import 'example_google_map.dart';
+import 'page.dart';
+
+class PlacePolygonPage extends GoogleMapExampleAppPage {
+  const PlacePolygonPage({Key? key})
+      : super(const Icon(Icons.linear_scale), 'Place polygon', key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return const PlacePolygonBody();
+  }
+}
+
+class PlacePolygonBody extends StatefulWidget {
+  const PlacePolygonBody({Key? key}) : super(key: key);
+
+  @override
+  State<StatefulWidget> createState() => PlacePolygonBodyState();
+}
+
+class PlacePolygonBodyState extends State<PlacePolygonBody> {
+  PlacePolygonBodyState();
+
+  ExampleGoogleMapController? controller;
+  Map<PolygonId, Polygon> polygons = <PolygonId, Polygon>{};
+  Map<PolygonId, double> polygonOffsets = <PolygonId, double>{};
+  int _polygonIdCounter = 0;
+  PolygonId? selectedPolygon;
+
+  // Values when toggling polygon color
+  int strokeColorsIndex = 0;
+  int fillColorsIndex = 0;
+  List<Color> colors = <Color>[
+    Colors.purple,
+    Colors.red,
+    Colors.green,
+    Colors.pink,
+  ];
+
+  // Values when toggling polygon width
+  int widthsIndex = 0;
+  List<int> widths = <int>[10, 20, 5];
+
+  // ignore: use_setters_to_change_properties
+  void _onMapCreated(ExampleGoogleMapController controller) {
+    this.controller = controller;
+  }
+
+  @override
+  void dispose() {
+    super.dispose();
+  }
+
+  void _onPolygonTapped(PolygonId polygonId) {
+    setState(() {
+      selectedPolygon = polygonId;
+    });
+  }
+
+  void _remove(PolygonId polygonId) {
+    setState(() {
+      if (polygons.containsKey(polygonId)) {
+        polygons.remove(polygonId);
+      }
+      selectedPolygon = null;
+    });
+  }
+
+  void _add() {
+    final int polygonCount = polygons.length;
+
+    if (polygonCount == 12) {
+      return;
+    }
+
+    final String polygonIdVal = 'polygon_id_$_polygonIdCounter';
+    final PolygonId polygonId = PolygonId(polygonIdVal);
+
+    final Polygon polygon = Polygon(
+      polygonId: polygonId,
+      consumeTapEvents: true,
+      strokeColor: Colors.orange,
+      strokeWidth: 5,
+      fillColor: Colors.green,
+      points: _createPoints(),
+      onTap: () {
+        _onPolygonTapped(polygonId);
+      },
+    );
+
+    setState(() {
+      polygons[polygonId] = polygon;
+      polygonOffsets[polygonId] = _polygonIdCounter.ceilToDouble();
+      // increment _polygonIdCounter to have unique polygon id each time
+      _polygonIdCounter++;
+    });
+  }
+
+  void _toggleGeodesic(PolygonId polygonId) {
+    final Polygon polygon = polygons[polygonId]!;
+    setState(() {
+      polygons[polygonId] = polygon.copyWith(
+        geodesicParam: !polygon.geodesic,
+      );
+    });
+  }
+
+  void _toggleVisible(PolygonId polygonId) {
+    final Polygon polygon = polygons[polygonId]!;
+    setState(() {
+      polygons[polygonId] = polygon.copyWith(
+        visibleParam: !polygon.visible,
+      );
+    });
+  }
+
+  void _changeStrokeColor(PolygonId polygonId) {
+    final Polygon polygon = polygons[polygonId]!;
+    setState(() {
+      polygons[polygonId] = polygon.copyWith(
+        strokeColorParam: colors[++strokeColorsIndex % colors.length],
+      );
+    });
+  }
+
+  void _changeFillColor(PolygonId polygonId) {
+    final Polygon polygon = polygons[polygonId]!;
+    setState(() {
+      polygons[polygonId] = polygon.copyWith(
+        fillColorParam: colors[++fillColorsIndex % colors.length],
+      );
+    });
+  }
+
+  void _changeWidth(PolygonId polygonId) {
+    final Polygon polygon = polygons[polygonId]!;
+    setState(() {
+      polygons[polygonId] = polygon.copyWith(
+        strokeWidthParam: widths[++widthsIndex % widths.length],
+      );
+    });
+  }
+
+  void _addHoles(PolygonId polygonId) {
+    final Polygon polygon = polygons[polygonId]!;
+    setState(() {
+      polygons[polygonId] =
+          polygon.copyWith(holesParam: _createHoles(polygonId));
+    });
+  }
+
+  void _removeHoles(PolygonId polygonId) {
+    final Polygon polygon = polygons[polygonId]!;
+    setState(() {
+      polygons[polygonId] = polygon.copyWith(
+        holesParam: <List<LatLng>>[],
+      );
+    });
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final PolygonId? selectedId = selectedPolygon;
+    return Column(
+      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+      crossAxisAlignment: CrossAxisAlignment.stretch,
+      children: <Widget>[
+        Center(
+          child: SizedBox(
+            width: 350.0,
+            height: 300.0,
+            child: ExampleGoogleMap(
+              initialCameraPosition: const CameraPosition(
+                target: LatLng(52.4478, -3.5402),
+                zoom: 7.0,
+              ),
+              polygons: Set<Polygon>.of(polygons.values),
+              onMapCreated: _onMapCreated,
+            ),
+          ),
+        ),
+        Expanded(
+          child: SingleChildScrollView(
+            child: Row(
+              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+              children: <Widget>[
+                Row(
+                  children: <Widget>[
+                    Column(
+                      children: <Widget>[
+                        TextButton(
+                          onPressed: _add,
+                          child: const Text('add'),
+                        ),
+                        TextButton(
+                          onPressed: (selectedId == null)
+                              ? null
+                              : () => _remove(selectedId),
+                          child: const Text('remove'),
+                        ),
+                        TextButton(
+                          onPressed: (selectedId == null)
+                              ? null
+                              : () => _toggleVisible(selectedId),
+                          child: const Text('toggle visible'),
+                        ),
+                        TextButton(
+                          onPressed: (selectedId == null)
+                              ? null
+                              : () => _toggleGeodesic(selectedId),
+                          child: const Text('toggle geodesic'),
+                        ),
+                      ],
+                    ),
+                    Column(
+                      children: <Widget>[
+                        TextButton(
+                          onPressed: (selectedId == null)
+                              ? null
+                              : ((polygons[selectedId]!.holes.isNotEmpty)
+                                  ? null
+                                  : () => _addHoles(selectedId)),
+                          child: const Text('add holes'),
+                        ),
+                        TextButton(
+                          onPressed: (selectedId == null)
+                              ? null
+                              : ((polygons[selectedId]!.holes.isEmpty)
+                                  ? null
+                                  : () => _removeHoles(selectedId)),
+                          child: const Text('remove holes'),
+                        ),
+                        TextButton(
+                          onPressed: (selectedId == null)
+                              ? null
+                              : () => _changeWidth(selectedId),
+                          child: const Text('change stroke width'),
+                        ),
+                        TextButton(
+                          onPressed: (selectedId == null)
+                              ? null
+                              : () => _changeStrokeColor(selectedId),
+                          child: const Text('change stroke color'),
+                        ),
+                        TextButton(
+                          onPressed: (selectedId == null)
+                              ? null
+                              : () => _changeFillColor(selectedId),
+                          child: const Text('change fill color'),
+                        ),
+                      ],
+                    )
+                  ],
+                )
+              ],
+            ),
+          ),
+        ),
+      ],
+    );
+  }
+
+  List<LatLng> _createPoints() {
+    final List<LatLng> points = <LatLng>[];
+    final double offset = _polygonIdCounter.ceilToDouble();
+    points.add(_createLatLng(51.2395 + offset, -3.4314));
+    points.add(_createLatLng(53.5234 + offset, -3.5314));
+    points.add(_createLatLng(52.4351 + offset, -4.5235));
+    points.add(_createLatLng(52.1231 + offset, -5.0829));
+    return points;
+  }
+
+  List<List<LatLng>> _createHoles(PolygonId polygonId) {
+    final List<List<LatLng>> holes = <List<LatLng>>[];
+    final double offset = polygonOffsets[polygonId]!;
+
+    final List<LatLng> hole1 = <LatLng>[];
+    hole1.add(_createLatLng(51.8395 + offset, -3.8814));
+    hole1.add(_createLatLng(52.0234 + offset, -3.9914));
+    hole1.add(_createLatLng(52.1351 + offset, -4.4435));
+    hole1.add(_createLatLng(52.0231 + offset, -4.5829));
+    holes.add(hole1);
+
+    final List<LatLng> hole2 = <LatLng>[];
+    hole2.add(_createLatLng(52.2395 + offset, -3.6814));
+    hole2.add(_createLatLng(52.4234 + offset, -3.7914));
+    hole2.add(_createLatLng(52.5351 + offset, -4.2435));
+    hole2.add(_createLatLng(52.4231 + offset, -4.3829));
+    holes.add(hole2);
+
+    return holes;
+  }
+
+  LatLng _createLatLng(double lat, double lng) {
+    return LatLng(lat, lng);
+  }
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/lib/place_polyline.dart b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/place_polyline.dart
new file mode 100644
index 0000000..004206b
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/place_polyline.dart
@@ -0,0 +1,325 @@
+// 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.
+
+// ignore_for_file: public_member_api_docs
+
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
+import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
+
+import 'example_google_map.dart';
+import 'page.dart';
+
+class PlacePolylinePage extends GoogleMapExampleAppPage {
+  const PlacePolylinePage({Key? key})
+      : super(const Icon(Icons.linear_scale), 'Place polyline', key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return const PlacePolylineBody();
+  }
+}
+
+class PlacePolylineBody extends StatefulWidget {
+  const PlacePolylineBody({Key? key}) : super(key: key);
+
+  @override
+  State<StatefulWidget> createState() => PlacePolylineBodyState();
+}
+
+class PlacePolylineBodyState extends State<PlacePolylineBody> {
+  PlacePolylineBodyState();
+
+  ExampleGoogleMapController? controller;
+  Map<PolylineId, Polyline> polylines = <PolylineId, Polyline>{};
+  int _polylineIdCounter = 0;
+  PolylineId? selectedPolyline;
+
+  // Values when toggling polyline color
+  int colorsIndex = 0;
+  List<Color> colors = <Color>[
+    Colors.purple,
+    Colors.red,
+    Colors.green,
+    Colors.pink,
+  ];
+
+  // Values when toggling polyline width
+  int widthsIndex = 0;
+  List<int> widths = <int>[10, 20, 5];
+
+  int jointTypesIndex = 0;
+  List<JointType> jointTypes = <JointType>[
+    JointType.mitered,
+    JointType.bevel,
+    JointType.round
+  ];
+
+  // Values when toggling polyline end cap type
+  int endCapsIndex = 0;
+  List<Cap> endCaps = <Cap>[Cap.buttCap, Cap.squareCap, Cap.roundCap];
+
+  // Values when toggling polyline start cap type
+  int startCapsIndex = 0;
+  List<Cap> startCaps = <Cap>[Cap.buttCap, Cap.squareCap, Cap.roundCap];
+
+  // Values when toggling polyline pattern
+  int patternsIndex = 0;
+  List<List<PatternItem>> patterns = <List<PatternItem>>[
+    <PatternItem>[],
+    <PatternItem>[
+      PatternItem.dash(30.0),
+      PatternItem.gap(20.0),
+      PatternItem.dot,
+      PatternItem.gap(20.0)
+    ],
+    <PatternItem>[PatternItem.dash(30.0), PatternItem.gap(20.0)],
+    <PatternItem>[PatternItem.dot, PatternItem.gap(10.0)],
+  ];
+
+  // ignore: use_setters_to_change_properties
+  void _onMapCreated(ExampleGoogleMapController controller) {
+    this.controller = controller;
+  }
+
+  @override
+  void dispose() {
+    super.dispose();
+  }
+
+  void _onPolylineTapped(PolylineId polylineId) {
+    setState(() {
+      selectedPolyline = polylineId;
+    });
+  }
+
+  void _remove(PolylineId polylineId) {
+    setState(() {
+      if (polylines.containsKey(polylineId)) {
+        polylines.remove(polylineId);
+      }
+      selectedPolyline = null;
+    });
+  }
+
+  void _add() {
+    final int polylineCount = polylines.length;
+
+    if (polylineCount == 12) {
+      return;
+    }
+
+    final String polylineIdVal = 'polyline_id_$_polylineIdCounter';
+    _polylineIdCounter++;
+    final PolylineId polylineId = PolylineId(polylineIdVal);
+
+    final Polyline polyline = Polyline(
+      polylineId: polylineId,
+      consumeTapEvents: true,
+      color: Colors.orange,
+      width: 5,
+      points: _createPoints(),
+      onTap: () {
+        _onPolylineTapped(polylineId);
+      },
+    );
+
+    setState(() {
+      polylines[polylineId] = polyline;
+    });
+  }
+
+  void _toggleGeodesic(PolylineId polylineId) {
+    final Polyline polyline = polylines[polylineId]!;
+    setState(() {
+      polylines[polylineId] = polyline.copyWith(
+        geodesicParam: !polyline.geodesic,
+      );
+    });
+  }
+
+  void _toggleVisible(PolylineId polylineId) {
+    final Polyline polyline = polylines[polylineId]!;
+    setState(() {
+      polylines[polylineId] = polyline.copyWith(
+        visibleParam: !polyline.visible,
+      );
+    });
+  }
+
+  void _changeColor(PolylineId polylineId) {
+    final Polyline polyline = polylines[polylineId]!;
+    setState(() {
+      polylines[polylineId] = polyline.copyWith(
+        colorParam: colors[++colorsIndex % colors.length],
+      );
+    });
+  }
+
+  void _changeWidth(PolylineId polylineId) {
+    final Polyline polyline = polylines[polylineId]!;
+    setState(() {
+      polylines[polylineId] = polyline.copyWith(
+        widthParam: widths[++widthsIndex % widths.length],
+      );
+    });
+  }
+
+  void _changeJointType(PolylineId polylineId) {
+    final Polyline polyline = polylines[polylineId]!;
+    setState(() {
+      polylines[polylineId] = polyline.copyWith(
+        jointTypeParam: jointTypes[++jointTypesIndex % jointTypes.length],
+      );
+    });
+  }
+
+  void _changeEndCap(PolylineId polylineId) {
+    final Polyline polyline = polylines[polylineId]!;
+    setState(() {
+      polylines[polylineId] = polyline.copyWith(
+        endCapParam: endCaps[++endCapsIndex % endCaps.length],
+      );
+    });
+  }
+
+  void _changeStartCap(PolylineId polylineId) {
+    final Polyline polyline = polylines[polylineId]!;
+    setState(() {
+      polylines[polylineId] = polyline.copyWith(
+        startCapParam: startCaps[++startCapsIndex % startCaps.length],
+      );
+    });
+  }
+
+  void _changePattern(PolylineId polylineId) {
+    final Polyline polyline = polylines[polylineId]!;
+    setState(() {
+      polylines[polylineId] = polyline.copyWith(
+        patternsParam: patterns[++patternsIndex % patterns.length],
+      );
+    });
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final bool isIOS = !kIsWeb && defaultTargetPlatform == TargetPlatform.iOS;
+
+    final PolylineId? selectedId = selectedPolyline;
+
+    return Column(
+      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+      crossAxisAlignment: CrossAxisAlignment.stretch,
+      children: <Widget>[
+        Center(
+          child: SizedBox(
+            width: 350.0,
+            height: 300.0,
+            child: ExampleGoogleMap(
+              initialCameraPosition: const CameraPosition(
+                target: LatLng(53.1721, -3.5402),
+                zoom: 7.0,
+              ),
+              polylines: Set<Polyline>.of(polylines.values),
+              onMapCreated: _onMapCreated,
+            ),
+          ),
+        ),
+        Expanded(
+          child: SingleChildScrollView(
+            child: Row(
+              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+              children: <Widget>[
+                Row(
+                  children: <Widget>[
+                    Column(
+                      children: <Widget>[
+                        TextButton(
+                          onPressed: _add,
+                          child: const Text('add'),
+                        ),
+                        TextButton(
+                          onPressed: (selectedId == null)
+                              ? null
+                              : () => _remove(selectedId),
+                          child: const Text('remove'),
+                        ),
+                        TextButton(
+                          onPressed: (selectedId == null)
+                              ? null
+                              : () => _toggleVisible(selectedId),
+                          child: const Text('toggle visible'),
+                        ),
+                        TextButton(
+                          onPressed: (selectedId == null)
+                              ? null
+                              : () => _toggleGeodesic(selectedId),
+                          child: const Text('toggle geodesic'),
+                        ),
+                      ],
+                    ),
+                    Column(
+                      children: <Widget>[
+                        TextButton(
+                          onPressed: (selectedId == null)
+                              ? null
+                              : () => _changeWidth(selectedId),
+                          child: const Text('change width'),
+                        ),
+                        TextButton(
+                          onPressed: (selectedId == null)
+                              ? null
+                              : () => _changeColor(selectedId),
+                          child: const Text('change color'),
+                        ),
+                        TextButton(
+                          onPressed: isIOS || (selectedId == null)
+                              ? null
+                              : () => _changeStartCap(selectedId),
+                          child: const Text('change start cap [Android only]'),
+                        ),
+                        TextButton(
+                          onPressed: isIOS || (selectedId == null)
+                              ? null
+                              : () => _changeEndCap(selectedId),
+                          child: const Text('change end cap [Android only]'),
+                        ),
+                        TextButton(
+                          onPressed: isIOS || (selectedId == null)
+                              ? null
+                              : () => _changeJointType(selectedId),
+                          child: const Text('change joint type [Android only]'),
+                        ),
+                        TextButton(
+                          onPressed: isIOS || (selectedId == null)
+                              ? null
+                              : () => _changePattern(selectedId),
+                          child: const Text('change pattern [Android only]'),
+                        ),
+                      ],
+                    )
+                  ],
+                )
+              ],
+            ),
+          ),
+        ),
+      ],
+    );
+  }
+
+  List<LatLng> _createPoints() {
+    final List<LatLng> points = <LatLng>[];
+    final double offset = _polylineIdCounter.ceilToDouble();
+    points.add(_createLatLng(51.4816 + offset, -3.1791));
+    points.add(_createLatLng(53.0430 + offset, -2.9925));
+    points.add(_createLatLng(53.1396 + offset, -4.2739));
+    points.add(_createLatLng(52.4153 + offset, -4.0829));
+    return points;
+  }
+
+  LatLng _createLatLng(double lat, double lng) {
+    return LatLng(lat, lng);
+  }
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/lib/scrolling_map.dart b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/scrolling_map.dart
new file mode 100644
index 0000000..7a9b75c
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/scrolling_map.dart
@@ -0,0 +1,114 @@
+// 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.
+
+// ignore_for_file: public_member_api_docs
+
+import 'package:flutter/foundation.dart';
+import 'package:flutter/gestures.dart';
+import 'package:flutter/material.dart';
+import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
+
+import 'example_google_map.dart';
+import 'page.dart';
+
+const LatLng _center = LatLng(32.080664, 34.9563837);
+
+class ScrollingMapPage extends GoogleMapExampleAppPage {
+  const ScrollingMapPage({Key? key})
+      : super(const Icon(Icons.map), 'Scrolling map', key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return const ScrollingMapBody();
+  }
+}
+
+class ScrollingMapBody extends StatelessWidget {
+  const ScrollingMapBody({Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return ListView(
+      children: <Widget>[
+        Card(
+          child: Padding(
+            padding: const EdgeInsets.symmetric(vertical: 30.0),
+            child: Column(
+              children: <Widget>[
+                const Padding(
+                  padding: EdgeInsets.only(bottom: 12.0),
+                  child: Text('This map consumes all touch events.'),
+                ),
+                Center(
+                  child: SizedBox(
+                    width: 300.0,
+                    height: 300.0,
+                    child: ExampleGoogleMap(
+                      initialCameraPosition: const CameraPosition(
+                        target: _center,
+                        zoom: 11.0,
+                      ),
+                      gestureRecognizers: //
+                          <Factory<OneSequenceGestureRecognizer>>{
+                        Factory<OneSequenceGestureRecognizer>(
+                          () => EagerGestureRecognizer(),
+                        ),
+                      },
+                    ),
+                  ),
+                ),
+              ],
+            ),
+          ),
+        ),
+        Card(
+          child: Padding(
+            padding: const EdgeInsets.symmetric(vertical: 30.0),
+            child: Column(
+              children: <Widget>[
+                const Text("This map doesn't consume the vertical drags."),
+                const Padding(
+                  padding: EdgeInsets.only(bottom: 12.0),
+                  child:
+                      Text('It still gets other gestures (e.g scale or tap).'),
+                ),
+                Center(
+                  child: SizedBox(
+                    width: 300.0,
+                    height: 300.0,
+                    child: ExampleGoogleMap(
+                      initialCameraPosition: const CameraPosition(
+                        target: _center,
+                        zoom: 11.0,
+                      ),
+                      markers: <Marker>{
+                        Marker(
+                          markerId: const MarkerId('test_marker_id'),
+                          position: LatLng(
+                            _center.latitude,
+                            _center.longitude,
+                          ),
+                          infoWindow: const InfoWindow(
+                            title: 'An interesting location',
+                            snippet: '*',
+                          ),
+                        ),
+                      },
+                      gestureRecognizers: <
+                          Factory<OneSequenceGestureRecognizer>>{
+                        Factory<OneSequenceGestureRecognizer>(
+                          () => ScaleGestureRecognizer(),
+                        ),
+                      },
+                    ),
+                  ),
+                ),
+              ],
+            ),
+          ),
+        ),
+      ],
+    );
+  }
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/lib/snapshot.dart b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/snapshot.dart
new file mode 100644
index 0000000..56a90a8
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/snapshot.dart
@@ -0,0 +1,76 @@
+// 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.
+
+// ignore_for_file: public_member_api_docs
+
+import 'dart:typed_data';
+
+import 'package:flutter/material.dart';
+import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
+
+import 'example_google_map.dart';
+import 'page.dart';
+
+const CameraPosition _kInitialPosition =
+    CameraPosition(target: LatLng(-33.852, 151.211), zoom: 11.0);
+
+class SnapshotPage extends GoogleMapExampleAppPage {
+  const SnapshotPage({Key? key})
+      : super(const Icon(Icons.camera_alt), 'Take a snapshot of the map',
+            key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return _SnapshotBody();
+  }
+}
+
+class _SnapshotBody extends StatefulWidget {
+  @override
+  _SnapshotBodyState createState() => _SnapshotBodyState();
+}
+
+class _SnapshotBodyState extends State<_SnapshotBody> {
+  ExampleGoogleMapController? _mapController;
+  Uint8List? _imageBytes;
+
+  @override
+  Widget build(BuildContext context) {
+    return Padding(
+      padding: const EdgeInsets.all(16),
+      child: Column(
+        crossAxisAlignment: CrossAxisAlignment.stretch,
+        children: <Widget>[
+          SizedBox(
+            height: 180,
+            child: ExampleGoogleMap(
+              onMapCreated: onMapCreated,
+              initialCameraPosition: _kInitialPosition,
+            ),
+          ),
+          TextButton(
+            child: const Text('Take a snapshot'),
+            onPressed: () async {
+              final Uint8List? imageBytes =
+                  await _mapController?.takeSnapshot();
+              setState(() {
+                _imageBytes = imageBytes;
+              });
+            },
+          ),
+          Container(
+            decoration: BoxDecoration(color: Colors.blueGrey[50]),
+            height: 180,
+            child: _imageBytes != null ? Image.memory(_imageBytes!) : null,
+          ),
+        ],
+      ),
+    );
+  }
+
+  // ignore: use_setters_to_change_properties
+  void onMapCreated(ExampleGoogleMapController controller) {
+    _mapController = controller;
+  }
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/lib/tile_overlay.dart b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/tile_overlay.dart
new file mode 100644
index 0000000..555f037
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/tile_overlay.dart
@@ -0,0 +1,156 @@
+// 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.
+
+// ignore_for_file: public_member_api_docs
+
+import 'dart:typed_data';
+import 'dart:ui' as ui;
+
+import 'package:flutter/material.dart';
+import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
+
+import 'example_google_map.dart';
+import 'page.dart';
+
+class TileOverlayPage extends GoogleMapExampleAppPage {
+  const TileOverlayPage({Key? key})
+      : super(const Icon(Icons.map), 'Tile overlay', key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return const TileOverlayBody();
+  }
+}
+
+class TileOverlayBody extends StatefulWidget {
+  const TileOverlayBody({Key? key}) : super(key: key);
+
+  @override
+  State<StatefulWidget> createState() => TileOverlayBodyState();
+}
+
+class TileOverlayBodyState extends State<TileOverlayBody> {
+  TileOverlayBodyState();
+
+  ExampleGoogleMapController? controller;
+  TileOverlay? _tileOverlay;
+
+  // ignore: use_setters_to_change_properties
+  void _onMapCreated(ExampleGoogleMapController controller) {
+    this.controller = controller;
+  }
+
+  @override
+  void dispose() {
+    super.dispose();
+  }
+
+  void _removeTileOverlay() {
+    setState(() {
+      _tileOverlay = null;
+    });
+  }
+
+  void _addTileOverlay() {
+    final TileOverlay tileOverlay = TileOverlay(
+      tileOverlayId: const TileOverlayId('tile_overlay_1'),
+      tileProvider: _DebugTileProvider(),
+    );
+    setState(() {
+      _tileOverlay = tileOverlay;
+    });
+  }
+
+  void _clearTileCache() {
+    if (_tileOverlay != null && controller != null) {
+      controller!.clearTileCache(_tileOverlay!.tileOverlayId);
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final Set<TileOverlay> overlays = <TileOverlay>{
+      if (_tileOverlay != null) _tileOverlay!,
+    };
+    return Column(
+      mainAxisSize: MainAxisSize.min,
+      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+      crossAxisAlignment: CrossAxisAlignment.stretch,
+      children: <Widget>[
+        Center(
+          child: SizedBox(
+            width: 350.0,
+            height: 300.0,
+            child: ExampleGoogleMap(
+              initialCameraPosition: const CameraPosition(
+                target: LatLng(59.935460, 30.325177),
+                zoom: 7.0,
+              ),
+              tileOverlays: overlays,
+              onMapCreated: _onMapCreated,
+            ),
+          ),
+        ),
+        TextButton(
+          onPressed: _addTileOverlay,
+          child: const Text('Add tile overlay'),
+        ),
+        TextButton(
+          onPressed: _removeTileOverlay,
+          child: const Text('Remove tile overlay'),
+        ),
+        TextButton(
+          onPressed: _clearTileCache,
+          child: const Text('Clear tile cache'),
+        ),
+      ],
+    );
+  }
+}
+
+class _DebugTileProvider implements TileProvider {
+  _DebugTileProvider() {
+    boxPaint.isAntiAlias = true;
+    boxPaint.color = Colors.blue;
+    boxPaint.strokeWidth = 2.0;
+    boxPaint.style = PaintingStyle.stroke;
+  }
+
+  static const int width = 100;
+  static const int height = 100;
+  static final Paint boxPaint = Paint();
+  static const TextStyle textStyle = TextStyle(
+    color: Colors.red,
+    fontSize: 20,
+  );
+
+  @override
+  Future<Tile> getTile(int x, int y, int? zoom) async {
+    final ui.PictureRecorder recorder = ui.PictureRecorder();
+    final Canvas canvas = Canvas(recorder);
+    final TextSpan textSpan = TextSpan(
+      text: '$x,$y',
+      style: textStyle,
+    );
+    final TextPainter textPainter = TextPainter(
+      text: textSpan,
+      textDirection: TextDirection.ltr,
+    );
+    textPainter.layout(
+      minWidth: 0.0,
+      maxWidth: width.toDouble(),
+    );
+    const Offset offset = Offset(0, 0);
+    textPainter.paint(canvas, offset);
+    canvas.drawRect(
+        Rect.fromLTRB(0, 0, width.toDouble(), width.toDouble()), boxPaint);
+    final ui.Picture picture = recorder.endRecording();
+    final Uint8List byteData = await picture
+        .toImage(width, height)
+        .then((ui.Image image) =>
+            image.toByteData(format: ui.ImageByteFormat.png))
+        .then((ByteData? byteData) => byteData!.buffer.asUint8List());
+    return Tile(width, height, byteData);
+  }
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_android/example/pubspec.yaml
new file mode 100644
index 0000000..fc1059b
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/example/pubspec.yaml
@@ -0,0 +1,33 @@
+name: google_maps_flutter_example
+description: Demonstrates how to use the google_maps_flutter plugin.
+publish_to: none
+
+environment:
+  sdk: ">=2.14.0 <3.0.0"
+  flutter: ">=2.8.0"
+
+dependencies:
+  cupertino_icons: ^0.1.0
+  flutter:
+    sdk: flutter
+  flutter_plugin_android_lifecycle: ^2.0.1
+  google_maps_flutter_android:
+    # When depending on this package from a real application you should use:
+    #   google_maps_flutter_android: ^x.y.z
+    # See https://dart.dev/tools/pub/dependencies#version-constraints
+    # The example app is bundled with the plugin so we use a path dependency on
+    # the parent directory to use the current plugin's version.
+    path: ../
+  google_maps_flutter_platform_interface: ^2.2.1
+
+dev_dependencies:
+  espresso: ^0.1.0+2
+  flutter_driver:
+    sdk: flutter
+  integration_test:
+    sdk: flutter
+
+flutter:
+  uses-material-design: true
+  assets:
+    - assets/
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/test_driver/integration_test.dart b/packages/google_maps_flutter/google_maps_flutter_android/example/test_driver/integration_test.dart
new file mode 100644
index 0000000..4f10f2a
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/example/test_driver/integration_test.dart
@@ -0,0 +1,7 @@
+// 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 'package:integration_test/integration_test_driver.dart';
+
+Future<void> main() => integrationDriver();
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/lib/google_maps_flutter_android.dart b/packages/google_maps_flutter/google_maps_flutter_android/lib/google_maps_flutter_android.dart
new file mode 100644
index 0000000..edd231e
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/lib/google_maps_flutter_android.dart
@@ -0,0 +1,5 @@
+// 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.
+
+export 'src/google_maps_flutter_android.dart';
diff --git a/packages/google_maps_flutter/google_maps_flutter/example/integration_test/google_map_inspector.dart b/packages/google_maps_flutter/google_maps_flutter_android/lib/src/google_map_inspector_android.dart
similarity index 67%
rename from packages/google_maps_flutter/google_maps_flutter/example/integration_test/google_map_inspector.dart
rename to packages/google_maps_flutter/google_maps_flutter_android/lib/src/google_map_inspector_android.dart
index 95ca969..4e0cad7 100644
--- a/packages/google_maps_flutter/google_maps_flutter/example/integration_test/google_map_inspector.dart
+++ b/packages/google_maps_flutter/google_maps_flutter_android/lib/src/google_map_inspector_android.dart
@@ -2,66 +2,59 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'package:flutter/foundation.dart';
+import 'package:flutter/services.dart';
 import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
 
-/// A method-channel-based implementation of [GoogleMapsInspectorPlatform], for
-/// use in tests in conjunction with [MethodChannelGoogleMapsFlutter].
-// TODO(stuartmorgan): Move this into the platform implementations when
-// federating the mobile implementations.
-class MethodChannelGoogleMapsInspector extends GoogleMapsInspectorPlatform {
+/// An Android of implementation of [GoogleMapsInspectorPlatform].
+@visibleForTesting
+class GoogleMapsInspectorAndroid extends GoogleMapsInspectorPlatform {
   /// Creates a method-channel-based inspector instance that gets the channel
-  /// for a given map ID from [mapsPlatform].
-  MethodChannelGoogleMapsInspector(MethodChannelGoogleMapsFlutter mapsPlatform)
-      : _mapsPlatform = mapsPlatform;
+  /// for a given map ID from [channelProvider].
+  GoogleMapsInspectorAndroid(MethodChannel? Function(int mapId) channelProvider)
+      : _channelProvider = channelProvider;
 
-  final MethodChannelGoogleMapsFlutter _mapsPlatform;
+  final MethodChannel? Function(int mapId) _channelProvider;
 
   @override
   Future<bool> areBuildingsEnabled({required int mapId}) async {
-    return (await _mapsPlatform
-        .channel(mapId)
+    return (await _channelProvider(mapId)!
         .invokeMethod<bool>('map#isBuildingsEnabled'))!;
   }
 
   @override
   Future<bool> areRotateGesturesEnabled({required int mapId}) async {
-    return (await _mapsPlatform
-        .channel(mapId)
+    return (await _channelProvider(mapId)!
         .invokeMethod<bool>('map#isRotateGesturesEnabled'))!;
   }
 
   @override
   Future<bool> areScrollGesturesEnabled({required int mapId}) async {
-    return (await _mapsPlatform
-        .channel(mapId)
+    return (await _channelProvider(mapId)!
         .invokeMethod<bool>('map#isScrollGesturesEnabled'))!;
   }
 
   @override
   Future<bool> areTiltGesturesEnabled({required int mapId}) async {
-    return (await _mapsPlatform
-        .channel(mapId)
+    return (await _channelProvider(mapId)!
         .invokeMethod<bool>('map#isTiltGesturesEnabled'))!;
   }
 
   @override
   Future<bool> areZoomControlsEnabled({required int mapId}) async {
-    return (await _mapsPlatform
-        .channel(mapId)
+    return (await _channelProvider(mapId)!
         .invokeMethod<bool>('map#isZoomControlsEnabled'))!;
   }
 
   @override
   Future<bool> areZoomGesturesEnabled({required int mapId}) async {
-    return (await _mapsPlatform
-        .channel(mapId)
+    return (await _channelProvider(mapId)!
         .invokeMethod<bool>('map#isZoomGesturesEnabled'))!;
   }
 
   @override
   Future<MinMaxZoomPreference> getMinMaxZoomLevels({required int mapId}) async {
-    final List<double> zoomLevels = (await _mapsPlatform
-            .channel(mapId)
+    final List<double> zoomLevels = (await _channelProvider(mapId)!
             .invokeMethod<List<dynamic>>('map#getMinMaxZoomLevels'))!
         .cast<double>();
     return MinMaxZoomPreference(zoomLevels[0], zoomLevels[1]);
@@ -70,8 +63,7 @@
   @override
   Future<TileOverlay?> getTileOverlayInfo(TileOverlayId tileOverlayId,
       {required int mapId}) async {
-    final Map<String, Object?>? tileInfo = await _mapsPlatform
-        .channel(mapId)
+    final Map<String, Object?>? tileInfo = await _channelProvider(mapId)!
         .invokeMapMethod<String, dynamic>(
             'map#getTileOverlayInfo', <String, String>{
       'tileOverlayId': tileOverlayId.value,
@@ -91,36 +83,31 @@
 
   @override
   Future<bool> isCompassEnabled({required int mapId}) async {
-    return (await _mapsPlatform
-        .channel(mapId)
+    return (await _channelProvider(mapId)!
         .invokeMethod<bool>('map#isCompassEnabled'))!;
   }
 
   @override
   Future<bool> isLiteModeEnabled({required int mapId}) async {
-    return (await _mapsPlatform
-        .channel(mapId)
+    return (await _channelProvider(mapId)!
         .invokeMethod<bool>('map#isLiteModeEnabled'))!;
   }
 
   @override
   Future<bool> isMapToolbarEnabled({required int mapId}) async {
-    return (await _mapsPlatform
-        .channel(mapId)
+    return (await _channelProvider(mapId)!
         .invokeMethod<bool>('map#isMapToolbarEnabled'))!;
   }
 
   @override
   Future<bool> isMyLocationButtonEnabled({required int mapId}) async {
-    return (await _mapsPlatform
-        .channel(mapId)
+    return (await _channelProvider(mapId)!
         .invokeMethod<bool>('map#isMyLocationButtonEnabled'))!;
   }
 
   @override
   Future<bool> isTrafficEnabled({required int mapId}) async {
-    return (await _mapsPlatform
-        .channel(mapId)
+    return (await _channelProvider(mapId)!
         .invokeMethod<bool>('map#isTrafficEnabled'))!;
   }
 }
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/lib/src/google_maps_flutter_android.dart b/packages/google_maps_flutter/google_maps_flutter_android/lib/src/google_maps_flutter_android.dart
new file mode 100644
index 0000000..2c11053
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/lib/src/google_maps_flutter_android.dart
@@ -0,0 +1,696 @@
+// 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 'dart:async';
+// TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231)
+// ignore: unnecessary_import
+import 'dart:typed_data';
+
+import 'package:flutter/foundation.dart';
+import 'package:flutter/gestures.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/rendering.dart';
+import 'package:flutter/services.dart';
+import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
+import 'package:stream_transform/stream_transform.dart';
+
+import 'google_map_inspector_android.dart';
+
+// TODO(stuartmorgan): Remove the dependency on platform interface toJson
+// methods. Channel serialization details should all be package-internal.
+
+/// Error thrown when an unknown map ID is provided to a method channel API.
+class UnknownMapIDError extends Error {
+  /// Creates an assertion error with the provided [mapId] and optional
+  /// [message].
+  UnknownMapIDError(this.mapId, [this.message]);
+
+  /// The unknown ID.
+  final int mapId;
+
+  /// Message describing the assertion error.
+  final Object? message;
+
+  @override
+  String toString() {
+    if (message != null) {
+      return 'Unknown map ID $mapId: ${Error.safeToString(message)}';
+    }
+    return 'Unknown map ID $mapId';
+  }
+}
+
+/// An implementation of [GoogleMapsFlutterPlatform] for Android.
+class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform {
+  /// Registers the Android implementation of GoogleMapsFlutterPlatform.
+  static void registerWith() {
+    GoogleMapsFlutterPlatform.instance = GoogleMapsFlutterAndroid();
+  }
+
+  // Keep a collection of id -> channel
+  // Every method call passes the int mapId
+  final Map<int, MethodChannel> _channels = <int, MethodChannel>{};
+
+  /// Accesses the MethodChannel associated to the passed mapId.
+  MethodChannel _channel(int mapId) {
+    final MethodChannel? channel = _channels[mapId];
+    if (channel == null) {
+      throw UnknownMapIDError(mapId);
+    }
+    return channel;
+  }
+
+  // Keep a collection of mapId to a map of TileOverlays.
+  final Map<int, Map<TileOverlayId, TileOverlay>> _tileOverlays =
+      <int, Map<TileOverlayId, TileOverlay>>{};
+
+  /// Returns the channel for [mapId], creating it if it doesn't already exist.
+  @visibleForTesting
+  MethodChannel ensureChannelInitialized(int mapId) {
+    MethodChannel? channel = _channels[mapId];
+    if (channel == null) {
+      channel = MethodChannel('plugins.flutter.dev/google_maps_android_$mapId');
+      channel.setMethodCallHandler(
+          (MethodCall call) => _handleMethodCall(call, mapId));
+      _channels[mapId] = channel;
+    }
+    return channel;
+  }
+
+  @override
+  Future<void> init(int mapId) {
+    final MethodChannel channel = ensureChannelInitialized(mapId);
+    return channel.invokeMethod<void>('map#waitForMap');
+  }
+
+  @override
+  void dispose({required int mapId}) {
+    // Noop!
+  }
+
+  // The controller we need to broadcast the different events coming
+  // from handleMethodCall.
+  //
+  // It is a `broadcast` because multiple controllers will connect to
+  // different stream views of this Controller.
+  final StreamController<MapEvent<Object?>> _mapEventStreamController =
+      StreamController<MapEvent<Object?>>.broadcast();
+
+  // Returns a filtered view of the events in the _controller, by mapId.
+  Stream<MapEvent<Object?>> _events(int mapId) =>
+      _mapEventStreamController.stream
+          .where((MapEvent<Object?> event) => event.mapId == mapId);
+
+  @override
+  Stream<CameraMoveStartedEvent> onCameraMoveStarted({required int mapId}) {
+    return _events(mapId).whereType<CameraMoveStartedEvent>();
+  }
+
+  @override
+  Stream<CameraMoveEvent> onCameraMove({required int mapId}) {
+    return _events(mapId).whereType<CameraMoveEvent>();
+  }
+
+  @override
+  Stream<CameraIdleEvent> onCameraIdle({required int mapId}) {
+    return _events(mapId).whereType<CameraIdleEvent>();
+  }
+
+  @override
+  Stream<MarkerTapEvent> onMarkerTap({required int mapId}) {
+    return _events(mapId).whereType<MarkerTapEvent>();
+  }
+
+  @override
+  Stream<InfoWindowTapEvent> onInfoWindowTap({required int mapId}) {
+    return _events(mapId).whereType<InfoWindowTapEvent>();
+  }
+
+  @override
+  Stream<MarkerDragStartEvent> onMarkerDragStart({required int mapId}) {
+    return _events(mapId).whereType<MarkerDragStartEvent>();
+  }
+
+  @override
+  Stream<MarkerDragEvent> onMarkerDrag({required int mapId}) {
+    return _events(mapId).whereType<MarkerDragEvent>();
+  }
+
+  @override
+  Stream<MarkerDragEndEvent> onMarkerDragEnd({required int mapId}) {
+    return _events(mapId).whereType<MarkerDragEndEvent>();
+  }
+
+  @override
+  Stream<PolylineTapEvent> onPolylineTap({required int mapId}) {
+    return _events(mapId).whereType<PolylineTapEvent>();
+  }
+
+  @override
+  Stream<PolygonTapEvent> onPolygonTap({required int mapId}) {
+    return _events(mapId).whereType<PolygonTapEvent>();
+  }
+
+  @override
+  Stream<CircleTapEvent> onCircleTap({required int mapId}) {
+    return _events(mapId).whereType<CircleTapEvent>();
+  }
+
+  @override
+  Stream<MapTapEvent> onTap({required int mapId}) {
+    return _events(mapId).whereType<MapTapEvent>();
+  }
+
+  @override
+  Stream<MapLongPressEvent> onLongPress({required int mapId}) {
+    return _events(mapId).whereType<MapLongPressEvent>();
+  }
+
+  Future<dynamic> _handleMethodCall(MethodCall call, int mapId) async {
+    switch (call.method) {
+      case 'camera#onMoveStarted':
+        _mapEventStreamController.add(CameraMoveStartedEvent(mapId));
+        break;
+      case 'camera#onMove':
+        _mapEventStreamController.add(CameraMoveEvent(
+          mapId,
+          CameraPosition.fromMap(call.arguments['position'])!,
+        ));
+        break;
+      case 'camera#onIdle':
+        _mapEventStreamController.add(CameraIdleEvent(mapId));
+        break;
+      case 'marker#onTap':
+        _mapEventStreamController.add(MarkerTapEvent(
+          mapId,
+          MarkerId(call.arguments['markerId'] as String),
+        ));
+        break;
+      case 'marker#onDragStart':
+        _mapEventStreamController.add(MarkerDragStartEvent(
+          mapId,
+          LatLng.fromJson(call.arguments['position'])!,
+          MarkerId(call.arguments['markerId'] as String),
+        ));
+        break;
+      case 'marker#onDrag':
+        _mapEventStreamController.add(MarkerDragEvent(
+          mapId,
+          LatLng.fromJson(call.arguments['position'])!,
+          MarkerId(call.arguments['markerId'] as String),
+        ));
+        break;
+      case 'marker#onDragEnd':
+        _mapEventStreamController.add(MarkerDragEndEvent(
+          mapId,
+          LatLng.fromJson(call.arguments['position'])!,
+          MarkerId(call.arguments['markerId'] as String),
+        ));
+        break;
+      case 'infoWindow#onTap':
+        _mapEventStreamController.add(InfoWindowTapEvent(
+          mapId,
+          MarkerId(call.arguments['markerId'] as String),
+        ));
+        break;
+      case 'polyline#onTap':
+        _mapEventStreamController.add(PolylineTapEvent(
+          mapId,
+          PolylineId(call.arguments['polylineId'] as String),
+        ));
+        break;
+      case 'polygon#onTap':
+        _mapEventStreamController.add(PolygonTapEvent(
+          mapId,
+          PolygonId(call.arguments['polygonId'] as String),
+        ));
+        break;
+      case 'circle#onTap':
+        _mapEventStreamController.add(CircleTapEvent(
+          mapId,
+          CircleId(call.arguments['circleId'] as String),
+        ));
+        break;
+      case 'map#onTap':
+        _mapEventStreamController.add(MapTapEvent(
+          mapId,
+          LatLng.fromJson(call.arguments['position'])!,
+        ));
+        break;
+      case 'map#onLongPress':
+        _mapEventStreamController.add(MapLongPressEvent(
+          mapId,
+          LatLng.fromJson(call.arguments['position'])!,
+        ));
+        break;
+      case 'tileOverlay#getTile':
+        final Map<TileOverlayId, TileOverlay>? tileOverlaysForThisMap =
+            _tileOverlays[mapId];
+        final String tileOverlayId = call.arguments['tileOverlayId'] as String;
+        final TileOverlay? tileOverlay =
+            tileOverlaysForThisMap?[TileOverlayId(tileOverlayId)];
+        final TileProvider? tileProvider = tileOverlay?.tileProvider;
+        if (tileProvider == null) {
+          return TileProvider.noTile.toJson();
+        }
+        final Tile tile = await tileProvider.getTile(
+          call.arguments['x'] as int,
+          call.arguments['y'] as int,
+          call.arguments['zoom'] as int?,
+        );
+        return tile.toJson();
+      default:
+        throw MissingPluginException();
+    }
+  }
+
+  @override
+  Future<void> updateMapOptions(
+    Map<String, dynamic> optionsUpdate, {
+    required int mapId,
+  }) {
+    assert(optionsUpdate != null);
+    return _channel(mapId).invokeMethod<void>(
+      'map#update',
+      <String, dynamic>{
+        'options': optionsUpdate,
+      },
+    );
+  }
+
+  @override
+  Future<void> updateMarkers(
+    MarkerUpdates markerUpdates, {
+    required int mapId,
+  }) {
+    assert(markerUpdates != null);
+    return _channel(mapId).invokeMethod<void>(
+      'markers#update',
+      markerUpdates.toJson(),
+    );
+  }
+
+  @override
+  Future<void> updatePolygons(
+    PolygonUpdates polygonUpdates, {
+    required int mapId,
+  }) {
+    assert(polygonUpdates != null);
+    return _channel(mapId).invokeMethod<void>(
+      'polygons#update',
+      polygonUpdates.toJson(),
+    );
+  }
+
+  @override
+  Future<void> updatePolylines(
+    PolylineUpdates polylineUpdates, {
+    required int mapId,
+  }) {
+    assert(polylineUpdates != null);
+    return _channel(mapId).invokeMethod<void>(
+      'polylines#update',
+      polylineUpdates.toJson(),
+    );
+  }
+
+  @override
+  Future<void> updateCircles(
+    CircleUpdates circleUpdates, {
+    required int mapId,
+  }) {
+    assert(circleUpdates != null);
+    return _channel(mapId).invokeMethod<void>(
+      'circles#update',
+      circleUpdates.toJson(),
+    );
+  }
+
+  @override
+  Future<void> updateTileOverlays({
+    required Set<TileOverlay> newTileOverlays,
+    required int mapId,
+  }) {
+    final Map<TileOverlayId, TileOverlay>? currentTileOverlays =
+        _tileOverlays[mapId];
+    final Set<TileOverlay> previousSet = currentTileOverlays != null
+        ? currentTileOverlays.values.toSet()
+        : <TileOverlay>{};
+    final _TileOverlayUpdates updates =
+        _TileOverlayUpdates.from(previousSet, newTileOverlays);
+    _tileOverlays[mapId] = keyTileOverlayId(newTileOverlays);
+    return _channel(mapId).invokeMethod<void>(
+      'tileOverlays#update',
+      updates.toJson(),
+    );
+  }
+
+  @override
+  Future<void> clearTileCache(
+    TileOverlayId tileOverlayId, {
+    required int mapId,
+  }) {
+    return _channel(mapId)
+        .invokeMethod<void>('tileOverlays#clearTileCache', <String, Object>{
+      'tileOverlayId': tileOverlayId.value,
+    });
+  }
+
+  @override
+  Future<void> animateCamera(
+    CameraUpdate cameraUpdate, {
+    required int mapId,
+  }) {
+    return _channel(mapId)
+        .invokeMethod<void>('camera#animate', <String, Object>{
+      'cameraUpdate': cameraUpdate.toJson(),
+    });
+  }
+
+  @override
+  Future<void> moveCamera(
+    CameraUpdate cameraUpdate, {
+    required int mapId,
+  }) {
+    return _channel(mapId).invokeMethod<void>('camera#move', <String, dynamic>{
+      'cameraUpdate': cameraUpdate.toJson(),
+    });
+  }
+
+  @override
+  Future<void> setMapStyle(
+    String? mapStyle, {
+    required int mapId,
+  }) async {
+    final List<dynamic> successAndError = (await _channel(mapId)
+        .invokeMethod<List<dynamic>>('map#setStyle', mapStyle))!;
+    final bool success = successAndError[0] as bool;
+    if (!success) {
+      throw MapStyleException(successAndError[1] as String);
+    }
+  }
+
+  @override
+  Future<LatLngBounds> getVisibleRegion({
+    required int mapId,
+  }) async {
+    final Map<String, dynamic> latLngBounds = (await _channel(mapId)
+        .invokeMapMethod<String, dynamic>('map#getVisibleRegion'))!;
+    final LatLng southwest = LatLng.fromJson(latLngBounds['southwest'])!;
+    final LatLng northeast = LatLng.fromJson(latLngBounds['northeast'])!;
+
+    return LatLngBounds(northeast: northeast, southwest: southwest);
+  }
+
+  @override
+  Future<ScreenCoordinate> getScreenCoordinate(
+    LatLng latLng, {
+    required int mapId,
+  }) async {
+    final Map<String, int> point = (await _channel(mapId)
+        .invokeMapMethod<String, int>(
+            'map#getScreenCoordinate', latLng.toJson()))!;
+
+    return ScreenCoordinate(x: point['x']!, y: point['y']!);
+  }
+
+  @override
+  Future<LatLng> getLatLng(
+    ScreenCoordinate screenCoordinate, {
+    required int mapId,
+  }) async {
+    final List<dynamic> latLng = (await _channel(mapId)
+        .invokeMethod<List<dynamic>>(
+            'map#getLatLng', screenCoordinate.toJson()))!;
+    return LatLng(latLng[0] as double, latLng[1] as double);
+  }
+
+  @override
+  Future<void> showMarkerInfoWindow(
+    MarkerId markerId, {
+    required int mapId,
+  }) {
+    assert(markerId != null);
+    return _channel(mapId).invokeMethod<void>(
+        'markers#showInfoWindow', <String, String>{'markerId': markerId.value});
+  }
+
+  @override
+  Future<void> hideMarkerInfoWindow(
+    MarkerId markerId, {
+    required int mapId,
+  }) {
+    assert(markerId != null);
+    return _channel(mapId).invokeMethod<void>(
+        'markers#hideInfoWindow', <String, String>{'markerId': markerId.value});
+  }
+
+  @override
+  Future<bool> isMarkerInfoWindowShown(
+    MarkerId markerId, {
+    required int mapId,
+  }) async {
+    assert(markerId != null);
+    return (await _channel(mapId).invokeMethod<bool>(
+        'markers#isInfoWindowShown',
+        <String, String>{'markerId': markerId.value}))!;
+  }
+
+  @override
+  Future<double> getZoomLevel({
+    required int mapId,
+  }) async {
+    return (await _channel(mapId).invokeMethod<double>('map#getZoomLevel'))!;
+  }
+
+  @override
+  Future<Uint8List?> takeSnapshot({
+    required int mapId,
+  }) {
+    return _channel(mapId).invokeMethod<Uint8List>('map#takeSnapshot');
+  }
+
+  /// Set [GoogleMapsFlutterPlatform] to use [AndroidViewSurface] to build the Google Maps widget.
+  ///
+  /// This implementation uses hybrid composition to render the Google Maps
+  /// Widget on Android. This comes at the cost of some performance on Android
+  /// versions below 10. See
+  /// https://flutter.dev/docs/development/platform-integration/platform-views#performance for more
+  /// information.
+  ///
+  /// If set to true, the google map widget should be built with
+  /// [buildViewWithTextDirection] instead of [buildView].
+  ///
+  /// Defaults to false.
+  bool useAndroidViewSurface = false;
+
+  Widget _buildView(
+    int creationId,
+    PlatformViewCreatedCallback onPlatformViewCreated, {
+    required MapWidgetConfiguration widgetConfiguration,
+    MapObjects mapObjects = const MapObjects(),
+    Map<String, dynamic> mapOptions = const <String, dynamic>{},
+  }) {
+    final Map<String, dynamic> creationParams = <String, dynamic>{
+      'initialCameraPosition':
+          widgetConfiguration.initialCameraPosition.toMap(),
+      'options': mapOptions,
+      'markersToAdd': serializeMarkerSet(mapObjects.markers),
+      'polygonsToAdd': serializePolygonSet(mapObjects.polygons),
+      'polylinesToAdd': serializePolylineSet(mapObjects.polylines),
+      'circlesToAdd': serializeCircleSet(mapObjects.circles),
+      'tileOverlaysToAdd': serializeTileOverlaySet(mapObjects.tileOverlays),
+    };
+
+    const String viewType = 'plugins.flutter.dev/google_maps_android';
+    if (useAndroidViewSurface) {
+      return PlatformViewLink(
+        viewType: viewType,
+        surfaceFactory: (
+          BuildContext context,
+          PlatformViewController controller,
+        ) {
+          return AndroidViewSurface(
+            controller: controller as AndroidViewController,
+            gestureRecognizers: widgetConfiguration.gestureRecognizers,
+            hitTestBehavior: PlatformViewHitTestBehavior.opaque,
+          );
+        },
+        onCreatePlatformView: (PlatformViewCreationParams params) {
+          final SurfaceAndroidViewController controller =
+              PlatformViewsService.initSurfaceAndroidView(
+            id: params.id,
+            viewType: viewType,
+            layoutDirection: widgetConfiguration.textDirection,
+            creationParams: creationParams,
+            creationParamsCodec: const StandardMessageCodec(),
+            onFocus: () => params.onFocusChanged(true),
+          );
+          controller.addOnPlatformViewCreatedListener(
+            params.onPlatformViewCreated,
+          );
+          controller.addOnPlatformViewCreatedListener(
+            onPlatformViewCreated,
+          );
+
+          controller.create();
+          return controller;
+        },
+      );
+    } else {
+      return AndroidView(
+        viewType: viewType,
+        onPlatformViewCreated: onPlatformViewCreated,
+        gestureRecognizers: widgetConfiguration.gestureRecognizers,
+        creationParams: creationParams,
+        creationParamsCodec: const StandardMessageCodec(),
+      );
+    }
+  }
+
+  @override
+  Widget buildViewWithConfiguration(
+    int creationId,
+    PlatformViewCreatedCallback onPlatformViewCreated, {
+    required MapWidgetConfiguration widgetConfiguration,
+    MapConfiguration mapConfiguration = const MapConfiguration(),
+    MapObjects mapObjects = const MapObjects(),
+  }) {
+    return _buildView(
+      creationId,
+      onPlatformViewCreated,
+      widgetConfiguration: widgetConfiguration,
+      mapObjects: mapObjects,
+      mapOptions: _jsonForMapConfiguration(mapConfiguration),
+    );
+  }
+
+  @override
+  Widget buildViewWithTextDirection(
+    int creationId,
+    PlatformViewCreatedCallback onPlatformViewCreated, {
+    required CameraPosition initialCameraPosition,
+    required TextDirection textDirection,
+    Set<Marker> markers = const <Marker>{},
+    Set<Polygon> polygons = const <Polygon>{},
+    Set<Polyline> polylines = const <Polyline>{},
+    Set<Circle> circles = const <Circle>{},
+    Set<TileOverlay> tileOverlays = const <TileOverlay>{},
+    Set<Factory<OneSequenceGestureRecognizer>>? gestureRecognizers,
+    Map<String, dynamic> mapOptions = const <String, dynamic>{},
+  }) {
+    return _buildView(
+      creationId,
+      onPlatformViewCreated,
+      widgetConfiguration: MapWidgetConfiguration(
+          initialCameraPosition: initialCameraPosition,
+          textDirection: textDirection),
+      mapObjects: MapObjects(
+          markers: markers,
+          polygons: polygons,
+          polylines: polylines,
+          circles: circles,
+          tileOverlays: tileOverlays),
+      mapOptions: mapOptions,
+    );
+  }
+
+  @override
+  Widget buildView(
+    int creationId,
+    PlatformViewCreatedCallback onPlatformViewCreated, {
+    required CameraPosition initialCameraPosition,
+    Set<Marker> markers = const <Marker>{},
+    Set<Polygon> polygons = const <Polygon>{},
+    Set<Polyline> polylines = const <Polyline>{},
+    Set<Circle> circles = const <Circle>{},
+    Set<TileOverlay> tileOverlays = const <TileOverlay>{},
+    Set<Factory<OneSequenceGestureRecognizer>>? gestureRecognizers,
+    Map<String, dynamic> mapOptions = const <String, dynamic>{},
+  }) {
+    return buildViewWithTextDirection(
+      creationId,
+      onPlatformViewCreated,
+      initialCameraPosition: initialCameraPosition,
+      textDirection: TextDirection.ltr,
+      markers: markers,
+      polygons: polygons,
+      polylines: polylines,
+      circles: circles,
+      tileOverlays: tileOverlays,
+      gestureRecognizers: gestureRecognizers,
+      mapOptions: mapOptions,
+    );
+  }
+
+  @override
+  @visibleForTesting
+  void enableDebugInspection() {
+    GoogleMapsInspectorPlatform.instance =
+        GoogleMapsInspectorAndroid((int mapId) => _channel(mapId));
+  }
+}
+
+Map<String, Object> _jsonForMapConfiguration(MapConfiguration config) {
+  final EdgeInsets? padding = config.padding;
+  return <String, Object>{
+    if (config.compassEnabled != null) 'compassEnabled': config.compassEnabled!,
+    if (config.mapToolbarEnabled != null)
+      'mapToolbarEnabled': config.mapToolbarEnabled!,
+    if (config.cameraTargetBounds != null)
+      'cameraTargetBounds': config.cameraTargetBounds!.toJson(),
+    if (config.mapType != null) 'mapType': config.mapType!.index,
+    if (config.minMaxZoomPreference != null)
+      'minMaxZoomPreference': config.minMaxZoomPreference!.toJson(),
+    if (config.rotateGesturesEnabled != null)
+      'rotateGesturesEnabled': config.rotateGesturesEnabled!,
+    if (config.scrollGesturesEnabled != null)
+      'scrollGesturesEnabled': config.scrollGesturesEnabled!,
+    if (config.tiltGesturesEnabled != null)
+      'tiltGesturesEnabled': config.tiltGesturesEnabled!,
+    if (config.zoomControlsEnabled != null)
+      'zoomControlsEnabled': config.zoomControlsEnabled!,
+    if (config.zoomGesturesEnabled != null)
+      'zoomGesturesEnabled': config.zoomGesturesEnabled!,
+    if (config.liteModeEnabled != null)
+      'liteModeEnabled': config.liteModeEnabled!,
+    if (config.trackCameraPosition != null)
+      'trackCameraPosition': config.trackCameraPosition!,
+    if (config.myLocationEnabled != null)
+      'myLocationEnabled': config.myLocationEnabled!,
+    if (config.myLocationButtonEnabled != null)
+      'myLocationButtonEnabled': config.myLocationButtonEnabled!,
+    if (padding != null)
+      'padding': <double>[
+        padding.top,
+        padding.left,
+        padding.bottom,
+        padding.right,
+      ],
+    if (config.indoorViewEnabled != null)
+      'indoorEnabled': config.indoorViewEnabled!,
+    if (config.trafficEnabled != null) 'trafficEnabled': config.trafficEnabled!,
+    if (config.buildingsEnabled != null)
+      'buildingsEnabled': config.buildingsEnabled!,
+  };
+}
+
+/// Update specification for a set of [TileOverlay]s.
+// TODO(stuartmorgan): Fix the missing export of this class in the platform
+// interface, and remove this copy.
+class _TileOverlayUpdates extends MapsObjectUpdates<TileOverlay> {
+  /// Computes [TileOverlayUpdates] given previous and current [TileOverlay]s.
+  _TileOverlayUpdates.from(Set<TileOverlay> previous, Set<TileOverlay> current)
+      : super.from(previous, current, objectName: 'tileOverlay');
+
+  /// Set of TileOverlays to be added in this update.
+  Set<TileOverlay> get tileOverlaysToAdd => objectsToAdd;
+
+  /// Set of TileOverlayIds to be removed in this update.
+  Set<TileOverlayId> get tileOverlayIdsToRemove =>
+      objectIdsToRemove.cast<TileOverlayId>();
+
+  /// Set of TileOverlays to be changed in this update.
+  Set<TileOverlay> get tileOverlaysToChange => objectsToChange;
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_android/pubspec.yaml
new file mode 100644
index 0000000..a0417d5
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/pubspec.yaml
@@ -0,0 +1,31 @@
+name: google_maps_flutter_android
+description: Android implementation of the google_maps_flutter plugin.
+repository: https://github.com/flutter/plugins/tree/main/packages/google_maps_flutter/google_maps_flutter_android
+issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+maps%22
+version: 2.1.10
+
+environment:
+  sdk: ">=2.14.0 <3.0.0"
+  flutter: ">=2.8.0"
+
+flutter:
+  plugin:
+    implements: google_maps_flutter
+    platforms:
+      android:
+        package: io.flutter.plugins.googlemaps
+        pluginClass: GoogleMapsPlugin
+        dartPluginClass: GoogleMapsFlutterAndroid
+
+dependencies:
+  flutter:
+    sdk: flutter
+  flutter_plugin_android_lifecycle: ^2.0.1
+  google_maps_flutter_platform_interface: ^2.2.1
+  stream_transform: ^2.0.0
+
+dev_dependencies:
+  async: ^2.5.0
+  flutter_test:
+    sdk: flutter
+  plugin_platform_interface: ^2.0.0
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/test/google_maps_flutter_android_test.dart b/packages/google_maps_flutter/google_maps_flutter_android/test/google_maps_flutter_android_test.dart
new file mode 100644
index 0000000..cba23d0
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/test/google_maps_flutter_android_test.dart
@@ -0,0 +1,153 @@
+// 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 'package:async/async.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter/widgets.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:google_maps_flutter_android/google_maps_flutter_android.dart';
+import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
+
+void main() {
+  TestWidgetsFlutterBinding.ensureInitialized();
+
+  late List<String> log;
+
+  setUp(() async {
+    log = <String>[];
+  });
+
+  /// Initializes a map with the given ID and canned responses, logging all
+  /// calls to [log].
+  void configureMockMap(
+    GoogleMapsFlutterAndroid maps, {
+    required int mapId,
+    required Future<dynamic>? Function(MethodCall call) handler,
+  }) {
+    maps
+        .ensureChannelInitialized(mapId)
+        .setMockMethodCallHandler((MethodCall methodCall) {
+      log.add(methodCall.method);
+      return handler(methodCall);
+    });
+  }
+
+  Future<void> sendPlatformMessage(
+      int mapId, String method, Map<dynamic, dynamic> data) async {
+    final ByteData byteData =
+        const StandardMethodCodec().encodeMethodCall(MethodCall(method, data));
+    await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger
+        .handlePlatformMessage('plugins.flutter.dev/google_maps_android_$mapId',
+            byteData, (ByteData? data) {});
+  }
+
+  test('registers instance', () async {
+    GoogleMapsFlutterAndroid.registerWith();
+    expect(GoogleMapsFlutterPlatform.instance, isA<GoogleMapsFlutterAndroid>());
+  });
+
+  // Calls each method that uses invokeMethod with a return type other than
+  // void to ensure that the casting/nullability handling succeeds.
+  //
+  // TODO(stuartmorgan): Remove this once there is real test coverage of
+  // each method, since that would cover this issue.
+  test('non-void invokeMethods handle types correctly', () async {
+    const int mapId = 0;
+    final GoogleMapsFlutterAndroid maps = GoogleMapsFlutterAndroid();
+    configureMockMap(maps, mapId: mapId,
+        handler: (MethodCall methodCall) async {
+      switch (methodCall.method) {
+        case 'map#getLatLng':
+          return <dynamic>[1.0, 2.0];
+        case 'markers#isInfoWindowShown':
+          return true;
+        case 'map#getZoomLevel':
+          return 2.5;
+        case 'map#takeSnapshot':
+          return null;
+      }
+    });
+
+    await maps.getLatLng(const ScreenCoordinate(x: 0, y: 0), mapId: mapId);
+    await maps.isMarkerInfoWindowShown(const MarkerId(''), mapId: mapId);
+    await maps.getZoomLevel(mapId: mapId);
+    await maps.takeSnapshot(mapId: mapId);
+    // Check that all the invokeMethod calls happened.
+    expect(log, <String>[
+      'map#getLatLng',
+      'markers#isInfoWindowShown',
+      'map#getZoomLevel',
+      'map#takeSnapshot',
+    ]);
+  });
+
+  test('markers send drag event to correct streams', () async {
+    const int mapId = 1;
+    final Map<dynamic, dynamic> jsonMarkerDragStartEvent = <dynamic, dynamic>{
+      'mapId': mapId,
+      'markerId': 'drag-start-marker',
+      'position': <double>[1.0, 1.0]
+    };
+    final Map<dynamic, dynamic> jsonMarkerDragEvent = <dynamic, dynamic>{
+      'mapId': mapId,
+      'markerId': 'drag-marker',
+      'position': <double>[1.0, 1.0]
+    };
+    final Map<dynamic, dynamic> jsonMarkerDragEndEvent = <dynamic, dynamic>{
+      'mapId': mapId,
+      'markerId': 'drag-end-marker',
+      'position': <double>[1.0, 1.0]
+    };
+
+    final GoogleMapsFlutterAndroid maps = GoogleMapsFlutterAndroid();
+    maps.ensureChannelInitialized(mapId);
+
+    final StreamQueue<MarkerDragStartEvent> markerDragStartStream =
+        StreamQueue<MarkerDragStartEvent>(maps.onMarkerDragStart(mapId: mapId));
+    final StreamQueue<MarkerDragEvent> markerDragStream =
+        StreamQueue<MarkerDragEvent>(maps.onMarkerDrag(mapId: mapId));
+    final StreamQueue<MarkerDragEndEvent> markerDragEndStream =
+        StreamQueue<MarkerDragEndEvent>(maps.onMarkerDragEnd(mapId: mapId));
+
+    await sendPlatformMessage(
+        mapId, 'marker#onDragStart', jsonMarkerDragStartEvent);
+    await sendPlatformMessage(mapId, 'marker#onDrag', jsonMarkerDragEvent);
+    await sendPlatformMessage(
+        mapId, 'marker#onDragEnd', jsonMarkerDragEndEvent);
+
+    expect((await markerDragStartStream.next).value.value,
+        equals('drag-start-marker'));
+    expect((await markerDragStream.next).value.value, equals('drag-marker'));
+    expect((await markerDragEndStream.next).value.value,
+        equals('drag-end-marker'));
+  });
+
+  test(
+    'Default widget is AndroidView',
+    () async {
+      final GoogleMapsFlutterAndroid maps = GoogleMapsFlutterAndroid();
+      final Widget widget = maps.buildViewWithConfiguration(1, (int _) {},
+          widgetConfiguration: const MapWidgetConfiguration(
+              initialCameraPosition:
+                  CameraPosition(target: LatLng(0, 0), zoom: 1),
+              textDirection: TextDirection.ltr));
+
+      expect(widget, isA<AndroidView>());
+    },
+  );
+
+  testWidgets('Use PlatformViewLink when using surface view',
+      (WidgetTester tester) async {
+    final GoogleMapsFlutterAndroid maps = GoogleMapsFlutterAndroid();
+    maps.useAndroidViewSurface = true;
+
+    final Widget widget = maps.buildViewWithConfiguration(1, (int _) {},
+        widgetConfiguration: const MapWidgetConfiguration(
+            initialCameraPosition:
+                CameraPosition(target: LatLng(0, 0), zoom: 1),
+            textDirection: TextDirection.ltr));
+
+    expect(widget, isA<PlatformViewLink>());
+  });
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/AUTHORS b/packages/google_maps_flutter/google_maps_flutter_ios/AUTHORS
new file mode 100644
index 0000000..9f1b53e
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/AUTHORS
@@ -0,0 +1,67 @@
+# Below is a list of people and organizations that have contributed
+# to the Flutter project. Names should be added to the list like so:
+#
+#   Name/Organization <email address>
+
+Google Inc.
+The Chromium Authors
+German Saprykin <saprykin.h@gmail.com>
+Benjamin Sauer <sauer.benjamin@gmail.com>
+larsenthomasj@gmail.com
+Ali Bitek <alibitek@protonmail.ch>
+Pol Batlló <pol.batllo@gmail.com>
+Anatoly Pulyaevskiy
+Hayden Flinner <haydenflinner@gmail.com>
+Stefano Rodriguez <hlsroddy@gmail.com>
+Salvatore Giordano <salvatoregiordanoo@gmail.com>
+Brian Armstrong <brian@flutter.institute>
+Paul DeMarco <paulmdemarco@gmail.com>
+Fabricio Nogueira <feufeu@gmail.com>
+Simon Lightfoot <simon@devangels.london>
+Ashton Thomas <ashton@acrinta.com>
+Thomas Danner <thmsdnnr@gmail.com>
+Diego Velásquez <diego.velasquez.lopez@gmail.com>
+Hajime Nakamura <nkmrhj@gmail.com>
+Tuyển Vũ Xuân <netsoft1985@gmail.com>
+Miguel Ruivo <miguel@miguelruivo.com>
+Sarthak Verma <sarthak@artiosys.com>
+Mike Diarmid <mike@invertase.io>
+Invertase <oss@invertase.io>
+Elliot Hesp <elliot@invertase.io>
+Vince Varga <vince.varga@smaho.com>
+Aawaz Gyawali <awazgyawali@gmail.com>
+EUI Limited <ian.evans3@admiralgroup.co.uk>
+Katarina Sheremet <katarina@sheremet.ch>
+Thomas Stockx <thomas@stockxit.com>
+Sarbagya Dhaubanjar <sarbagyastha@gmail.com>
+Ozkan Eksi <ozeksi@gmail.com>
+Rishab Nayak <rishab@bu.edu>
+ko2ic <ko2ic.dev@gmail.com>
+Jonathan Younger <jonathan@daikini.com>
+Jose Sanchez <josesm82@gmail.com>
+Debkanchan Samadder <debu.samadder@gmail.com>
+Audrius Karosevicius <audrius.karosevicius@gmail.com>
+Lukasz Piliszczuk <lukasz@intheloup.io>
+SoundReply Solutions GmbH <ch@soundreply.com>
+Rafal Wachol <rwachol@gmail.com>
+Pau Picas <pau.picas@gmail.com>
+Christian Weder <chrstian.weder@yapeal.ch>
+Alexandru Tuca <salexandru.tuca@outlook.com>
+Christian Weder <chrstian.weder@yapeal.ch>
+Rhodes Davis Jr. <rody.davis.jr@gmail.com>
+Luigi Agosti <luigi@tengio.com>
+Quentin Le Guennec <quentin@tengio.com>
+Koushik Ravikumar <koushik@tengio.com>
+Nissim Dsilva <nissim@tengio.com>
+Giancarlo Rocha <giancarloiff@gmail.com>
+Ryo Miyake <ryo@miyake.id>
+Théo Champion <contact.theochampion@gmail.com>
+Kazuki Yamaguchi <y.kazuki0614n@gmail.com>
+Eitan Schwartz <eshvartz@gmail.com>
+Chris Rutkowski <chrisrutkowski89@gmail.com>
+Juan Alvarez <juan.alvarez@resideo.com>
+Aleksandr Yurkovskiy <sanekyy@gmail.com>
+Anton Borries <mail@antonborri.es>
+Alex Li <google@alexv525.com>
+Rahul Raj <64.rahulraj@gmail.com>
+Taha Tesser <tesser@gmail.com>
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter_ios/CHANGELOG.md
new file mode 100644
index 0000000..e4b243a
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/CHANGELOG.md
@@ -0,0 +1,4 @@
+## 2.1.10
+
+* Splits iOS implementation out of `google_maps_flutter` as a federated
+  implementation.
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/LICENSE b/packages/google_maps_flutter/google_maps_flutter_ios/LICENSE
new file mode 100644
index 0000000..c6823b8
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/LICENSE
@@ -0,0 +1,25 @@
+Copyright 2013 The Flutter Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+    * Neither the name of Google Inc. nor the names of its
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/README.md b/packages/google_maps_flutter/google_maps_flutter_ios/README.md
new file mode 100644
index 0000000..cd5d3f1
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/README.md
@@ -0,0 +1,12 @@
+# google\_maps\_flutter\_ios
+
+The iOS implementation of [`google_maps_flutter`][1].
+
+## Usage
+
+This package is [endorsed][2], which means you can simply use
+`google_maps_flutter` normally. This package will be automatically included in
+your app when you do.
+
+[1]: https://pub.dev/packages/google_maps_flutter
+[2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/.metadata b/packages/google_maps_flutter/google_maps_flutter_ios/example/.metadata
new file mode 100644
index 0000000..46e884c
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/.metadata
@@ -0,0 +1,8 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled and should not be manually edited.
+
+version:
+  revision: 3ea4d06340a97a1e9d7cae97567c64e0569dcaa2
+  channel: beta
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/README.md b/packages/google_maps_flutter/google_maps_flutter_ios/example/README.md
new file mode 100644
index 0000000..c885264
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/README.md
@@ -0,0 +1,3 @@
+# google_maps_flutter_example
+
+Demonstrates how to use the google_maps_flutter plugin.
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/assets/2.0x/red_square.png b/packages/google_maps_flutter/google_maps_flutter_ios/example/assets/2.0x/red_square.png
new file mode 100644
index 0000000..0f82237
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/assets/2.0x/red_square.png
Binary files differ
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/assets/3.0x/red_square.png b/packages/google_maps_flutter/google_maps_flutter_ios/example/assets/3.0x/red_square.png
new file mode 100644
index 0000000..7e27399
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/assets/3.0x/red_square.png
Binary files differ
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/assets/night_mode.json b/packages/google_maps_flutter/google_maps_flutter_ios/example/assets/night_mode.json
new file mode 100644
index 0000000..1f16e00
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/assets/night_mode.json
@@ -0,0 +1,162 @@
+[
+  {
+    "elementType": "geometry",
+    "stylers": [
+      {
+        "color": "#242f3e"
+      }
+    ]
+  },
+  {
+    "elementType": "labels.text.fill",
+    "stylers": [
+      {
+        "color": "#746855"
+      }
+    ]
+  },
+  {
+    "elementType": "labels.text.stroke",
+    "stylers": [
+      {
+        "color": "#242f3e"
+      }
+    ]
+  },
+  {
+    "featureType": "administrative.locality",
+    "elementType": "labels.text.fill",
+    "stylers": [
+      {
+        "color": "#d59563"
+      }
+    ]
+  },
+  {
+    "featureType": "poi",
+    "elementType": "labels.text.fill",
+    "stylers": [
+      {
+        "color": "#d59563"
+      }
+    ]
+  },
+  {
+    "featureType": "poi.park",
+    "elementType": "geometry",
+    "stylers": [
+      {
+        "color": "#263c3f"
+      }
+    ]
+  },
+  {
+    "featureType": "poi.park",
+    "elementType": "labels.text.fill",
+    "stylers": [
+      {
+        "color": "#6b9a76"
+      }
+    ]
+  },
+  {
+    "featureType": "road",
+    "elementType": "geometry",
+    "stylers": [
+      {
+        "color": "#38414e"
+      }
+    ]
+  },
+  {
+    "featureType": "road",
+    "elementType": "geometry.stroke",
+    "stylers": [
+      {
+        "color": "#212a37"
+      }
+    ]
+  },
+  {
+    "featureType": "road",
+    "elementType": "labels.text.fill",
+    "stylers": [
+      {
+        "color": "#9ca5b3"
+      }
+    ]
+  },
+  {
+    "featureType": "road.highway",
+    "elementType": "geometry",
+    "stylers": [
+      {
+        "color": "#746855"
+      }
+    ]
+  },
+  {
+    "featureType": "road.highway",
+    "elementType": "geometry.stroke",
+    "stylers": [
+      {
+        "color": "#1f2835"
+      }
+    ]
+  },
+  {
+    "featureType": "road.highway",
+    "elementType": "labels.text.fill",
+    "stylers": [
+      {
+        "color": "#f3d19c"
+      }
+    ]
+  },
+  {
+    "featureType": "transit",
+    "elementType": "geometry",
+    "stylers": [
+      {
+        "color": "#2f3948"
+      }
+    ]
+  },
+  {
+    "featureType": "transit.station",
+    "elementType": "labels.text.fill",
+    "stylers": [
+      {
+        "color": "#d59563"
+      }
+    ]
+  },
+  {
+    "featureType": "water",
+    "elementType": "geometry",
+    "stylers": [
+      {
+        "color": "#17263c"
+      }
+    ]
+  },
+  {
+    "featureType": "water",
+    "elementType": "labels.text.fill",
+    "stylers": [
+      {
+        "color": "#515c6d"
+      }
+    ]
+  },
+  {
+    "featureType": "water",
+    "elementType": "labels.text.stroke",
+    "stylers": [
+      {
+        "color": "#17263c"
+      }
+    ]
+  }
+]
+
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/assets/red_square.png b/packages/google_maps_flutter/google_maps_flutter_ios/example/assets/red_square.png
new file mode 100644
index 0000000..650a2de
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/assets/red_square.png
Binary files differ
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/integration_test/google_maps_test.dart b/packages/google_maps_flutter/google_maps_flutter_ios/example/integration_test/google_maps_test.dart
new file mode 100644
index 0000000..a2299e6
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/integration_test/google_maps_test.dart
@@ -0,0 +1,1095 @@
+// 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 'dart:async';
+import 'dart:typed_data';
+import 'dart:ui' as ui;
+
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:google_maps_flutter_example/example_google_map.dart';
+import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
+import 'package:integration_test/integration_test.dart';
+
+const LatLng _kInitialMapCenter = LatLng(0, 0);
+const double _kInitialZoomLevel = 5;
+const CameraPosition _kInitialCameraPosition =
+    CameraPosition(target: _kInitialMapCenter, zoom: _kInitialZoomLevel);
+
+void main() {
+  IntegrationTestWidgetsFlutterBinding.ensureInitialized();
+  GoogleMapsFlutterPlatform.instance.enableDebugInspection();
+
+  testWidgets('testCompassToggle', (WidgetTester tester) async {
+    final Key key = GlobalKey();
+    final Completer<int> mapIdCompleter = Completer<int>();
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        compassEnabled: false,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          mapIdCompleter.complete(controller.mapId);
+        },
+      ),
+    ));
+
+    final int mapId = await mapIdCompleter.future;
+    final GoogleMapsInspectorPlatform inspector =
+        GoogleMapsInspectorPlatform.instance!;
+    bool compassEnabled = await inspector.isCompassEnabled(mapId: mapId);
+    expect(compassEnabled, false);
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        compassEnabled: true,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          fail('OnMapCreated should get called only once.');
+        },
+      ),
+    ));
+
+    compassEnabled = await inspector.isCompassEnabled(mapId: mapId);
+    expect(compassEnabled, true);
+  });
+
+  testWidgets('testMapToolbar returns false', (WidgetTester tester) async {
+    final Key key = GlobalKey();
+    final Completer<int> mapIdCompleter = Completer<int>();
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        mapToolbarEnabled: true,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          mapIdCompleter.complete(controller.mapId);
+        },
+      ),
+    ));
+
+    final int mapId = await mapIdCompleter.future;
+    final GoogleMapsInspectorPlatform inspector =
+        GoogleMapsInspectorPlatform.instance!;
+    final bool mapToolbarEnabled =
+        await inspector.isMapToolbarEnabled(mapId: mapId);
+    // This is only supported on Android, so should always return false.
+    expect(mapToolbarEnabled, false);
+  });
+
+  testWidgets('updateMinMaxZoomLevels', (WidgetTester tester) async {
+    final Key key = GlobalKey();
+    final Completer<ExampleGoogleMapController> controllerCompleter =
+        Completer<ExampleGoogleMapController>();
+
+    const MinMaxZoomPreference initialZoomLevel = MinMaxZoomPreference(4, 8);
+    const MinMaxZoomPreference finalZoomLevel = MinMaxZoomPreference(6, 10);
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        minMaxZoomPreference: initialZoomLevel,
+        onMapCreated: (ExampleGoogleMapController c) async {
+          controllerCompleter.complete(c);
+        },
+      ),
+    ));
+
+    final ExampleGoogleMapController controller =
+        await controllerCompleter.future;
+    final GoogleMapsInspectorPlatform inspector =
+        GoogleMapsInspectorPlatform.instance!;
+
+    MinMaxZoomPreference zoomLevel =
+        await inspector.getMinMaxZoomLevels(mapId: controller.mapId);
+    expect(zoomLevel, equals(initialZoomLevel));
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        minMaxZoomPreference: finalZoomLevel,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          fail('OnMapCreated should get called only once.');
+        },
+      ),
+    ));
+
+    zoomLevel = await inspector.getMinMaxZoomLevels(mapId: controller.mapId);
+    expect(zoomLevel, equals(finalZoomLevel));
+  });
+
+  testWidgets('testZoomGesturesEnabled', (WidgetTester tester) async {
+    final Key key = GlobalKey();
+    final Completer<int> mapIdCompleter = Completer<int>();
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        zoomGesturesEnabled: false,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          mapIdCompleter.complete(controller.mapId);
+        },
+      ),
+    ));
+
+    final int mapId = await mapIdCompleter.future;
+    final GoogleMapsInspectorPlatform inspector =
+        GoogleMapsInspectorPlatform.instance!;
+    bool zoomGesturesEnabled =
+        await inspector.areZoomGesturesEnabled(mapId: mapId);
+    expect(zoomGesturesEnabled, false);
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        zoomGesturesEnabled: true,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          fail('OnMapCreated should get called only once.');
+        },
+      ),
+    ));
+
+    zoomGesturesEnabled = await inspector.areZoomGesturesEnabled(mapId: mapId);
+    expect(zoomGesturesEnabled, true);
+  });
+
+  testWidgets('testZoomControlsEnabled', (WidgetTester tester) async {
+    final Key key = GlobalKey();
+    final Completer<int> mapIdCompleter = Completer<int>();
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          mapIdCompleter.complete(controller.mapId);
+        },
+      ),
+    ));
+
+    final int mapId = await mapIdCompleter.future;
+    final GoogleMapsInspectorPlatform inspector =
+        GoogleMapsInspectorPlatform.instance!;
+    final bool zoomControlsEnabled =
+        await inspector.areZoomControlsEnabled(mapId: mapId);
+
+    /// Zoom Controls functionality is not available on iOS at the moment.
+    expect(zoomControlsEnabled, false);
+  });
+
+  testWidgets('testRotateGesturesEnabled', (WidgetTester tester) async {
+    final Key key = GlobalKey();
+    final Completer<int> mapIdCompleter = Completer<int>();
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        rotateGesturesEnabled: false,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          mapIdCompleter.complete(controller.mapId);
+        },
+      ),
+    ));
+
+    final int mapId = await mapIdCompleter.future;
+    final GoogleMapsInspectorPlatform inspector =
+        GoogleMapsInspectorPlatform.instance!;
+    bool rotateGesturesEnabled =
+        await inspector.areRotateGesturesEnabled(mapId: mapId);
+    expect(rotateGesturesEnabled, false);
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        rotateGesturesEnabled: true,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          fail('OnMapCreated should get called only once.');
+        },
+      ),
+    ));
+
+    rotateGesturesEnabled =
+        await inspector.areRotateGesturesEnabled(mapId: mapId);
+    expect(rotateGesturesEnabled, true);
+  });
+
+  testWidgets('testTiltGesturesEnabled', (WidgetTester tester) async {
+    final Key key = GlobalKey();
+    final Completer<int> mapIdCompleter = Completer<int>();
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        tiltGesturesEnabled: false,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          mapIdCompleter.complete(controller.mapId);
+        },
+      ),
+    ));
+
+    final int mapId = await mapIdCompleter.future;
+    final GoogleMapsInspectorPlatform inspector =
+        GoogleMapsInspectorPlatform.instance!;
+    bool tiltGesturesEnabled =
+        await inspector.areTiltGesturesEnabled(mapId: mapId);
+    expect(tiltGesturesEnabled, false);
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        tiltGesturesEnabled: true,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          fail('OnMapCreated should get called only once.');
+        },
+      ),
+    ));
+
+    tiltGesturesEnabled = await inspector.areTiltGesturesEnabled(mapId: mapId);
+    expect(tiltGesturesEnabled, true);
+  });
+
+  testWidgets('testScrollGesturesEnabled', (WidgetTester tester) async {
+    final Key key = GlobalKey();
+    final Completer<int> mapIdCompleter = Completer<int>();
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        scrollGesturesEnabled: false,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          mapIdCompleter.complete(controller.mapId);
+        },
+      ),
+    ));
+
+    final int mapId = await mapIdCompleter.future;
+    final GoogleMapsInspectorPlatform inspector =
+        GoogleMapsInspectorPlatform.instance!;
+    bool scrollGesturesEnabled =
+        await inspector.areScrollGesturesEnabled(mapId: mapId);
+    expect(scrollGesturesEnabled, false);
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        scrollGesturesEnabled: true,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          fail('OnMapCreated should get called only once.');
+        },
+      ),
+    ));
+
+    scrollGesturesEnabled =
+        await inspector.areScrollGesturesEnabled(mapId: mapId);
+    expect(scrollGesturesEnabled, true);
+  });
+
+  testWidgets('testInitialCenterLocationAtCenter', (WidgetTester tester) async {
+    await tester.binding.setSurfaceSize(const Size(800, 600));
+
+    final Completer<ExampleGoogleMapController> mapControllerCompleter =
+        Completer<ExampleGoogleMapController>();
+    final Key key = GlobalKey();
+    await tester.pumpWidget(
+      Directionality(
+        textDirection: TextDirection.ltr,
+        child: ExampleGoogleMap(
+          key: key,
+          initialCameraPosition: _kInitialCameraPosition,
+          onMapCreated: (ExampleGoogleMapController controller) {
+            mapControllerCompleter.complete(controller);
+          },
+        ),
+      ),
+    );
+    final ExampleGoogleMapController mapController =
+        await mapControllerCompleter.future;
+
+    await tester.pumpAndSettle();
+
+    // TODO(cyanglaz): Remove this after we added `mapRendered` callback, and `mapControllerCompleter.complete(controller)` above should happen
+    // in `mapRendered`.
+    // https://github.com/flutter/flutter/issues/54758
+    await Future<void>.delayed(const Duration(seconds: 1));
+
+    final ScreenCoordinate coordinate =
+        await mapController.getScreenCoordinate(_kInitialCameraPosition.target);
+    final Rect rect = tester.getRect(find.byKey(key));
+    expect(coordinate.x, (rect.center.dx - rect.topLeft.dx).round());
+    expect(coordinate.y, (rect.center.dy - rect.topLeft.dy).round());
+
+    await tester.binding.setSurfaceSize(null);
+  });
+
+  testWidgets('testGetVisibleRegion', (WidgetTester tester) async {
+    final Key key = GlobalKey();
+    final LatLngBounds zeroLatLngBounds = LatLngBounds(
+        southwest: const LatLng(0, 0), northeast: const LatLng(0, 0));
+
+    final Completer<ExampleGoogleMapController> mapControllerCompleter =
+        Completer<ExampleGoogleMapController>();
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          mapControllerCompleter.complete(controller);
+        },
+      ),
+    ));
+    await tester.pumpAndSettle();
+
+    final ExampleGoogleMapController mapController =
+        await mapControllerCompleter.future;
+
+    final LatLngBounds firstVisibleRegion =
+        await mapController.getVisibleRegion();
+
+    expect(firstVisibleRegion, isNotNull);
+    expect(firstVisibleRegion.southwest, isNotNull);
+    expect(firstVisibleRegion.northeast, isNotNull);
+    expect(firstVisibleRegion, isNot(zeroLatLngBounds));
+    expect(firstVisibleRegion.contains(_kInitialMapCenter), isTrue);
+
+    // Making a new `LatLngBounds` about (10, 10) distance south west to the `firstVisibleRegion`.
+    // The size of the `LatLngBounds` is 10 by 10.
+    final LatLng southWest = LatLng(firstVisibleRegion.southwest.latitude - 20,
+        firstVisibleRegion.southwest.longitude - 20);
+    final LatLng northEast = LatLng(firstVisibleRegion.southwest.latitude - 10,
+        firstVisibleRegion.southwest.longitude - 10);
+    final LatLng newCenter = LatLng(
+      (northEast.latitude + southWest.latitude) / 2,
+      (northEast.longitude + southWest.longitude) / 2,
+    );
+
+    expect(firstVisibleRegion.contains(northEast), isFalse);
+    expect(firstVisibleRegion.contains(southWest), isFalse);
+
+    final LatLngBounds latLngBounds =
+        LatLngBounds(southwest: southWest, northeast: northEast);
+
+    // TODO(iskakaushik): non-zero padding is needed for some device configurations
+    // https://github.com/flutter/flutter/issues/30575
+    const double padding = 0;
+    await mapController
+        .moveCamera(CameraUpdate.newLatLngBounds(latLngBounds, padding));
+    await tester.pumpAndSettle(const Duration(seconds: 3));
+
+    final LatLngBounds secondVisibleRegion =
+        await mapController.getVisibleRegion();
+
+    expect(secondVisibleRegion, isNotNull);
+    expect(secondVisibleRegion.southwest, isNotNull);
+    expect(secondVisibleRegion.northeast, isNotNull);
+    expect(secondVisibleRegion, isNot(zeroLatLngBounds));
+
+    expect(firstVisibleRegion, isNot(secondVisibleRegion));
+    expect(secondVisibleRegion.contains(newCenter), isTrue);
+  });
+
+  testWidgets('testTraffic', (WidgetTester tester) async {
+    final Key key = GlobalKey();
+    final Completer<int> mapIdCompleter = Completer<int>();
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        trafficEnabled: true,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          mapIdCompleter.complete(controller.mapId);
+        },
+      ),
+    ));
+
+    final int mapId = await mapIdCompleter.future;
+    final GoogleMapsInspectorPlatform inspector =
+        GoogleMapsInspectorPlatform.instance!;
+    bool isTrafficEnabled = await inspector.isTrafficEnabled(mapId: mapId);
+    expect(isTrafficEnabled, true);
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        trafficEnabled: false,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          fail('OnMapCreated should get called only once.');
+        },
+      ),
+    ));
+
+    isTrafficEnabled = await inspector.isTrafficEnabled(mapId: mapId);
+    expect(isTrafficEnabled, false);
+  });
+
+  testWidgets('testBuildings', (WidgetTester tester) async {
+    final Key key = GlobalKey();
+    final Completer<int> mapIdCompleter = Completer<int>();
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        buildingsEnabled: true,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          mapIdCompleter.complete(controller.mapId);
+        },
+      ),
+    ));
+
+    final int mapId = await mapIdCompleter.future;
+    final GoogleMapsInspectorPlatform inspector =
+        GoogleMapsInspectorPlatform.instance!;
+    final bool isBuildingsEnabled =
+        await inspector.areBuildingsEnabled(mapId: mapId);
+    expect(isBuildingsEnabled, true);
+  });
+
+  testWidgets('testMyLocationButtonToggle', (WidgetTester tester) async {
+    final Key key = GlobalKey();
+    final Completer<int> mapIdCompleter = Completer<int>();
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        myLocationButtonEnabled: true,
+        myLocationEnabled: false,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          mapIdCompleter.complete(controller.mapId);
+        },
+      ),
+    ));
+
+    final int mapId = await mapIdCompleter.future;
+    final GoogleMapsInspectorPlatform inspector =
+        GoogleMapsInspectorPlatform.instance!;
+    bool myLocationButtonEnabled =
+        await inspector.isMyLocationButtonEnabled(mapId: mapId);
+    expect(myLocationButtonEnabled, true);
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        myLocationButtonEnabled: false,
+        myLocationEnabled: false,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          fail('OnMapCreated should get called only once.');
+        },
+      ),
+    ));
+
+    myLocationButtonEnabled =
+        await inspector.isMyLocationButtonEnabled(mapId: mapId);
+    expect(myLocationButtonEnabled, false);
+  });
+
+  testWidgets('testMyLocationButton initial value false',
+      (WidgetTester tester) async {
+    final Key key = GlobalKey();
+    final Completer<int> mapIdCompleter = Completer<int>();
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        myLocationButtonEnabled: false,
+        myLocationEnabled: false,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          mapIdCompleter.complete(controller.mapId);
+        },
+      ),
+    ));
+
+    final int mapId = await mapIdCompleter.future;
+    final GoogleMapsInspectorPlatform inspector =
+        GoogleMapsInspectorPlatform.instance!;
+    final bool myLocationButtonEnabled =
+        await inspector.isMyLocationButtonEnabled(mapId: mapId);
+    expect(myLocationButtonEnabled, false);
+  });
+
+  testWidgets('testMyLocationButton initial value true',
+      (WidgetTester tester) async {
+    final Key key = GlobalKey();
+    final Completer<int> mapIdCompleter = Completer<int>();
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        myLocationButtonEnabled: true,
+        myLocationEnabled: false,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          mapIdCompleter.complete(controller.mapId);
+        },
+      ),
+    ));
+
+    final int mapId = await mapIdCompleter.future;
+    final GoogleMapsInspectorPlatform inspector =
+        GoogleMapsInspectorPlatform.instance!;
+    final bool myLocationButtonEnabled =
+        await inspector.isMyLocationButtonEnabled(mapId: mapId);
+    expect(myLocationButtonEnabled, true);
+  });
+
+  testWidgets('testSetMapStyle valid Json String', (WidgetTester tester) async {
+    final Key key = GlobalKey();
+    final Completer<ExampleGoogleMapController> controllerCompleter =
+        Completer<ExampleGoogleMapController>();
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          controllerCompleter.complete(controller);
+        },
+      ),
+    ));
+
+    final ExampleGoogleMapController controller =
+        await controllerCompleter.future;
+    const String mapStyle =
+        '[{"elementType":"geometry","stylers":[{"color":"#242f3e"}]}]';
+    await controller.setMapStyle(mapStyle);
+  });
+
+  testWidgets('testSetMapStyle invalid Json String',
+      (WidgetTester tester) async {
+    final Key key = GlobalKey();
+    final Completer<ExampleGoogleMapController> controllerCompleter =
+        Completer<ExampleGoogleMapController>();
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          controllerCompleter.complete(controller);
+        },
+      ),
+    ));
+
+    final ExampleGoogleMapController controller =
+        await controllerCompleter.future;
+
+    try {
+      await controller.setMapStyle('invalid_value');
+      fail('expected MapStyleException');
+    } on MapStyleException catch (e) {
+      expect(e.cause, isNotNull);
+    }
+  });
+
+  testWidgets('testSetMapStyle null string', (WidgetTester tester) async {
+    final Key key = GlobalKey();
+    final Completer<ExampleGoogleMapController> controllerCompleter =
+        Completer<ExampleGoogleMapController>();
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          controllerCompleter.complete(controller);
+        },
+      ),
+    ));
+
+    final ExampleGoogleMapController controller =
+        await controllerCompleter.future;
+    await controller.setMapStyle(null);
+  });
+
+  testWidgets('testGetLatLng', (WidgetTester tester) async {
+    final Key key = GlobalKey();
+    final Completer<ExampleGoogleMapController> controllerCompleter =
+        Completer<ExampleGoogleMapController>();
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          controllerCompleter.complete(controller);
+        },
+      ),
+    ));
+
+    final ExampleGoogleMapController controller =
+        await controllerCompleter.future;
+
+    await tester.pumpAndSettle();
+    // TODO(cyanglaz): Remove this after we added `mapRendered` callback, and `mapControllerCompleter.complete(controller)` above should happen
+    // in `mapRendered`.
+    // https://github.com/flutter/flutter/issues/54758
+    await Future<void>.delayed(const Duration(seconds: 1));
+
+    final LatLngBounds visibleRegion = await controller.getVisibleRegion();
+    final LatLng topLeft =
+        await controller.getLatLng(const ScreenCoordinate(x: 0, y: 0));
+    final LatLng northWest = LatLng(
+      visibleRegion.northeast.latitude,
+      visibleRegion.southwest.longitude,
+    );
+
+    expect(topLeft, northWest);
+  });
+
+  testWidgets('testGetZoomLevel', (WidgetTester tester) async {
+    final Key key = GlobalKey();
+    final Completer<ExampleGoogleMapController> controllerCompleter =
+        Completer<ExampleGoogleMapController>();
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          controllerCompleter.complete(controller);
+        },
+      ),
+    ));
+
+    final ExampleGoogleMapController controller =
+        await controllerCompleter.future;
+
+    await tester.pumpAndSettle();
+    // TODO(cyanglaz): Remove this after we added `mapRendered` callback, and `mapControllerCompleter.complete(controller)` above should happen
+    // in `mapRendered`.
+    // https://github.com/flutter/flutter/issues/54758
+    await Future<void>.delayed(const Duration(seconds: 1));
+
+    double zoom = await controller.getZoomLevel();
+    expect(zoom, _kInitialZoomLevel);
+
+    await controller.moveCamera(CameraUpdate.zoomTo(7));
+    await tester.pumpAndSettle();
+    zoom = await controller.getZoomLevel();
+    expect(zoom, equals(7));
+  });
+
+  testWidgets('testScreenCoordinate', (WidgetTester tester) async {
+    final Key key = GlobalKey();
+    final Completer<ExampleGoogleMapController> controllerCompleter =
+        Completer<ExampleGoogleMapController>();
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        key: key,
+        initialCameraPosition: _kInitialCameraPosition,
+        onMapCreated: (ExampleGoogleMapController controller) {
+          controllerCompleter.complete(controller);
+        },
+      ),
+    ));
+    final ExampleGoogleMapController controller =
+        await controllerCompleter.future;
+
+    await tester.pumpAndSettle();
+    // TODO(cyanglaz): Remove this after we added `mapRendered` callback, and `mapControllerCompleter.complete(controller)` above should happen
+    // in `mapRendered`.
+    // https://github.com/flutter/flutter/issues/54758
+    await Future<void>.delayed(const Duration(seconds: 1));
+
+    final LatLngBounds visibleRegion = await controller.getVisibleRegion();
+    final LatLng northWest = LatLng(
+      visibleRegion.northeast.latitude,
+      visibleRegion.southwest.longitude,
+    );
+    final ScreenCoordinate topLeft =
+        await controller.getScreenCoordinate(northWest);
+    expect(topLeft, const ScreenCoordinate(x: 0, y: 0));
+  });
+
+  testWidgets('testResizeWidget', (WidgetTester tester) async {
+    final Completer<ExampleGoogleMapController> controllerCompleter =
+        Completer<ExampleGoogleMapController>();
+    final ExampleGoogleMap map = ExampleGoogleMap(
+      initialCameraPosition: _kInitialCameraPosition,
+      onMapCreated: (ExampleGoogleMapController controller) async {
+        controllerCompleter.complete(controller);
+      },
+    );
+    await tester.pumpWidget(Directionality(
+        textDirection: TextDirection.ltr,
+        child: MaterialApp(
+            home: Scaffold(
+                body: SizedBox(height: 100, width: 100, child: map)))));
+    final ExampleGoogleMapController controller =
+        await controllerCompleter.future;
+
+    await tester.pumpWidget(Directionality(
+        textDirection: TextDirection.ltr,
+        child: MaterialApp(
+            home: Scaffold(
+                body: SizedBox(height: 400, width: 400, child: map)))));
+
+    await tester.pumpAndSettle();
+    // TODO(cyanglaz): Remove this after we added `mapRendered` callback, and `mapControllerCompleter.complete(controller)` above should happen
+    // in `mapRendered`.
+    // https://github.com/flutter/flutter/issues/54758
+    await Future<void>.delayed(const Duration(seconds: 1));
+
+    // Simple call to make sure that the app hasn't crashed.
+    final LatLngBounds bounds1 = await controller.getVisibleRegion();
+    final LatLngBounds bounds2 = await controller.getVisibleRegion();
+    expect(bounds1, bounds2);
+  });
+
+  testWidgets('testToggleInfoWindow', (WidgetTester tester) async {
+    const Marker marker = Marker(
+        markerId: MarkerId('marker'),
+        infoWindow: InfoWindow(title: 'InfoWindow'));
+    final Set<Marker> markers = <Marker>{marker};
+
+    final Completer<ExampleGoogleMapController> controllerCompleter =
+        Completer<ExampleGoogleMapController>();
+
+    await tester.pumpWidget(Directionality(
+      textDirection: TextDirection.ltr,
+      child: ExampleGoogleMap(
+        initialCameraPosition: const CameraPosition(target: LatLng(10.0, 15.0)),
+        markers: markers,
+        onMapCreated: (ExampleGoogleMapController googleMapController) {
+          controllerCompleter.complete(googleMapController);
+        },
+      ),
+    ));
+
+    final ExampleGoogleMapController controller =
+        await controllerCompleter.future;
+
+    bool iwVisibleStatus =
+        await controller.isMarkerInfoWindowShown(marker.markerId);
+    expect(iwVisibleStatus, false);
+
+    await controller.showMarkerInfoWindow(marker.markerId);
+    iwVisibleStatus = await controller.isMarkerInfoWindowShown(marker.markerId);
+    expect(iwVisibleStatus, true);
+
+    await controller.hideMarkerInfoWindow(marker.markerId);
+    iwVisibleStatus = await controller.isMarkerInfoWindowShown(marker.markerId);
+    expect(iwVisibleStatus, false);
+  });
+
+  testWidgets('fromAssetImage', (WidgetTester tester) async {
+    const double pixelRatio = 2;
+    const ImageConfiguration imageConfiguration =
+        ImageConfiguration(devicePixelRatio: pixelRatio);
+    final BitmapDescriptor mip = await BitmapDescriptor.fromAssetImage(
+        imageConfiguration, 'red_square.png');
+    final BitmapDescriptor scaled = await BitmapDescriptor.fromAssetImage(
+        imageConfiguration, 'red_square.png',
+        mipmaps: false);
+    expect((mip.toJson() as List<dynamic>)[2], 1);
+    expect((scaled.toJson() as List<dynamic>)[2], 2);
+  });
+
+  testWidgets('testTakeSnapshot', (WidgetTester tester) async {
+    final Completer<ExampleGoogleMapController> controllerCompleter =
+        Completer<ExampleGoogleMapController>();
+
+    await tester.pumpWidget(
+      Directionality(
+        textDirection: TextDirection.ltr,
+        child: ExampleGoogleMap(
+          initialCameraPosition: _kInitialCameraPosition,
+          onMapCreated: (ExampleGoogleMapController controller) {
+            controllerCompleter.complete(controller);
+          },
+        ),
+      ),
+    );
+
+    await tester.pumpAndSettle(const Duration(seconds: 3));
+
+    final ExampleGoogleMapController controller =
+        await controllerCompleter.future;
+    final Uint8List? bytes = await controller.takeSnapshot();
+    expect(bytes?.isNotEmpty, true);
+  });
+
+  testWidgets(
+    'set tileOverlay correctly',
+    (WidgetTester tester) async {
+      final Completer<int> mapIdCompleter = Completer<int>();
+      final TileOverlay tileOverlay1 = TileOverlay(
+        tileOverlayId: const TileOverlayId('tile_overlay_1'),
+        tileProvider: _DebugTileProvider(),
+        zIndex: 2,
+        visible: true,
+        transparency: 0.2,
+        fadeIn: true,
+      );
+
+      final TileOverlay tileOverlay2 = TileOverlay(
+        tileOverlayId: const TileOverlayId('tile_overlay_2'),
+        tileProvider: _DebugTileProvider(),
+        zIndex: 1,
+        visible: false,
+        transparency: 0.3,
+        fadeIn: false,
+      );
+      await tester.pumpWidget(
+        Directionality(
+          textDirection: TextDirection.ltr,
+          child: ExampleGoogleMap(
+            initialCameraPosition: _kInitialCameraPosition,
+            tileOverlays: <TileOverlay>{tileOverlay1, tileOverlay2},
+            onMapCreated: (ExampleGoogleMapController controller) {
+              mapIdCompleter.complete(controller.mapId);
+            },
+          ),
+        ),
+      );
+      await tester.pumpAndSettle(const Duration(seconds: 3));
+
+      final int mapId = await mapIdCompleter.future;
+      final GoogleMapsInspectorPlatform inspector =
+          GoogleMapsInspectorPlatform.instance!;
+
+      final TileOverlay tileOverlayInfo1 = (await inspector
+          .getTileOverlayInfo(tileOverlay1.mapsId, mapId: mapId))!;
+      final TileOverlay tileOverlayInfo2 = (await inspector
+          .getTileOverlayInfo(tileOverlay2.mapsId, mapId: mapId))!;
+
+      expect(tileOverlayInfo1.visible, isTrue);
+      expect(tileOverlayInfo1.fadeIn, isTrue);
+      expect(
+          tileOverlayInfo1.transparency, moreOrLessEquals(0.2, epsilon: 0.001));
+      expect(tileOverlayInfo1.zIndex, 2);
+
+      expect(tileOverlayInfo2.visible, isFalse);
+      expect(tileOverlayInfo2.fadeIn, isFalse);
+      expect(
+          tileOverlayInfo2.transparency, moreOrLessEquals(0.3, epsilon: 0.001));
+      expect(tileOverlayInfo2.zIndex, 1);
+    },
+  );
+
+  testWidgets(
+    'update tileOverlays correctly',
+    (WidgetTester tester) async {
+      final Completer<int> mapIdCompleter = Completer<int>();
+      final Key key = GlobalKey();
+      final TileOverlay tileOverlay1 = TileOverlay(
+        tileOverlayId: const TileOverlayId('tile_overlay_1'),
+        tileProvider: _DebugTileProvider(),
+        zIndex: 2,
+        visible: true,
+        transparency: 0.2,
+        fadeIn: true,
+      );
+
+      final TileOverlay tileOverlay2 = TileOverlay(
+        tileOverlayId: const TileOverlayId('tile_overlay_2'),
+        tileProvider: _DebugTileProvider(),
+        zIndex: 3,
+        visible: true,
+        transparency: 0.5,
+        fadeIn: true,
+      );
+      await tester.pumpWidget(
+        Directionality(
+          textDirection: TextDirection.ltr,
+          child: ExampleGoogleMap(
+            key: key,
+            initialCameraPosition: _kInitialCameraPosition,
+            tileOverlays: <TileOverlay>{tileOverlay1, tileOverlay2},
+            onMapCreated: (ExampleGoogleMapController controller) {
+              mapIdCompleter.complete(controller.mapId);
+            },
+          ),
+        ),
+      );
+
+      final int mapId = await mapIdCompleter.future;
+      final GoogleMapsInspectorPlatform inspector =
+          GoogleMapsInspectorPlatform.instance!;
+
+      final TileOverlay tileOverlay1New = TileOverlay(
+        tileOverlayId: const TileOverlayId('tile_overlay_1'),
+        tileProvider: _DebugTileProvider(),
+        zIndex: 1,
+        visible: false,
+        transparency: 0.3,
+        fadeIn: false,
+      );
+
+      await tester.pumpWidget(
+        Directionality(
+          textDirection: TextDirection.ltr,
+          child: ExampleGoogleMap(
+            key: key,
+            initialCameraPosition: _kInitialCameraPosition,
+            tileOverlays: <TileOverlay>{tileOverlay1New},
+            onMapCreated: (ExampleGoogleMapController controller) {
+              fail('update: OnMapCreated should get called only once.');
+            },
+          ),
+        ),
+      );
+
+      await tester.pumpAndSettle(const Duration(seconds: 3));
+
+      final TileOverlay tileOverlayInfo1 = (await inspector
+          .getTileOverlayInfo(tileOverlay1.mapsId, mapId: mapId))!;
+      final TileOverlay? tileOverlayInfo2 =
+          await inspector.getTileOverlayInfo(tileOverlay2.mapsId, mapId: mapId);
+
+      expect(tileOverlayInfo1.visible, isFalse);
+      expect(tileOverlayInfo1.fadeIn, isFalse);
+      expect(
+          tileOverlayInfo1.transparency, moreOrLessEquals(0.3, epsilon: 0.001));
+      expect(tileOverlayInfo1.zIndex, 1);
+
+      expect(tileOverlayInfo2, isNull);
+    },
+  );
+
+  testWidgets(
+    'remove tileOverlays correctly',
+    (WidgetTester tester) async {
+      final Completer<int> mapIdCompleter = Completer<int>();
+      final Key key = GlobalKey();
+      final TileOverlay tileOverlay1 = TileOverlay(
+        tileOverlayId: const TileOverlayId('tile_overlay_1'),
+        tileProvider: _DebugTileProvider(),
+        zIndex: 2,
+        visible: true,
+        transparency: 0.2,
+        fadeIn: true,
+      );
+
+      await tester.pumpWidget(
+        Directionality(
+          textDirection: TextDirection.ltr,
+          child: ExampleGoogleMap(
+            key: key,
+            initialCameraPosition: _kInitialCameraPosition,
+            tileOverlays: <TileOverlay>{tileOverlay1},
+            onMapCreated: (ExampleGoogleMapController controller) {
+              mapIdCompleter.complete(controller.mapId);
+            },
+          ),
+        ),
+      );
+
+      final int mapId = await mapIdCompleter.future;
+      final GoogleMapsInspectorPlatform inspector =
+          GoogleMapsInspectorPlatform.instance!;
+
+      await tester.pumpWidget(
+        Directionality(
+          textDirection: TextDirection.ltr,
+          child: ExampleGoogleMap(
+            key: key,
+            initialCameraPosition: _kInitialCameraPosition,
+            onMapCreated: (ExampleGoogleMapController controller) {
+              fail('OnMapCreated should get called only once.');
+            },
+          ),
+        ),
+      );
+
+      await tester.pumpAndSettle(const Duration(seconds: 3));
+      final TileOverlay? tileOverlayInfo1 =
+          await inspector.getTileOverlayInfo(tileOverlay1.mapsId, mapId: mapId);
+
+      expect(tileOverlayInfo1, isNull);
+    },
+  );
+}
+
+class _DebugTileProvider implements TileProvider {
+  _DebugTileProvider() {
+    boxPaint.isAntiAlias = true;
+    boxPaint.color = Colors.blue;
+    boxPaint.strokeWidth = 2.0;
+    boxPaint.style = PaintingStyle.stroke;
+  }
+
+  static const int width = 100;
+  static const int height = 100;
+  static final Paint boxPaint = Paint();
+  static const TextStyle textStyle = TextStyle(
+    color: Colors.red,
+    fontSize: 20,
+  );
+
+  @override
+  Future<Tile> getTile(int x, int y, int? zoom) async {
+    final ui.PictureRecorder recorder = ui.PictureRecorder();
+    final Canvas canvas = Canvas(recorder);
+    final TextSpan textSpan = TextSpan(
+      text: '$x,$y',
+      style: textStyle,
+    );
+    final TextPainter textPainter = TextPainter(
+      text: textSpan,
+      textDirection: TextDirection.ltr,
+    );
+    textPainter.layout(
+      minWidth: 0.0,
+      maxWidth: width.toDouble(),
+    );
+    const Offset offset = Offset(0, 0);
+    textPainter.paint(canvas, offset);
+    canvas.drawRect(
+        Rect.fromLTRB(0, 0, width.toDouble(), width.toDouble()), boxPaint);
+    final ui.Picture picture = recorder.endRecording();
+    final Uint8List byteData = await picture
+        .toImage(width, height)
+        .then((ui.Image image) =>
+            image.toByteData(format: ui.ImageByteFormat.png))
+        .then((ByteData? byteData) => byteData!.buffer.asUint8List());
+    return Tile(width, height, byteData);
+  }
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Flutter/AppFrameworkInfo.plist b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Flutter/AppFrameworkInfo.plist
new file mode 100644
index 0000000..3a9c234
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Flutter/AppFrameworkInfo.plist
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+  <key>CFBundleDevelopmentRegion</key>
+  <string>en</string>
+  <key>CFBundleExecutable</key>
+  <string>App</string>
+  <key>CFBundleIdentifier</key>
+  <string>io.flutter.flutter.app</string>
+  <key>CFBundleInfoDictionaryVersion</key>
+  <string>6.0</string>
+  <key>CFBundleName</key>
+  <string>App</string>
+  <key>CFBundlePackageType</key>
+  <string>FMWK</string>
+  <key>CFBundleShortVersionString</key>
+  <string>1.0</string>
+  <key>CFBundleSignature</key>
+  <string>????</string>
+  <key>CFBundleVersion</key>
+  <string>1.0</string>
+  <key>UIRequiredDeviceCapabilities</key>
+  <array>
+    <string>arm64</string>
+  </array>
+  <key>MinimumOSVersion</key>
+  <string>9.0</string>
+</dict>
+</plist>
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Flutter/Debug.xcconfig b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Flutter/Debug.xcconfig
new file mode 100644
index 0000000..e8efba1
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Flutter/Debug.xcconfig
@@ -0,0 +1,2 @@
+#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
+#include "Generated.xcconfig"
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Flutter/Release.xcconfig b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Flutter/Release.xcconfig
new file mode 100644
index 0000000..399e934
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Flutter/Release.xcconfig
@@ -0,0 +1,2 @@
+#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
+#include "Generated.xcconfig"
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Podfile b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Podfile
new file mode 100644
index 0000000..14b4bdc
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Podfile
@@ -0,0 +1,49 @@
+# Uncomment this line to define a global platform for your project
+# platform :ios, '9.0'
+
+# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
+ENV['COCOAPODS_DISABLE_STATS'] = 'true'
+
+project 'Runner', {
+  'Debug' => :debug,
+  'Profile' => :release,
+  'Release' => :release,
+}
+
+def flutter_root
+  generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
+  unless File.exist?(generated_xcode_build_settings_path)
+    raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
+  end
+
+  File.foreach(generated_xcode_build_settings_path) do |line|
+    matches = line.match(/FLUTTER_ROOT\=(.*)/)
+    return matches[1].strip if matches
+  end
+  raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
+end
+
+require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
+
+flutter_ios_podfile_setup
+
+target 'Runner' do
+  flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
+  target 'RunnerTests' do
+    inherit! :search_paths
+
+    pod 'OCMock', '~> 3.9.1'
+  end
+  target 'RunnerUITests' do
+    inherit! :search_paths
+  end
+end
+
+post_install do |installer|
+  installer.pods_project.targets.each do |target|
+    flutter_additional_ios_build_settings(target)
+    target.build_configurations.each do |build_configuration|
+      build_configuration.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = 'arm64 i386'
+    end
+  end
+end
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner.xcodeproj/project.pbxproj b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner.xcodeproj/project.pbxproj
new file mode 100644
index 0000000..343e050
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner.xcodeproj/project.pbxproj
@@ -0,0 +1,789 @@
+// !$*UTF8*$!
+{
+	archiveVersion = 1;
+	classes = {
+	};
+	objectVersion = 46;
+	objects = {
+
+/* Begin PBXBuildFile section */
+		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 */; };
+		4A097997B7B27CE82FFC3AB8 /* libPods-RunnerUITests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = DC8ED0578E8D540BBDA17645 /* libPods-RunnerUITests.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 */; };
+		97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
+		97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
+		982F2A6C27BADE17003C81F4 /* PartiallyMockedMapView.m in Sources */ = {isa = PBXBuildFile; fileRef = 982F2A6B27BADE17003C81F4 /* PartiallyMockedMapView.m */; };
+		F7151F13265D7ED70028CB91 /* GoogleMapsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = F7151F12265D7ED70028CB91 /* GoogleMapsTests.m */; };
+		F7151F21265D7EE50028CB91 /* GoogleMapsUITests.m in Sources */ = {isa = PBXBuildFile; fileRef = F7151F20265D7EE50028CB91 /* GoogleMapsUITests.m */; };
+		FC8F35FC8CD533B128950487 /* libPods-RunnerTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F267F68029D1A4E2E4C572A7 /* libPods-RunnerTests.a */; };
+/* End PBXBuildFile section */
+
+/* Begin PBXContainerItemProxy section */
+		F7151F15265D7ED70028CB91 /* PBXContainerItemProxy */ = {
+			isa = PBXContainerItemProxy;
+			containerPortal = 97C146E61CF9000F007C117D /* Project object */;
+			proxyType = 1;
+			remoteGlobalIDString = 97C146ED1CF9000F007C117D;
+			remoteInfo = Runner;
+		};
+		F7151F23265D7EE50028CB91 /* PBXContainerItemProxy */ = {
+			isa = PBXContainerItemProxy;
+			containerPortal = 97C146E61CF9000F007C117D /* Project object */;
+			proxyType = 1;
+			remoteGlobalIDString = 97C146ED1CF9000F007C117D;
+			remoteInfo = Runner;
+		};
+/* End PBXContainerItemProxy section */
+
+/* Begin PBXCopyFilesBuildPhase section */
+		9705A1C41CF9048500538489 /* Embed Frameworks */ = {
+			isa = PBXCopyFilesBuildPhase;
+			buildActionMask = 2147483647;
+			dstPath = "";
+			dstSubfolderSpec = 10;
+			files = (
+			);
+			name = "Embed Frameworks";
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXCopyFilesBuildPhase section */
+
+/* Begin PBXFileReference section */
+		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; };
+		6AC1E6095B09DE4B02ECF64E /* Pods-RunnerUITests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerUITests.release.xcconfig"; path = "Pods/Target Support Files/Pods-RunnerUITests/Pods-RunnerUITests.release.xcconfig"; sourceTree = "<group>"; };
+		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>"; };
+		7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
+		7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
+		9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
+		9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
+		97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
+		97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
+		97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
+		97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
+		97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
+		97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+		982F2A6A27BADE17003C81F4 /* PartiallyMockedMapView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PartiallyMockedMapView.h; sourceTree = "<group>"; };
+		982F2A6B27BADE17003C81F4 /* PartiallyMockedMapView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = PartiallyMockedMapView.m; sourceTree = "<group>"; };
+		B7AFC65E3DD5AC60D834D83D /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
+		DC8ED0578E8D540BBDA17645 /* libPods-RunnerUITests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerUITests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
+		DDDAC1342ABDF2F125577581 /* Pods-RunnerUITests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerUITests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-RunnerUITests/Pods-RunnerUITests.debug.xcconfig"; sourceTree = "<group>"; };
+		E52C6A6210A56F027C582EF9 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
+		EA0E91726245EDC22B97E8B9 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
+		F267F68029D1A4E2E4C572A7 /* libPods-RunnerTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-RunnerTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
+		F7151F10265D7ED70028CB91 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+		F7151F12265D7ED70028CB91 /* GoogleMapsTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GoogleMapsTests.m; sourceTree = "<group>"; };
+		F7151F14265D7ED70028CB91 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+		F7151F1E265D7EE50028CB91 /* RunnerUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
+		F7151F20265D7EE50028CB91 /* GoogleMapsUITests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = GoogleMapsUITests.m; sourceTree = "<group>"; };
+		F7151F22265D7EE50028CB91 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
+/* End PBXFileReference section */
+
+/* Begin PBXFrameworksBuildPhase section */
+		97C146EB1CF9000F007C117D /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				4510D964F3B1259FEDD3ABA6 /* libPods-Runner.a in Frameworks */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		F7151F0D265D7ED70028CB91 /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				68E4726A2836FF0C00BDDDAC /* MapKit.framework in Frameworks */,
+				FC8F35FC8CD533B128950487 /* libPods-RunnerTests.a in Frameworks */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		F7151F1B265D7EE50028CB91 /* Frameworks */ = {
+			isa = PBXFrameworksBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				4A097997B7B27CE82FFC3AB8 /* libPods-RunnerUITests.a in Frameworks */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXFrameworksBuildPhase section */
+
+/* Begin PBXGroup section */
+		1E7CF0857EFC88FC263CF3B2 /* Frameworks */ = {
+			isa = PBXGroup;
+			children = (
+				68E472692836FF0C00BDDDAC /* MapKit.framework */,
+				7755F8F4BABC3D6A0BD4048B /* libPods-Runner.a */,
+				F267F68029D1A4E2E4C572A7 /* libPods-RunnerTests.a */,
+				DC8ED0578E8D540BBDA17645 /* libPods-RunnerUITests.a */,
+			);
+			name = Frameworks;
+			sourceTree = "<group>";
+		};
+		9740EEB11CF90186004384FC /* Flutter */ = {
+			isa = PBXGroup;
+			children = (
+				3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
+				9740EEB21CF90195004384FC /* Debug.xcconfig */,
+				7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
+				9740EEB31CF90195004384FC /* Generated.xcconfig */,
+			);
+			name = Flutter;
+			sourceTree = "<group>";
+		};
+		97C146E51CF9000F007C117D = {
+			isa = PBXGroup;
+			children = (
+				9740EEB11CF90186004384FC /* Flutter */,
+				97C146F01CF9000F007C117D /* Runner */,
+				F7151F11265D7ED70028CB91 /* RunnerTests */,
+				F7151F1F265D7EE50028CB91 /* RunnerUITests */,
+				97C146EF1CF9000F007C117D /* Products */,
+				A189CFE5474BF8A07908B2E0 /* Pods */,
+				1E7CF0857EFC88FC263CF3B2 /* Frameworks */,
+			);
+			sourceTree = "<group>";
+		};
+		97C146EF1CF9000F007C117D /* Products */ = {
+			isa = PBXGroup;
+			children = (
+				97C146EE1CF9000F007C117D /* Runner.app */,
+				F7151F10265D7ED70028CB91 /* RunnerTests.xctest */,
+				F7151F1E265D7EE50028CB91 /* RunnerUITests.xctest */,
+			);
+			name = Products;
+			sourceTree = "<group>";
+		};
+		97C146F01CF9000F007C117D /* Runner */ = {
+			isa = PBXGroup;
+			children = (
+				7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */,
+				7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */,
+				97C146FA1CF9000F007C117D /* Main.storyboard */,
+				97C146FD1CF9000F007C117D /* Assets.xcassets */,
+				97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
+				97C147021CF9000F007C117D /* Info.plist */,
+				97C146F11CF9000F007C117D /* Supporting Files */,
+				1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
+				1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
+			);
+			path = Runner;
+			sourceTree = "<group>";
+		};
+		97C146F11CF9000F007C117D /* Supporting Files */ = {
+			isa = PBXGroup;
+			children = (
+				97C146F21CF9000F007C117D /* main.m */,
+			);
+			name = "Supporting Files";
+			sourceTree = "<group>";
+		};
+		A189CFE5474BF8A07908B2E0 /* Pods */ = {
+			isa = PBXGroup;
+			children = (
+				B7AFC65E3DD5AC60D834D83D /* Pods-Runner.debug.xcconfig */,
+				EA0E91726245EDC22B97E8B9 /* Pods-Runner.release.xcconfig */,
+				E52C6A6210A56F027C582EF9 /* Pods-RunnerTests.debug.xcconfig */,
+				733AFAB37683A9DA7512F09C /* Pods-RunnerTests.release.xcconfig */,
+				DDDAC1342ABDF2F125577581 /* Pods-RunnerUITests.debug.xcconfig */,
+				6AC1E6095B09DE4B02ECF64E /* Pods-RunnerUITests.release.xcconfig */,
+			);
+			name = Pods;
+			sourceTree = "<group>";
+		};
+		F7151F11265D7ED70028CB91 /* RunnerTests */ = {
+			isa = PBXGroup;
+			children = (
+				6851F3552835BC180032B7C8 /* FLTGoogleMapJSONConversionsConversionTests.m */,
+				F7151F12265D7ED70028CB91 /* GoogleMapsTests.m */,
+				982F2A6A27BADE17003C81F4 /* PartiallyMockedMapView.h */,
+				982F2A6B27BADE17003C81F4 /* PartiallyMockedMapView.m */,
+				F7151F14265D7ED70028CB91 /* Info.plist */,
+			);
+			path = RunnerTests;
+			sourceTree = "<group>";
+		};
+		F7151F1F265D7EE50028CB91 /* RunnerUITests */ = {
+			isa = PBXGroup;
+			children = (
+				F7151F20265D7EE50028CB91 /* GoogleMapsUITests.m */,
+				F7151F22265D7EE50028CB91 /* Info.plist */,
+			);
+			path = RunnerUITests;
+			sourceTree = "<group>";
+		};
+/* End PBXGroup section */
+
+/* Begin PBXNativeTarget section */
+		97C146ED1CF9000F007C117D /* Runner */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
+			buildPhases = (
+				74BF216DF17B0C7F983459BD /* [CP] Check Pods Manifest.lock */,
+				9740EEB61CF901F6004384FC /* Run Script */,
+				97C146EA1CF9000F007C117D /* Sources */,
+				97C146EB1CF9000F007C117D /* Frameworks */,
+				97C146EC1CF9000F007C117D /* Resources */,
+				9705A1C41CF9048500538489 /* Embed Frameworks */,
+				3B06AD1E1E4923F5004D2608 /* Thin Binary */,
+				BB6BD9A1101E970BEF85B6D2 /* [CP] Copy Pods Resources */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+			);
+			name = Runner;
+			productName = Runner;
+			productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
+			productType = "com.apple.product-type.application";
+		};
+		F7151F0F265D7ED70028CB91 /* RunnerTests */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = F7151F19265D7ED70028CB91 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
+			buildPhases = (
+				D067548A17DC238B80D2BD12 /* [CP] Check Pods Manifest.lock */,
+				F7151F0C265D7ED70028CB91 /* Sources */,
+				F7151F0D265D7ED70028CB91 /* Frameworks */,
+				F7151F0E265D7ED70028CB91 /* Resources */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+				F7151F16265D7ED70028CB91 /* PBXTargetDependency */,
+			);
+			name = RunnerTests;
+			productName = RunnerTests;
+			productReference = F7151F10265D7ED70028CB91 /* RunnerTests.xctest */;
+			productType = "com.apple.product-type.bundle.unit-test";
+		};
+		F7151F1D265D7EE50028CB91 /* RunnerUITests */ = {
+			isa = PBXNativeTarget;
+			buildConfigurationList = F7151F25265D7EE50028CB91 /* Build configuration list for PBXNativeTarget "RunnerUITests" */;
+			buildPhases = (
+				BD39F60794E9A0264D5D3752 /* [CP] Check Pods Manifest.lock */,
+				F7151F1A265D7EE50028CB91 /* Sources */,
+				F7151F1B265D7EE50028CB91 /* Frameworks */,
+				F7151F1C265D7EE50028CB91 /* Resources */,
+			);
+			buildRules = (
+			);
+			dependencies = (
+				F7151F24265D7EE50028CB91 /* PBXTargetDependency */,
+			);
+			name = RunnerUITests;
+			productName = RunnerUITests;
+			productReference = F7151F1E265D7EE50028CB91 /* RunnerUITests.xctest */;
+			productType = "com.apple.product-type.bundle.ui-testing";
+		};
+/* End PBXNativeTarget section */
+
+/* Begin PBXProject section */
+		97C146E61CF9000F007C117D /* Project object */ = {
+			isa = PBXProject;
+			attributes = {
+				LastUpgradeCheck = 1320;
+				ORGANIZATIONNAME = "The Flutter Authors";
+				TargetAttributes = {
+					97C146ED1CF9000F007C117D = {
+						CreatedOnToolsVersion = 7.3.1;
+					};
+					F7151F0F265D7ED70028CB91 = {
+						CreatedOnToolsVersion = 12.5;
+						ProvisioningStyle = Automatic;
+						TestTargetID = 97C146ED1CF9000F007C117D;
+					};
+					F7151F1D265D7EE50028CB91 = {
+						CreatedOnToolsVersion = 12.5;
+						ProvisioningStyle = Automatic;
+						TestTargetID = 97C146ED1CF9000F007C117D;
+					};
+				};
+			};
+			buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
+			compatibilityVersion = "Xcode 3.2";
+			developmentRegion = en;
+			hasScannedForEncodings = 0;
+			knownRegions = (
+				en,
+				Base,
+			);
+			mainGroup = 97C146E51CF9000F007C117D;
+			productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
+			projectDirPath = "";
+			projectRoot = "";
+			targets = (
+				97C146ED1CF9000F007C117D /* Runner */,
+				F7151F0F265D7ED70028CB91 /* RunnerTests */,
+				F7151F1D265D7EE50028CB91 /* RunnerUITests */,
+			);
+		};
+/* End PBXProject section */
+
+/* Begin PBXResourcesBuildPhase section */
+		97C146EC1CF9000F007C117D /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
+				3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
+				97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
+				97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		F7151F0E265D7ED70028CB91 /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		F7151F1C265D7EE50028CB91 /* Resources */ = {
+			isa = PBXResourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXResourcesBuildPhase section */
+
+/* Begin PBXShellScriptBuildPhase section */
+		3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputPaths = (
+			);
+			name = "Thin Binary";
+			outputPaths = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
+		};
+		74BF216DF17B0C7F983459BD /* [CP] Check Pods Manifest.lock */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputPaths = (
+				"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+				"${PODS_ROOT}/Manifest.lock",
+			);
+			name = "[CP] Check Pods Manifest.lock";
+			outputPaths = (
+				"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n    # print error to STDERR\n    echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n    exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+			showEnvVarsInLog = 0;
+		};
+		9740EEB61CF901F6004384FC /* Run Script */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputPaths = (
+			);
+			name = "Run Script";
+			outputPaths = (
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
+		};
+		BB6BD9A1101E970BEF85B6D2 /* [CP] Copy Pods Resources */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputPaths = (
+				"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh",
+				"${PODS_ROOT}/GoogleMaps/Maps/Frameworks/GoogleMaps.framework/Resources/GoogleMaps.bundle",
+			);
+			name = "[CP] Copy Pods Resources";
+			outputPaths = (
+				"${TARGET_BUILD_DIR}/${UNLOCALIZED_RESOURCES_FOLDER_PATH}/GoogleMaps.bundle",
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
+			showEnvVarsInLog = 0;
+		};
+		BD39F60794E9A0264D5D3752 /* [CP] Check Pods Manifest.lock */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputFileListPaths = (
+			);
+			inputPaths = (
+				"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+				"${PODS_ROOT}/Manifest.lock",
+			);
+			name = "[CP] Check Pods Manifest.lock";
+			outputFileListPaths = (
+			);
+			outputPaths = (
+				"$(DERIVED_FILE_DIR)/Pods-RunnerUITests-checkManifestLockResult.txt",
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n    # print error to STDERR\n    echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n    exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+			showEnvVarsInLog = 0;
+		};
+		D067548A17DC238B80D2BD12 /* [CP] Check Pods Manifest.lock */ = {
+			isa = PBXShellScriptBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+			);
+			inputFileListPaths = (
+			);
+			inputPaths = (
+				"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
+				"${PODS_ROOT}/Manifest.lock",
+			);
+			name = "[CP] Check Pods Manifest.lock";
+			outputFileListPaths = (
+			);
+			outputPaths = (
+				"$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+			shellPath = /bin/sh;
+			shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n    # print error to STDERR\n    echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n    exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
+			showEnvVarsInLog = 0;
+		};
+/* End PBXShellScriptBuildPhase section */
+
+/* Begin PBXSourcesBuildPhase section */
+		97C146EA1CF9000F007C117D /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */,
+				97C146F31CF9000F007C117D /* main.m in Sources */,
+				1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		F7151F0C265D7ED70028CB91 /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				F7151F13265D7ED70028CB91 /* GoogleMapsTests.m in Sources */,
+				6851F3562835BC180032B7C8 /* FLTGoogleMapJSONConversionsConversionTests.m in Sources */,
+				982F2A6C27BADE17003C81F4 /* PartiallyMockedMapView.m in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+		F7151F1A265D7EE50028CB91 /* Sources */ = {
+			isa = PBXSourcesBuildPhase;
+			buildActionMask = 2147483647;
+			files = (
+				F7151F21265D7EE50028CB91 /* GoogleMapsUITests.m in Sources */,
+			);
+			runOnlyForDeploymentPostprocessing = 0;
+		};
+/* End PBXSourcesBuildPhase section */
+
+/* Begin PBXTargetDependency section */
+		F7151F16265D7ED70028CB91 /* PBXTargetDependency */ = {
+			isa = PBXTargetDependency;
+			target = 97C146ED1CF9000F007C117D /* Runner */;
+			targetProxy = F7151F15265D7ED70028CB91 /* PBXContainerItemProxy */;
+		};
+		F7151F24265D7EE50028CB91 /* PBXTargetDependency */ = {
+			isa = PBXTargetDependency;
+			target = 97C146ED1CF9000F007C117D /* Runner */;
+			targetProxy = F7151F23265D7EE50028CB91 /* PBXContainerItemProxy */;
+		};
+/* End PBXTargetDependency section */
+
+/* Begin PBXVariantGroup section */
+		97C146FA1CF9000F007C117D /* Main.storyboard */ = {
+			isa = PBXVariantGroup;
+			children = (
+				97C146FB1CF9000F007C117D /* Base */,
+			);
+			name = Main.storyboard;
+			sourceTree = "<group>";
+		};
+		97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
+			isa = PBXVariantGroup;
+			children = (
+				97C147001CF9000F007C117D /* Base */,
+			);
+			name = LaunchScreen.storyboard;
+			sourceTree = "<group>";
+		};
+/* End PBXVariantGroup section */
+
+/* Begin XCBuildConfiguration section */
+		97C147031CF9000F007C117D /* Debug */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_COMMA = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_STRICT_PROTOTYPES = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+				COPY_PHASE_STRIP = NO;
+				DEBUG_INFORMATION_FORMAT = dwarf;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				ENABLE_TESTABILITY = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu99;
+				GCC_DYNAMIC_NO_PIC = NO;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_OPTIMIZATION_LEVEL = 0;
+				GCC_PREPROCESSOR_DEFINITIONS = (
+					"DEBUG=1",
+					"$(inherited)",
+				);
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+				MTL_ENABLE_DEBUG_INFO = YES;
+				ONLY_ACTIVE_ARCH = YES;
+				SDKROOT = iphoneos;
+				TARGETED_DEVICE_FAMILY = "1,2";
+			};
+			name = Debug;
+		};
+		97C147041CF9000F007C117D /* Release */ = {
+			isa = XCBuildConfiguration;
+			buildSettings = {
+				ALWAYS_SEARCH_USER_PATHS = NO;
+				CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
+				CLANG_ANALYZER_NONNULL = YES;
+				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
+				CLANG_CXX_LIBRARY = "libc++";
+				CLANG_ENABLE_MODULES = YES;
+				CLANG_ENABLE_OBJC_ARC = YES;
+				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
+				CLANG_WARN_BOOL_CONVERSION = YES;
+				CLANG_WARN_COMMA = YES;
+				CLANG_WARN_CONSTANT_CONVERSION = YES;
+				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
+				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
+				CLANG_WARN_EMPTY_BODY = YES;
+				CLANG_WARN_ENUM_CONVERSION = YES;
+				CLANG_WARN_INFINITE_RECURSION = YES;
+				CLANG_WARN_INT_CONVERSION = YES;
+				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
+				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
+				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
+				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
+				CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
+				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
+				CLANG_WARN_STRICT_PROTOTYPES = YES;
+				CLANG_WARN_SUSPICIOUS_MOVE = YES;
+				CLANG_WARN_UNREACHABLE_CODE = YES;
+				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
+				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
+				COPY_PHASE_STRIP = NO;
+				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
+				ENABLE_NS_ASSERTIONS = NO;
+				ENABLE_STRICT_OBJC_MSGSEND = YES;
+				GCC_C_LANGUAGE_STANDARD = gnu99;
+				GCC_NO_COMMON_BLOCKS = YES;
+				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
+				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
+				GCC_WARN_UNDECLARED_SELECTOR = YES;
+				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
+				GCC_WARN_UNUSED_FUNCTION = YES;
+				GCC_WARN_UNUSED_VARIABLE = YES;
+				IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+				MTL_ENABLE_DEBUG_INFO = NO;
+				SDKROOT = iphoneos;
+				TARGETED_DEVICE_FAMILY = "1,2";
+				VALIDATE_PRODUCT = YES;
+			};
+			name = Release;
+		};
+		97C147061CF9000F007C117D /* Debug */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
+			buildSettings = {
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				ENABLE_BITCODE = NO;
+				"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "i386 arm64";
+				FRAMEWORK_SEARCH_PATHS = (
+					"$(inherited)",
+					"$(PROJECT_DIR)/Flutter",
+				);
+				INFOPLIST_FILE = Runner/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+				LIBRARY_SEARCH_PATHS = (
+					"$(inherited)",
+					"$(PROJECT_DIR)/Flutter",
+				);
+				PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.googleMobileMapsExample;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+			};
+			name = Debug;
+		};
+		97C147071CF9000F007C117D /* Release */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
+			buildSettings = {
+				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+				ENABLE_BITCODE = NO;
+				"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "i386 arm64";
+				FRAMEWORK_SEARCH_PATHS = (
+					"$(inherited)",
+					"$(PROJECT_DIR)/Flutter",
+				);
+				INFOPLIST_FILE = Runner/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
+				LIBRARY_SEARCH_PATHS = (
+					"$(inherited)",
+					"$(PROJECT_DIR)/Flutter",
+				);
+				PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.googleMobileMapsExample;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+			};
+			name = Release;
+		};
+		F7151F17265D7ED70028CB91 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = E52C6A6210A56F027C582EF9 /* Pods-RunnerTests.debug.xcconfig */;
+			buildSettings = {
+				BUNDLE_LOADER = "$(TEST_HOST)";
+				CODE_SIGN_STYLE = Automatic;
+				"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "i386 arm64";
+				INFOPLIST_FILE = RunnerTests/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+				MTL_FAST_MATH = YES;
+				PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerTests;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner";
+			};
+			name = Debug;
+		};
+		F7151F18265D7ED70028CB91 /* Release */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 733AFAB37683A9DA7512F09C /* Pods-RunnerTests.release.xcconfig */;
+			buildSettings = {
+				BUNDLE_LOADER = "$(TEST_HOST)";
+				CODE_SIGN_STYLE = Automatic;
+				"EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "i386 arm64";
+				INFOPLIST_FILE = RunnerTests/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+				MTL_FAST_MATH = YES;
+				PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerTests;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/Runner";
+			};
+			name = Release;
+		};
+		F7151F26265D7EE50028CB91 /* Debug */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = DDDAC1342ABDF2F125577581 /* Pods-RunnerUITests.debug.xcconfig */;
+			buildSettings = {
+				CODE_SIGN_STYLE = Automatic;
+				INFOPLIST_FILE = RunnerUITests/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+				MTL_FAST_MATH = YES;
+				PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerUITests;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				TEST_TARGET_NAME = Runner;
+			};
+			name = Debug;
+		};
+		F7151F27265D7EE50028CB91 /* Release */ = {
+			isa = XCBuildConfiguration;
+			baseConfigurationReference = 6AC1E6095B09DE4B02ECF64E /* Pods-RunnerUITests.release.xcconfig */;
+			buildSettings = {
+				CODE_SIGN_STYLE = Automatic;
+				INFOPLIST_FILE = RunnerUITests/Info.plist;
+				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
+				MTL_FAST_MATH = YES;
+				PRODUCT_BUNDLE_IDENTIFIER = dev.flutter.plugins.RunnerUITests;
+				PRODUCT_NAME = "$(TARGET_NAME)";
+				TEST_TARGET_NAME = Runner;
+			};
+			name = Release;
+		};
+/* End XCBuildConfiguration section */
+
+/* Begin XCConfigurationList section */
+		97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				97C147031CF9000F007C117D /* Debug */,
+				97C147041CF9000F007C117D /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				97C147061CF9000F007C117D /* Debug */,
+				97C147071CF9000F007C117D /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		F7151F19265D7ED70028CB91 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				F7151F17265D7ED70028CB91 /* Debug */,
+				F7151F18265D7ED70028CB91 /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+		F7151F25265D7EE50028CB91 /* Build configuration list for PBXNativeTarget "RunnerUITests" */ = {
+			isa = XCConfigurationList;
+			buildConfigurations = (
+				F7151F26265D7EE50028CB91 /* Debug */,
+				F7151F27265D7EE50028CB91 /* Release */,
+			);
+			defaultConfigurationIsVisible = 0;
+			defaultConfigurationName = Release;
+		};
+/* End XCConfigurationList section */
+	};
+	rootObject = 97C146E61CF9000F007C117D /* Project object */;
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..919434a
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Workspace
+   version = "1.0">
+   <FileRef
+      location = "self:">
+   </FileRef>
+</Workspace>
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
new file mode 100644
index 0000000..c983bfc
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
@@ -0,0 +1,107 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+   LastUpgradeVersion = "1320"
+   version = "1.3">
+   <BuildAction
+      parallelizeBuildables = "YES"
+      buildImplicitDependencies = "YES">
+      <BuildActionEntries>
+         <BuildActionEntry
+            buildForTesting = "YES"
+            buildForRunning = "YES"
+            buildForProfiling = "YES"
+            buildForArchiving = "YES"
+            buildForAnalyzing = "YES">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "97C146ED1CF9000F007C117D"
+               BuildableName = "Runner.app"
+               BlueprintName = "Runner"
+               ReferencedContainer = "container:Runner.xcodeproj">
+            </BuildableReference>
+         </BuildActionEntry>
+      </BuildActionEntries>
+   </BuildAction>
+   <TestAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      shouldUseLaunchSchemeArgsEnv = "YES">
+      <MacroExpansion>
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "97C146ED1CF9000F007C117D"
+            BuildableName = "Runner.app"
+            BlueprintName = "Runner"
+            ReferencedContainer = "container:Runner.xcodeproj">
+         </BuildableReference>
+      </MacroExpansion>
+      <Testables>
+         <TestableReference
+            skipped = "NO">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "F7151F0F265D7ED70028CB91"
+               BuildableName = "RunnerTests.xctest"
+               BlueprintName = "RunnerTests"
+               ReferencedContainer = "container:Runner.xcodeproj">
+            </BuildableReference>
+         </TestableReference>
+         <TestableReference
+            skipped = "NO">
+            <BuildableReference
+               BuildableIdentifier = "primary"
+               BlueprintIdentifier = "F7151F1D265D7EE50028CB91"
+               BuildableName = "RunnerUITests.xctest"
+               BlueprintName = "RunnerUITests"
+               ReferencedContainer = "container:Runner.xcodeproj">
+            </BuildableReference>
+         </TestableReference>
+      </Testables>
+   </TestAction>
+   <LaunchAction
+      buildConfiguration = "Debug"
+      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+      launchStyle = "0"
+      useCustomWorkingDirectory = "NO"
+      ignoresPersistentStateOnLaunch = "NO"
+      debugDocumentVersioning = "YES"
+      debugServiceExtension = "internal"
+      allowLocationSimulation = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "97C146ED1CF9000F007C117D"
+            BuildableName = "Runner.app"
+            BlueprintName = "Runner"
+            ReferencedContainer = "container:Runner.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+   </LaunchAction>
+   <ProfileAction
+      buildConfiguration = "Release"
+      shouldUseLaunchSchemeArgsEnv = "YES"
+      savedToolIdentifier = ""
+      useCustomWorkingDirectory = "NO"
+      debugDocumentVersioning = "YES">
+      <BuildableProductRunnable
+         runnableDebuggingMode = "0">
+         <BuildableReference
+            BuildableIdentifier = "primary"
+            BlueprintIdentifier = "97C146ED1CF9000F007C117D"
+            BuildableName = "Runner.app"
+            BlueprintName = "Runner"
+            ReferencedContainer = "container:Runner.xcodeproj">
+         </BuildableReference>
+      </BuildableProductRunnable>
+   </ProfileAction>
+   <AnalyzeAction
+      buildConfiguration = "Debug">
+   </AnalyzeAction>
+   <ArchiveAction
+      buildConfiguration = "Release"
+      revealArchiveInOrganizer = "YES">
+   </ArchiveAction>
+</Scheme>
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..21a3cc1
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Workspace
+   version = "1.0">
+   <FileRef
+      location = "group:Runner.xcodeproj">
+   </FileRef>
+   <FileRef
+      location = "group:Pods/Pods.xcodeproj">
+   </FileRef>
+</Workspace>
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
new file mode 100644
index 0000000..18d9810
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>IDEDidComputeMac32BitWarning</key>
+	<true/>
+</dict>
+</plist>
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/AppDelegate.h b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/AppDelegate.h
new file mode 100644
index 0000000..9bc6c56
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/AppDelegate.h
@@ -0,0 +1,9 @@
+// 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 <UIKit/UIKit.h>
+
+@interface AppDelegate : FlutterAppDelegate
+@end
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/AppDelegate.m b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/AppDelegate.m
new file mode 100644
index 0000000..5573344
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/AppDelegate.m
@@ -0,0 +1,27 @@
+// 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 "AppDelegate.h"
+#import "GeneratedPluginRegistrant.h"
+
+@import GoogleMaps;
+
+@implementation AppDelegate
+
+- (BOOL)application:(UIApplication *)application
+    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
+  // Provide the GoogleMaps API key.
+  NSString *mapsApiKey = [[NSProcessInfo processInfo] environment][@"MAPS_API_KEY"];
+  if ([mapsApiKey length] == 0) {
+    mapsApiKey = @"YOUR KEY HERE";
+  }
+  [GMSServices provideAPIKey:mapsApiKey];
+
+  // Register Flutter plugins.
+  [GeneratedPluginRegistrant registerWithRegistry:self];
+
+  return [super application:application didFinishLaunchingWithOptions:launchOptions];
+}
+
+@end
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
new file mode 100644
index 0000000..d36b1fa
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json
@@ -0,0 +1,122 @@
+{
+  "images" : [
+    {
+      "size" : "20x20",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-20x20@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "20x20",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-20x20@3x.png",
+      "scale" : "3x"
+    },
+    {
+      "size" : "29x29",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-29x29@1x.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "29x29",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-29x29@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "29x29",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-29x29@3x.png",
+      "scale" : "3x"
+    },
+    {
+      "size" : "40x40",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-40x40@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "40x40",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-40x40@3x.png",
+      "scale" : "3x"
+    },
+    {
+      "size" : "60x60",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-60x60@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "60x60",
+      "idiom" : "iphone",
+      "filename" : "Icon-App-60x60@3x.png",
+      "scale" : "3x"
+    },
+    {
+      "size" : "20x20",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-20x20@1x.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "20x20",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-20x20@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "29x29",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-29x29@1x.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "29x29",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-29x29@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "40x40",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-40x40@1x.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "40x40",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-40x40@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "76x76",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-76x76@1x.png",
+      "scale" : "1x"
+    },
+    {
+      "size" : "76x76",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-76x76@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "83.5x83.5",
+      "idiom" : "ipad",
+      "filename" : "Icon-App-83.5x83.5@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "size" : "1024x1024",
+      "idiom" : "ios-marketing",
+      "filename" : "Icon-App-1024x1024@1x.png",
+      "scale" : "1x"
+    }
+  ],
+  "info" : {
+    "version" : 1,
+    "author" : "xcode"
+  }
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
new file mode 100644
index 0000000..3d43d11
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
Binary files differ
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
new file mode 100644
index 0000000..28c6bf0
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
Binary files differ
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
new file mode 100644
index 0000000..2ccbfd9
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
Binary files differ
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
new file mode 100644
index 0000000..f091b6b
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
Binary files differ
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
new file mode 100644
index 0000000..4cde121
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
Binary files differ
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
new file mode 100644
index 0000000..d0ef06e
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
Binary files differ
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
new file mode 100644
index 0000000..dcdc230
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
Binary files differ
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
new file mode 100644
index 0000000..2ccbfd9
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
Binary files differ
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
new file mode 100644
index 0000000..c8f9ed8
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
Binary files differ
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
new file mode 100644
index 0000000..a6d6b86
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
Binary files differ
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
new file mode 100644
index 0000000..a6d6b86
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
Binary files differ
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
new file mode 100644
index 0000000..75b2d16
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
Binary files differ
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
new file mode 100644
index 0000000..c4df70d
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
Binary files differ
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
new file mode 100644
index 0000000..6a84f41
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
Binary files differ
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
new file mode 100644
index 0000000..d0e1f58
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
Binary files differ
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
new file mode 100644
index 0000000..0bedcf2
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+  "images" : [
+    {
+      "idiom" : "universal",
+      "filename" : "LaunchImage.png",
+      "scale" : "1x"
+    },
+    {
+      "idiom" : "universal",
+      "filename" : "LaunchImage@2x.png",
+      "scale" : "2x"
+    },
+    {
+      "idiom" : "universal",
+      "filename" : "LaunchImage@3x.png",
+      "scale" : "3x"
+    }
+  ],
+  "info" : {
+    "version" : 1,
+    "author" : "xcode"
+  }
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
new file mode 100644
index 0000000..9da19ea
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
Binary files differ
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
new file mode 100644
index 0000000..9da19ea
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
Binary files differ
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
new file mode 100644
index 0000000..9da19ea
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
Binary files differ
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
new file mode 100644
index 0000000..89c2725
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
@@ -0,0 +1,5 @@
+# Launch Screen Assets
+
+You can customize the launch screen with your own desired assets by replacing the image files in this directory.
+
+You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
\ No newline at end of file
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Base.lproj/LaunchScreen.storyboard
new file mode 100644
index 0000000..f2e259c
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Base.lproj/LaunchScreen.storyboard
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
+    </dependencies>
+    <scenes>
+        <!--View Controller-->
+        <scene sceneID="EHf-IW-A2E">
+            <objects>
+                <viewController id="01J-lp-oVM" sceneMemberID="viewController">
+                    <layoutGuides>
+                        <viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
+                        <viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
+                    </layoutGuides>
+                    <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                        <subviews>
+                            <imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
+                            </imageView>
+                        </subviews>
+                        <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+                        <constraints>
+                            <constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
+                            <constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
+                        </constraints>
+                    </view>
+                </viewController>
+                <placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
+            </objects>
+            <point key="canvasLocation" x="53" y="375"/>
+        </scene>
+    </scenes>
+    <resources>
+        <image name="LaunchImage" width="168" height="185"/>
+    </resources>
+</document>
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Base.lproj/Main.storyboard b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Base.lproj/Main.storyboard
new file mode 100644
index 0000000..f3c2851
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Base.lproj/Main.storyboard
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
+    <dependencies>
+        <deployment identifier="iOS"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
+    </dependencies>
+    <scenes>
+        <!--Flutter View Controller-->
+        <scene sceneID="tne-QT-ifu">
+            <objects>
+                <viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
+                    <layoutGuides>
+                        <viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
+                        <viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
+                    </layoutGuides>
+                    <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
+                        <rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
+                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+                        <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
+                    </view>
+                </viewController>
+                <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
+            </objects>
+        </scene>
+    </scenes>
+</document>
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Info.plist b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Info.plist
new file mode 100644
index 0000000..0fa9c73
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/Info.plist
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>en</string>
+	<key>CFBundleExecutable</key>
+	<string>$(EXECUTABLE_NAME)</string>
+	<key>CFBundleIdentifier</key>
+	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>google_maps_flutter_example</string>
+	<key>CFBundlePackageType</key>
+	<string>APPL</string>
+	<key>CFBundleShortVersionString</key>
+	<string>1.0</string>
+	<key>CFBundleSignature</key>
+	<string>????</string>
+	<key>CFBundleVersion</key>
+	<string>1</string>
+	<key>LSRequiresIPhoneOS</key>
+	<true/>
+	<key>NSLocationWhenInUseUsageDescription</key>
+	<string>This app needs your location to test the location feature of the Google Maps plugin.</string>
+	<key>UILaunchStoryboardName</key>
+	<string>LaunchScreen</string>
+	<key>UIMainStoryboardFile</key>
+	<string>Main</string>
+	<key>UIRequiredDeviceCapabilities</key>
+	<array>
+		<string>arm64</string>
+	</array>
+	<key>UISupportedInterfaceOrientations</key>
+	<array>
+		<string>UIInterfaceOrientationPortrait</string>
+		<string>UIInterfaceOrientationLandscapeLeft</string>
+		<string>UIInterfaceOrientationLandscapeRight</string>
+	</array>
+	<key>UISupportedInterfaceOrientations~ipad</key>
+	<array>
+		<string>UIInterfaceOrientationPortrait</string>
+		<string>UIInterfaceOrientationPortraitUpsideDown</string>
+		<string>UIInterfaceOrientationLandscapeLeft</string>
+		<string>UIInterfaceOrientationLandscapeRight</string>
+	</array>
+	<key>UIViewControllerBasedStatusBarAppearance</key>
+	<false/>
+</dict>
+</plist>
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/main.m b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/main.m
new file mode 100644
index 0000000..f143297
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/Runner/main.m
@@ -0,0 +1,13 @@
+// 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 <UIKit/UIKit.h>
+#import "AppDelegate.h"
+
+int main(int argc, char *argv[]) {
+  @autoreleasepool {
+    return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
+  }
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter/example/ios/RunnerTests/FLTGoogleMapJSONConversionsConversionTests.m b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/RunnerTests/FLTGoogleMapJSONConversionsConversionTests.m
similarity index 99%
rename from packages/google_maps_flutter/google_maps_flutter/example/ios/RunnerTests/FLTGoogleMapJSONConversionsConversionTests.m
rename to packages/google_maps_flutter/google_maps_flutter_ios/example/ios/RunnerTests/FLTGoogleMapJSONConversionsConversionTests.m
index bf226fe..bb9020d 100644
--- a/packages/google_maps_flutter/google_maps_flutter/example/ios/RunnerTests/FLTGoogleMapJSONConversionsConversionTests.m
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/RunnerTests/FLTGoogleMapJSONConversionsConversionTests.m
@@ -2,8 +2,8 @@
 // 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 google_maps_flutter_ios;
+@import google_maps_flutter_ios.Test;
 @import XCTest;
 @import MapKit;
 @import GoogleMaps;
diff --git a/packages/google_maps_flutter/google_maps_flutter/example/ios/RunnerTests/GoogleMapsTests.m b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/RunnerTests/GoogleMapsTests.m
similarity index 94%
rename from packages/google_maps_flutter/google_maps_flutter/example/ios/RunnerTests/GoogleMapsTests.m
rename to packages/google_maps_flutter/google_maps_flutter_ios/example/ios/RunnerTests/GoogleMapsTests.m
index f03dca2..a8768e1 100644
--- a/packages/google_maps_flutter/google_maps_flutter/example/ios/RunnerTests/GoogleMapsTests.m
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/RunnerTests/GoogleMapsTests.m
@@ -2,8 +2,8 @@
 // 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 google_maps_flutter_ios;
+@import google_maps_flutter_ios.Test;
 @import XCTest;
 
 #import <OCMock/OCMock.h>
diff --git a/packages/google_maps_flutter/google_maps_flutter/example/ios/RunnerTests/Info.plist b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/RunnerTests/Info.plist
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/example/ios/RunnerTests/Info.plist
rename to packages/google_maps_flutter/google_maps_flutter_ios/example/ios/RunnerTests/Info.plist
diff --git a/packages/google_maps_flutter/google_maps_flutter/example/ios/RunnerTests/PartiallyMockedMapView.h b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/RunnerTests/PartiallyMockedMapView.h
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/example/ios/RunnerTests/PartiallyMockedMapView.h
rename to packages/google_maps_flutter/google_maps_flutter_ios/example/ios/RunnerTests/PartiallyMockedMapView.h
diff --git a/packages/google_maps_flutter/google_maps_flutter/example/ios/RunnerTests/PartiallyMockedMapView.m b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/RunnerTests/PartiallyMockedMapView.m
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/example/ios/RunnerTests/PartiallyMockedMapView.m
rename to packages/google_maps_flutter/google_maps_flutter_ios/example/ios/RunnerTests/PartiallyMockedMapView.m
diff --git a/packages/google_maps_flutter/google_maps_flutter/example/ios/RunnerUITests/GoogleMapsUITests.m b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/RunnerUITests/GoogleMapsUITests.m
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/example/ios/RunnerUITests/GoogleMapsUITests.m
rename to packages/google_maps_flutter/google_maps_flutter_ios/example/ios/RunnerUITests/GoogleMapsUITests.m
diff --git a/packages/google_maps_flutter/google_maps_flutter/example/ios/RunnerUITests/Info.plist b/packages/google_maps_flutter/google_maps_flutter_ios/example/ios/RunnerUITests/Info.plist
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/example/ios/RunnerUITests/Info.plist
rename to packages/google_maps_flutter/google_maps_flutter_ios/example/ios/RunnerUITests/Info.plist
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/animate_camera.dart b/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/animate_camera.dart
new file mode 100644
index 0000000..c34a3ba
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/animate_camera.dart
@@ -0,0 +1,171 @@
+// 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.
+
+// ignore_for_file: public_member_api_docs
+
+import 'package:flutter/material.dart';
+import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
+
+import 'example_google_map.dart';
+import 'page.dart';
+
+class AnimateCameraPage extends GoogleMapExampleAppPage {
+  const AnimateCameraPage({Key? key})
+      : super(const Icon(Icons.map), 'Camera control, animated', key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return const AnimateCamera();
+  }
+}
+
+class AnimateCamera extends StatefulWidget {
+  const AnimateCamera({Key? key}) : super(key: key);
+  @override
+  State createState() => AnimateCameraState();
+}
+
+class AnimateCameraState extends State<AnimateCamera> {
+  ExampleGoogleMapController? mapController;
+
+  // ignore: use_setters_to_change_properties
+  void _onMapCreated(ExampleGoogleMapController controller) {
+    mapController = controller;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Column(
+      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+      crossAxisAlignment: CrossAxisAlignment.stretch,
+      children: <Widget>[
+        Center(
+          child: SizedBox(
+            width: 300.0,
+            height: 200.0,
+            child: ExampleGoogleMap(
+              onMapCreated: _onMapCreated,
+              initialCameraPosition:
+                  const CameraPosition(target: LatLng(0.0, 0.0)),
+            ),
+          ),
+        ),
+        Row(
+          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+          children: <Widget>[
+            Column(
+              children: <Widget>[
+                TextButton(
+                  onPressed: () {
+                    mapController?.animateCamera(
+                      CameraUpdate.newCameraPosition(
+                        const CameraPosition(
+                          bearing: 270.0,
+                          target: LatLng(51.5160895, -0.1294527),
+                          tilt: 30.0,
+                          zoom: 17.0,
+                        ),
+                      ),
+                    );
+                  },
+                  child: const Text('newCameraPosition'),
+                ),
+                TextButton(
+                  onPressed: () {
+                    mapController?.animateCamera(
+                      CameraUpdate.newLatLng(
+                        const LatLng(56.1725505, 10.1850512),
+                      ),
+                    );
+                  },
+                  child: const Text('newLatLng'),
+                ),
+                TextButton(
+                  onPressed: () {
+                    mapController?.animateCamera(
+                      CameraUpdate.newLatLngBounds(
+                        LatLngBounds(
+                          southwest: const LatLng(-38.483935, 113.248673),
+                          northeast: const LatLng(-8.982446, 153.823821),
+                        ),
+                        10.0,
+                      ),
+                    );
+                  },
+                  child: const Text('newLatLngBounds'),
+                ),
+                TextButton(
+                  onPressed: () {
+                    mapController?.animateCamera(
+                      CameraUpdate.newLatLngZoom(
+                        const LatLng(37.4231613, -122.087159),
+                        11.0,
+                      ),
+                    );
+                  },
+                  child: const Text('newLatLngZoom'),
+                ),
+                TextButton(
+                  onPressed: () {
+                    mapController?.animateCamera(
+                      CameraUpdate.scrollBy(150.0, -225.0),
+                    );
+                  },
+                  child: const Text('scrollBy'),
+                ),
+              ],
+            ),
+            Column(
+              children: <Widget>[
+                TextButton(
+                  onPressed: () {
+                    mapController?.animateCamera(
+                      CameraUpdate.zoomBy(
+                        -0.5,
+                        const Offset(30.0, 20.0),
+                      ),
+                    );
+                  },
+                  child: const Text('zoomBy with focus'),
+                ),
+                TextButton(
+                  onPressed: () {
+                    mapController?.animateCamera(
+                      CameraUpdate.zoomBy(-0.5),
+                    );
+                  },
+                  child: const Text('zoomBy'),
+                ),
+                TextButton(
+                  onPressed: () {
+                    mapController?.animateCamera(
+                      CameraUpdate.zoomIn(),
+                    );
+                  },
+                  child: const Text('zoomIn'),
+                ),
+                TextButton(
+                  onPressed: () {
+                    mapController?.animateCamera(
+                      CameraUpdate.zoomOut(),
+                    );
+                  },
+                  child: const Text('zoomOut'),
+                ),
+                TextButton(
+                  onPressed: () {
+                    mapController?.animateCamera(
+                      CameraUpdate.zoomTo(16.0),
+                    );
+                  },
+                  child: const Text('zoomTo'),
+                ),
+              ],
+            ),
+          ],
+        )
+      ],
+    );
+  }
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/example_google_map.dart b/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/example_google_map.dart
new file mode 100644
index 0000000..e2c7131
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/example_google_map.dart
@@ -0,0 +1,538 @@
+// 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 'dart:async';
+// TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231)
+// ignore: unnecessary_import
+import 'dart:typed_data';
+
+import 'package:flutter/foundation.dart';
+import 'package:flutter/gestures.dart';
+import 'package:flutter/material.dart';
+import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
+
+// This is a pared down version of the Dart code from the app-facing package,
+// to allow running the same examples for package-local testing.
+// TODO(stuartmorgan): Consider extracting this to a shared package. See also
+// https://github.com/flutter/flutter/issues/46716.
+
+/// Controller for a single ExampleGoogleMap instance running on the host platform.
+class ExampleGoogleMapController {
+  ExampleGoogleMapController._(
+    this._googleMapState, {
+    required this.mapId,
+  }) {
+    _connectStreams(mapId);
+  }
+
+  /// The mapId for this controller
+  final int mapId;
+
+  /// Initialize control of a [ExampleGoogleMap] with [id].
+  ///
+  /// Mainly for internal use when instantiating a [ExampleGoogleMapController] passed
+  /// in [ExampleGoogleMap.onMapCreated] callback.
+  static Future<ExampleGoogleMapController> _init(
+    int id,
+    CameraPosition initialCameraPosition,
+    _ExampleGoogleMapState googleMapState,
+  ) async {
+    await GoogleMapsFlutterPlatform.instance.init(id);
+    return ExampleGoogleMapController._(
+      googleMapState,
+      mapId: id,
+    );
+  }
+
+  final _ExampleGoogleMapState _googleMapState;
+
+  void _connectStreams(int mapId) {
+    if (_googleMapState.widget.onCameraMoveStarted != null) {
+      GoogleMapsFlutterPlatform.instance
+          .onCameraMoveStarted(mapId: mapId)
+          .listen((_) => _googleMapState.widget.onCameraMoveStarted!());
+    }
+    if (_googleMapState.widget.onCameraMove != null) {
+      GoogleMapsFlutterPlatform.instance.onCameraMove(mapId: mapId).listen(
+          (CameraMoveEvent e) => _googleMapState.widget.onCameraMove!(e.value));
+    }
+    if (_googleMapState.widget.onCameraIdle != null) {
+      GoogleMapsFlutterPlatform.instance
+          .onCameraIdle(mapId: mapId)
+          .listen((_) => _googleMapState.widget.onCameraIdle!());
+    }
+    GoogleMapsFlutterPlatform.instance
+        .onMarkerTap(mapId: mapId)
+        .listen((MarkerTapEvent e) => _googleMapState.onMarkerTap(e.value));
+    GoogleMapsFlutterPlatform.instance.onMarkerDragStart(mapId: mapId).listen(
+        (MarkerDragStartEvent e) =>
+            _googleMapState.onMarkerDragStart(e.value, e.position));
+    GoogleMapsFlutterPlatform.instance.onMarkerDrag(mapId: mapId).listen(
+        (MarkerDragEvent e) =>
+            _googleMapState.onMarkerDrag(e.value, e.position));
+    GoogleMapsFlutterPlatform.instance.onMarkerDragEnd(mapId: mapId).listen(
+        (MarkerDragEndEvent e) =>
+            _googleMapState.onMarkerDragEnd(e.value, e.position));
+    GoogleMapsFlutterPlatform.instance.onInfoWindowTap(mapId: mapId).listen(
+        (InfoWindowTapEvent e) => _googleMapState.onInfoWindowTap(e.value));
+    GoogleMapsFlutterPlatform.instance
+        .onPolylineTap(mapId: mapId)
+        .listen((PolylineTapEvent e) => _googleMapState.onPolylineTap(e.value));
+    GoogleMapsFlutterPlatform.instance
+        .onPolygonTap(mapId: mapId)
+        .listen((PolygonTapEvent e) => _googleMapState.onPolygonTap(e.value));
+    GoogleMapsFlutterPlatform.instance
+        .onCircleTap(mapId: mapId)
+        .listen((CircleTapEvent e) => _googleMapState.onCircleTap(e.value));
+    GoogleMapsFlutterPlatform.instance
+        .onTap(mapId: mapId)
+        .listen((MapTapEvent e) => _googleMapState.onTap(e.position));
+    GoogleMapsFlutterPlatform.instance.onLongPress(mapId: mapId).listen(
+        (MapLongPressEvent e) => _googleMapState.onLongPress(e.position));
+  }
+
+  /// Updates configuration options of the map user interface.
+  Future<void> _updateMapConfiguration(MapConfiguration update) {
+    return GoogleMapsFlutterPlatform.instance
+        .updateMapConfiguration(update, mapId: mapId);
+  }
+
+  /// Updates marker configuration.
+  Future<void> _updateMarkers(MarkerUpdates markerUpdates) {
+    return GoogleMapsFlutterPlatform.instance
+        .updateMarkers(markerUpdates, mapId: mapId);
+  }
+
+  /// Updates polygon configuration.
+  Future<void> _updatePolygons(PolygonUpdates polygonUpdates) {
+    return GoogleMapsFlutterPlatform.instance
+        .updatePolygons(polygonUpdates, mapId: mapId);
+  }
+
+  /// Updates polyline configuration.
+  Future<void> _updatePolylines(PolylineUpdates polylineUpdates) {
+    return GoogleMapsFlutterPlatform.instance
+        .updatePolylines(polylineUpdates, mapId: mapId);
+  }
+
+  /// Updates circle configuration.
+  Future<void> _updateCircles(CircleUpdates circleUpdates) {
+    return GoogleMapsFlutterPlatform.instance
+        .updateCircles(circleUpdates, mapId: mapId);
+  }
+
+  /// Updates tile overlays configuration.
+  Future<void> _updateTileOverlays(Set<TileOverlay> newTileOverlays) {
+    return GoogleMapsFlutterPlatform.instance
+        .updateTileOverlays(newTileOverlays: newTileOverlays, mapId: mapId);
+  }
+
+  /// Clears the tile cache so that all tiles will be requested again from the
+  /// [TileProvider].
+  Future<void> clearTileCache(TileOverlayId tileOverlayId) async {
+    return GoogleMapsFlutterPlatform.instance
+        .clearTileCache(tileOverlayId, mapId: mapId);
+  }
+
+  /// Starts an animated change of the map camera position.
+  Future<void> animateCamera(CameraUpdate cameraUpdate) {
+    return GoogleMapsFlutterPlatform.instance
+        .animateCamera(cameraUpdate, mapId: mapId);
+  }
+
+  /// Changes the map camera position.
+  Future<void> moveCamera(CameraUpdate cameraUpdate) {
+    return GoogleMapsFlutterPlatform.instance
+        .moveCamera(cameraUpdate, mapId: mapId);
+  }
+
+  /// Sets the styling of the base map.
+  Future<void> setMapStyle(String? mapStyle) {
+    return GoogleMapsFlutterPlatform.instance
+        .setMapStyle(mapStyle, mapId: mapId);
+  }
+
+  /// Return [LatLngBounds] defining the region that is visible in a map.
+  Future<LatLngBounds> getVisibleRegion() {
+    return GoogleMapsFlutterPlatform.instance.getVisibleRegion(mapId: mapId);
+  }
+
+  /// Return [ScreenCoordinate] of the [LatLng] in the current map view.
+  Future<ScreenCoordinate> getScreenCoordinate(LatLng latLng) {
+    return GoogleMapsFlutterPlatform.instance
+        .getScreenCoordinate(latLng, mapId: mapId);
+  }
+
+  /// Returns [LatLng] corresponding to the [ScreenCoordinate] in the current map view.
+  Future<LatLng> getLatLng(ScreenCoordinate screenCoordinate) {
+    return GoogleMapsFlutterPlatform.instance
+        .getLatLng(screenCoordinate, mapId: mapId);
+  }
+
+  /// Programmatically show the Info Window for a [Marker].
+  Future<void> showMarkerInfoWindow(MarkerId markerId) {
+    return GoogleMapsFlutterPlatform.instance
+        .showMarkerInfoWindow(markerId, mapId: mapId);
+  }
+
+  /// Programmatically hide the Info Window for a [Marker].
+  Future<void> hideMarkerInfoWindow(MarkerId markerId) {
+    return GoogleMapsFlutterPlatform.instance
+        .hideMarkerInfoWindow(markerId, mapId: mapId);
+  }
+
+  /// Returns `true` when the [InfoWindow] is showing, `false` otherwise.
+  Future<bool> isMarkerInfoWindowShown(MarkerId markerId) {
+    return GoogleMapsFlutterPlatform.instance
+        .isMarkerInfoWindowShown(markerId, mapId: mapId);
+  }
+
+  /// Returns the current zoom level of the map
+  Future<double> getZoomLevel() {
+    return GoogleMapsFlutterPlatform.instance.getZoomLevel(mapId: mapId);
+  }
+
+  /// Returns the image bytes of the map
+  Future<Uint8List?> takeSnapshot() {
+    return GoogleMapsFlutterPlatform.instance.takeSnapshot(mapId: mapId);
+  }
+
+  /// Disposes of the platform resources
+  void dispose() {
+    GoogleMapsFlutterPlatform.instance.dispose(mapId: mapId);
+  }
+}
+
+// The next map ID to create.
+int _nextMapCreationId = 0;
+
+/// A widget which displays a map with data obtained from the Google Maps service.
+class ExampleGoogleMap extends StatefulWidget {
+  /// Creates a widget displaying data from Google Maps services.
+  ///
+  /// [AssertionError] will be thrown if [initialCameraPosition] is null;
+  const ExampleGoogleMap({
+    Key? key,
+    required this.initialCameraPosition,
+    this.onMapCreated,
+    this.gestureRecognizers = const <Factory<OneSequenceGestureRecognizer>>{},
+    this.compassEnabled = true,
+    this.mapToolbarEnabled = true,
+    this.cameraTargetBounds = CameraTargetBounds.unbounded,
+    this.mapType = MapType.normal,
+    this.minMaxZoomPreference = MinMaxZoomPreference.unbounded,
+    this.rotateGesturesEnabled = true,
+    this.scrollGesturesEnabled = true,
+    this.zoomControlsEnabled = true,
+    this.zoomGesturesEnabled = true,
+    this.liteModeEnabled = false,
+    this.tiltGesturesEnabled = true,
+    this.myLocationEnabled = false,
+    this.myLocationButtonEnabled = true,
+    this.layoutDirection,
+
+    /// If no padding is specified default padding will be 0.
+    this.padding = const EdgeInsets.all(0),
+    this.indoorViewEnabled = false,
+    this.trafficEnabled = false,
+    this.buildingsEnabled = true,
+    this.markers = const <Marker>{},
+    this.polygons = const <Polygon>{},
+    this.polylines = const <Polyline>{},
+    this.circles = const <Circle>{},
+    this.onCameraMoveStarted,
+    this.tileOverlays = const <TileOverlay>{},
+    this.onCameraMove,
+    this.onCameraIdle,
+    this.onTap,
+    this.onLongPress,
+  }) : super(key: key);
+
+  /// Callback method for when the map is ready to be used.
+  ///
+  /// Used to receive a [ExampleGoogleMapController] for this [ExampleGoogleMap].
+  final void Function(ExampleGoogleMapController controller)? onMapCreated;
+
+  /// The initial position of the map's camera.
+  final CameraPosition initialCameraPosition;
+
+  /// True if the map should show a compass when rotated.
+  final bool compassEnabled;
+
+  /// True if the map should show a toolbar when you interact with the map. Android only.
+  final bool mapToolbarEnabled;
+
+  /// Geographical bounding box for the camera target.
+  final CameraTargetBounds cameraTargetBounds;
+
+  /// Type of map tiles to be rendered.
+  final MapType mapType;
+
+  /// The layout direction to use for the embedded view.
+  final TextDirection? layoutDirection;
+
+  /// Preferred bounds for the camera zoom level.
+  ///
+  /// Actual bounds depend on map data and device.
+  final MinMaxZoomPreference minMaxZoomPreference;
+
+  /// True if the map view should respond to rotate gestures.
+  final bool rotateGesturesEnabled;
+
+  /// True if the map view should respond to scroll gestures.
+  final bool scrollGesturesEnabled;
+
+  /// True if the map view should show zoom controls. This includes two buttons
+  /// to zoom in and zoom out. The default value is to show zoom controls.
+  final bool zoomControlsEnabled;
+
+  /// True if the map view should respond to zoom gestures.
+  final bool zoomGesturesEnabled;
+
+  /// True if the map view should be in lite mode. Android only.
+  final bool liteModeEnabled;
+
+  /// True if the map view should respond to tilt gestures.
+  final bool tiltGesturesEnabled;
+
+  /// Padding to be set on map.
+  final EdgeInsets padding;
+
+  /// Markers to be placed on the map.
+  final Set<Marker> markers;
+
+  /// Polygons to be placed on the map.
+  final Set<Polygon> polygons;
+
+  /// Polylines to be placed on the map.
+  final Set<Polyline> polylines;
+
+  /// Circles to be placed on the map.
+  final Set<Circle> circles;
+
+  /// Tile overlays to be placed on the map.
+  final Set<TileOverlay> tileOverlays;
+
+  /// Called when the camera starts moving.
+  final VoidCallback? onCameraMoveStarted;
+
+  /// Called repeatedly as the camera continues to move after an
+  /// onCameraMoveStarted call.
+  final CameraPositionCallback? onCameraMove;
+
+  /// Called when camera movement has ended, there are no pending
+  /// animations and the user has stopped interacting with the map.
+  final VoidCallback? onCameraIdle;
+
+  /// Called every time a [ExampleGoogleMap] is tapped.
+  final ArgumentCallback<LatLng>? onTap;
+
+  /// Called every time a [ExampleGoogleMap] is long pressed.
+  final ArgumentCallback<LatLng>? onLongPress;
+
+  /// True if a "My Location" layer should be shown on the map.
+  final bool myLocationEnabled;
+
+  /// Enables or disables the my-location button.
+  final bool myLocationButtonEnabled;
+
+  /// Enables or disables the indoor view from the map
+  final bool indoorViewEnabled;
+
+  /// Enables or disables the traffic layer of the map
+  final bool trafficEnabled;
+
+  /// Enables or disables showing 3D buildings where available
+  final bool buildingsEnabled;
+
+  /// Which gestures should be consumed by the map.
+  final Set<Factory<OneSequenceGestureRecognizer>> gestureRecognizers;
+
+  /// Creates a [State] for this [ExampleGoogleMap].
+  @override
+  State createState() => _ExampleGoogleMapState();
+}
+
+class _ExampleGoogleMapState extends State<ExampleGoogleMap> {
+  final int _mapId = _nextMapCreationId++;
+
+  final Completer<ExampleGoogleMapController> _controller =
+      Completer<ExampleGoogleMapController>();
+
+  Map<MarkerId, Marker> _markers = <MarkerId, Marker>{};
+  Map<PolygonId, Polygon> _polygons = <PolygonId, Polygon>{};
+  Map<PolylineId, Polyline> _polylines = <PolylineId, Polyline>{};
+  Map<CircleId, Circle> _circles = <CircleId, Circle>{};
+  late MapConfiguration _mapConfiguration;
+
+  @override
+  Widget build(BuildContext context) {
+    return GoogleMapsFlutterPlatform.instance.buildViewWithConfiguration(
+      _mapId,
+      onPlatformViewCreated,
+      widgetConfiguration: MapWidgetConfiguration(
+        textDirection: widget.layoutDirection ??
+            Directionality.maybeOf(context) ??
+            TextDirection.ltr,
+        initialCameraPosition: widget.initialCameraPosition,
+        gestureRecognizers: widget.gestureRecognizers,
+      ),
+      mapObjects: MapObjects(
+        markers: widget.markers,
+        polygons: widget.polygons,
+        polylines: widget.polylines,
+        circles: widget.circles,
+      ),
+      mapConfiguration: _mapConfiguration,
+    );
+  }
+
+  @override
+  void initState() {
+    super.initState();
+    _mapConfiguration = _configurationFromMapWidget(widget);
+    _markers = keyByMarkerId(widget.markers);
+    _polygons = keyByPolygonId(widget.polygons);
+    _polylines = keyByPolylineId(widget.polylines);
+    _circles = keyByCircleId(widget.circles);
+  }
+
+  @override
+  void dispose() {
+    _controller.future
+        .then((ExampleGoogleMapController controller) => controller.dispose());
+    super.dispose();
+  }
+
+  @override
+  void didUpdateWidget(ExampleGoogleMap oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    _updateOptions();
+    _updateMarkers();
+    _updatePolygons();
+    _updatePolylines();
+    _updateCircles();
+    _updateTileOverlays();
+  }
+
+  Future<void> _updateOptions() async {
+    final MapConfiguration newConfig = _configurationFromMapWidget(widget);
+    final MapConfiguration updates = newConfig.diffFrom(_mapConfiguration);
+    if (updates.isEmpty) {
+      return;
+    }
+    final ExampleGoogleMapController controller = await _controller.future;
+    controller._updateMapConfiguration(updates);
+    _mapConfiguration = newConfig;
+  }
+
+  Future<void> _updateMarkers() async {
+    final ExampleGoogleMapController controller = await _controller.future;
+    controller._updateMarkers(
+        MarkerUpdates.from(_markers.values.toSet(), widget.markers));
+    _markers = keyByMarkerId(widget.markers);
+  }
+
+  Future<void> _updatePolygons() async {
+    final ExampleGoogleMapController controller = await _controller.future;
+    controller._updatePolygons(
+        PolygonUpdates.from(_polygons.values.toSet(), widget.polygons));
+    _polygons = keyByPolygonId(widget.polygons);
+  }
+
+  Future<void> _updatePolylines() async {
+    final ExampleGoogleMapController controller = await _controller.future;
+    controller._updatePolylines(
+        PolylineUpdates.from(_polylines.values.toSet(), widget.polylines));
+    _polylines = keyByPolylineId(widget.polylines);
+  }
+
+  Future<void> _updateCircles() async {
+    final ExampleGoogleMapController controller = await _controller.future;
+    controller._updateCircles(
+        CircleUpdates.from(_circles.values.toSet(), widget.circles));
+    _circles = keyByCircleId(widget.circles);
+  }
+
+  Future<void> _updateTileOverlays() async {
+    final ExampleGoogleMapController controller = await _controller.future;
+    controller._updateTileOverlays(widget.tileOverlays);
+  }
+
+  Future<void> onPlatformViewCreated(int id) async {
+    final ExampleGoogleMapController controller =
+        await ExampleGoogleMapController._init(
+      id,
+      widget.initialCameraPosition,
+      this,
+    );
+    _controller.complete(controller);
+    _updateTileOverlays();
+    widget.onMapCreated?.call(controller);
+  }
+
+  void onMarkerTap(MarkerId markerId) {
+    _markers[markerId]!.onTap?.call();
+  }
+
+  void onMarkerDragStart(MarkerId markerId, LatLng position) {
+    _markers[markerId]!.onDragStart?.call(position);
+  }
+
+  void onMarkerDrag(MarkerId markerId, LatLng position) {
+    _markers[markerId]!.onDrag?.call(position);
+  }
+
+  void onMarkerDragEnd(MarkerId markerId, LatLng position) {
+    _markers[markerId]!.onDragEnd?.call(position);
+  }
+
+  void onPolygonTap(PolygonId polygonId) {
+    _polygons[polygonId]!.onTap?.call();
+  }
+
+  void onPolylineTap(PolylineId polylineId) {
+    _polylines[polylineId]!.onTap?.call();
+  }
+
+  void onCircleTap(CircleId circleId) {
+    _circles[circleId]!.onTap?.call();
+  }
+
+  void onInfoWindowTap(MarkerId markerId) {
+    _markers[markerId]!.infoWindow.onTap?.call();
+  }
+
+  void onTap(LatLng position) {
+    widget.onTap?.call(position);
+  }
+
+  void onLongPress(LatLng position) {
+    widget.onLongPress?.call(position);
+  }
+}
+
+/// Builds a [MapConfiguration] from the given [map].
+MapConfiguration _configurationFromMapWidget(ExampleGoogleMap map) {
+  return MapConfiguration(
+    compassEnabled: map.compassEnabled,
+    mapToolbarEnabled: map.mapToolbarEnabled,
+    cameraTargetBounds: map.cameraTargetBounds,
+    mapType: map.mapType,
+    minMaxZoomPreference: map.minMaxZoomPreference,
+    rotateGesturesEnabled: map.rotateGesturesEnabled,
+    scrollGesturesEnabled: map.scrollGesturesEnabled,
+    tiltGesturesEnabled: map.tiltGesturesEnabled,
+    trackCameraPosition: map.onCameraMove != null,
+    zoomControlsEnabled: map.zoomControlsEnabled,
+    zoomGesturesEnabled: map.zoomGesturesEnabled,
+    liteModeEnabled: map.liteModeEnabled,
+    myLocationEnabled: map.myLocationEnabled,
+    myLocationButtonEnabled: map.myLocationButtonEnabled,
+    padding: map.padding,
+    indoorViewEnabled: map.indoorViewEnabled,
+    trafficEnabled: map.trafficEnabled,
+    buildingsEnabled: map.buildingsEnabled,
+  );
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/lite_mode.dart b/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/lite_mode.dart
new file mode 100644
index 0000000..f7bead9
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/lite_mode.dart
@@ -0,0 +1,47 @@
+// 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.
+
+// ignore_for_file: public_member_api_docs
+
+import 'package:flutter/material.dart';
+import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
+
+import 'example_google_map.dart';
+import 'page.dart';
+
+const CameraPosition _kInitialPosition =
+    CameraPosition(target: LatLng(-33.852, 151.211), zoom: 11.0);
+
+class LiteModePage extends GoogleMapExampleAppPage {
+  const LiteModePage({Key? key})
+      : super(const Icon(Icons.map), 'Lite mode', key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return const _LiteModeBody();
+  }
+}
+
+class _LiteModeBody extends StatelessWidget {
+  const _LiteModeBody();
+
+  @override
+  Widget build(BuildContext context) {
+    return const Card(
+      child: Padding(
+        padding: EdgeInsets.symmetric(vertical: 30.0),
+        child: Center(
+          child: SizedBox(
+            width: 300.0,
+            height: 300.0,
+            child: ExampleGoogleMap(
+              initialCameraPosition: _kInitialPosition,
+              liteModeEnabled: true,
+            ),
+          ),
+        ),
+      ),
+    );
+  }
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/main.dart b/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/main.dart
new file mode 100644
index 0000000..c02e4af
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/main.dart
@@ -0,0 +1,73 @@
+// 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 'package:flutter/material.dart';
+import 'package:google_maps_flutter_example/lite_mode.dart';
+
+import 'animate_camera.dart';
+import 'map_click.dart';
+import 'map_coordinates.dart';
+import 'map_ui.dart';
+import 'marker_icons.dart';
+import 'move_camera.dart';
+import 'padding.dart';
+import 'page.dart';
+import 'place_circle.dart';
+import 'place_marker.dart';
+import 'place_polygon.dart';
+import 'place_polyline.dart';
+import 'scrolling_map.dart';
+import 'snapshot.dart';
+import 'tile_overlay.dart';
+
+final List<GoogleMapExampleAppPage> _allPages = <GoogleMapExampleAppPage>[
+  const MapUiPage(),
+  const MapCoordinatesPage(),
+  const MapClickPage(),
+  const AnimateCameraPage(),
+  const MoveCameraPage(),
+  const PlaceMarkerPage(),
+  const MarkerIconsPage(),
+  const ScrollingMapPage(),
+  const PlacePolylinePage(),
+  const PlacePolygonPage(),
+  const PlaceCirclePage(),
+  const PaddingPage(),
+  const SnapshotPage(),
+  const LiteModePage(),
+  const TileOverlayPage(),
+];
+
+/// MapsDemo is the Main Application.
+class MapsDemo extends StatelessWidget {
+  /// Default Constructor
+  const MapsDemo({Key? key}) : super(key: key);
+
+  void _pushPage(BuildContext context, GoogleMapExampleAppPage page) {
+    Navigator.of(context).push(MaterialPageRoute<void>(
+        builder: (_) => Scaffold(
+              appBar: AppBar(title: Text(page.title)),
+              body: page,
+            )));
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Scaffold(
+      appBar: AppBar(title: const Text('GoogleMaps examples')),
+      body: ListView.builder(
+        itemCount: _allPages.length,
+        itemBuilder: (_, int index) => ListTile(
+          leading: _allPages[index].leading,
+          title: Text(_allPages[index].title),
+          onTap: () => _pushPage(context, _allPages[index]),
+        ),
+      ),
+    );
+  }
+}
+
+void main() {
+  runApp(const MaterialApp(home: MapsDemo()));
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/map_click.dart b/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/map_click.dart
new file mode 100644
index 0000000..20718d9
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/map_click.dart
@@ -0,0 +1,106 @@
+// 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.
+
+// ignore_for_file: public_member_api_docs
+
+import 'package:flutter/material.dart';
+import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
+
+import 'example_google_map.dart';
+import 'page.dart';
+
+const CameraPosition _kInitialPosition =
+    CameraPosition(target: LatLng(-33.852, 151.211), zoom: 11.0);
+
+class MapClickPage extends GoogleMapExampleAppPage {
+  const MapClickPage({Key? key})
+      : super(const Icon(Icons.mouse), 'Map click', key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return const _MapClickBody();
+  }
+}
+
+class _MapClickBody extends StatefulWidget {
+  const _MapClickBody();
+
+  @override
+  State<StatefulWidget> createState() => _MapClickBodyState();
+}
+
+class _MapClickBodyState extends State<_MapClickBody> {
+  _MapClickBodyState();
+
+  ExampleGoogleMapController? mapController;
+  LatLng? _lastTap;
+  LatLng? _lastLongPress;
+
+  @override
+  Widget build(BuildContext context) {
+    final ExampleGoogleMap googleMap = ExampleGoogleMap(
+      onMapCreated: onMapCreated,
+      initialCameraPosition: _kInitialPosition,
+      onTap: (LatLng pos) {
+        setState(() {
+          _lastTap = pos;
+        });
+      },
+      onLongPress: (LatLng pos) {
+        setState(() {
+          _lastLongPress = pos;
+        });
+      },
+    );
+
+    final List<Widget> columnChildren = <Widget>[
+      Padding(
+        padding: const EdgeInsets.all(10.0),
+        child: Center(
+          child: SizedBox(
+            width: 300.0,
+            height: 200.0,
+            child: googleMap,
+          ),
+        ),
+      ),
+    ];
+
+    if (mapController != null) {
+      final String lastTap = 'Tap:\n${_lastTap ?? ""}\n';
+      final String lastLongPress = 'Long press:\n${_lastLongPress ?? ""}';
+      columnChildren.add(Center(
+          child: Text(
+        lastTap,
+        textAlign: TextAlign.center,
+      )));
+      columnChildren.add(Center(
+          child: Text(
+        _lastTap != null ? 'Tapped' : '',
+        textAlign: TextAlign.center,
+      )));
+      columnChildren.add(Center(
+          child: Text(
+        lastLongPress,
+        textAlign: TextAlign.center,
+      )));
+      columnChildren.add(Center(
+          child: Text(
+        _lastLongPress != null ? 'Long pressed' : '',
+        textAlign: TextAlign.center,
+      )));
+    }
+    return Column(
+      mainAxisAlignment: MainAxisAlignment.start,
+      crossAxisAlignment: CrossAxisAlignment.stretch,
+      children: columnChildren,
+    );
+  }
+
+  Future<void> onMapCreated(ExampleGoogleMapController controller) async {
+    setState(() {
+      mapController = controller;
+    });
+  }
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/map_coordinates.dart b/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/map_coordinates.dart
new file mode 100644
index 0000000..185a97e
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/map_coordinates.dart
@@ -0,0 +1,100 @@
+// 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.
+
+// ignore_for_file: public_member_api_docs
+
+import 'package:flutter/material.dart';
+import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
+
+import 'example_google_map.dart';
+import 'page.dart';
+
+const CameraPosition _kInitialPosition =
+    CameraPosition(target: LatLng(-33.852, 151.211), zoom: 11.0);
+
+class MapCoordinatesPage extends GoogleMapExampleAppPage {
+  const MapCoordinatesPage({Key? key})
+      : super(const Icon(Icons.map), 'Map coordinates', key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return const _MapCoordinatesBody();
+  }
+}
+
+class _MapCoordinatesBody extends StatefulWidget {
+  const _MapCoordinatesBody();
+
+  @override
+  State<StatefulWidget> createState() => _MapCoordinatesBodyState();
+}
+
+class _MapCoordinatesBodyState extends State<_MapCoordinatesBody> {
+  _MapCoordinatesBodyState();
+
+  ExampleGoogleMapController? mapController;
+  LatLngBounds _visibleRegion = LatLngBounds(
+    southwest: const LatLng(0, 0),
+    northeast: const LatLng(0, 0),
+  );
+
+  @override
+  Widget build(BuildContext context) {
+    final ExampleGoogleMap googleMap = ExampleGoogleMap(
+      onMapCreated: onMapCreated,
+      initialCameraPosition: _kInitialPosition,
+      onCameraIdle:
+          _updateVisibleRegion, // https://github.com/flutter/flutter/issues/54758
+    );
+
+    return NotificationListener<ScrollNotification>(
+      onNotification: (ScrollNotification scrollState) {
+        _updateVisibleRegion();
+        return true;
+      },
+      child: ListView(
+        children: <Widget>[
+          Padding(
+            padding: const EdgeInsets.all(10.0),
+            child: Center(
+              child: SizedBox(
+                width: 300.0,
+                height: 200.0,
+                child: googleMap,
+              ),
+            ),
+          ),
+          if (mapController != null)
+            Center(
+              child: Text('VisibleRegion:'
+                  '\nnortheast: ${_visibleRegion.northeast},'
+                  '\nsouthwest: ${_visibleRegion.southwest}'),
+            ),
+          // Add a block at the bottom of this list to allow validation that the visible region of the map
+          // does not change when scrolled under the safe view on iOS.
+          // https://github.com/flutter/flutter/issues/107913
+          Container(
+            width: 300,
+            height: 1000,
+          ),
+        ],
+      ),
+    );
+  }
+
+  Future<void> onMapCreated(ExampleGoogleMapController controller) async {
+    final LatLngBounds visibleRegion = await controller.getVisibleRegion();
+    setState(() {
+      mapController = controller;
+      _visibleRegion = visibleRegion;
+    });
+  }
+
+  Future<void> _updateVisibleRegion() async {
+    final LatLngBounds visibleRegion = await mapController!.getVisibleRegion();
+    setState(() {
+      _visibleRegion = visibleRegion;
+    });
+  }
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/map_ui.dart b/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/map_ui.dart
new file mode 100644
index 0000000..6c38e14
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/map_ui.dart
@@ -0,0 +1,358 @@
+// 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.
+
+// ignore_for_file: public_member_api_docs
+
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart' show rootBundle;
+import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
+
+import 'example_google_map.dart';
+import 'page.dart';
+
+final LatLngBounds sydneyBounds = LatLngBounds(
+  southwest: const LatLng(-34.022631, 150.620685),
+  northeast: const LatLng(-33.571835, 151.325952),
+);
+
+class MapUiPage extends GoogleMapExampleAppPage {
+  const MapUiPage({Key? key})
+      : super(const Icon(Icons.map), 'User interface', key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return const MapUiBody();
+  }
+}
+
+class MapUiBody extends StatefulWidget {
+  const MapUiBody({Key? key}) : super(key: key);
+
+  @override
+  State<StatefulWidget> createState() => MapUiBodyState();
+}
+
+class MapUiBodyState extends State<MapUiBody> {
+  MapUiBodyState();
+
+  static const CameraPosition _kInitialPosition = CameraPosition(
+    target: LatLng(-33.852, 151.211),
+    zoom: 11.0,
+  );
+
+  CameraPosition _position = _kInitialPosition;
+  bool _isMapCreated = false;
+  final bool _isMoving = false;
+  bool _compassEnabled = true;
+  bool _mapToolbarEnabled = true;
+  CameraTargetBounds _cameraTargetBounds = CameraTargetBounds.unbounded;
+  MinMaxZoomPreference _minMaxZoomPreference = MinMaxZoomPreference.unbounded;
+  MapType _mapType = MapType.normal;
+  bool _rotateGesturesEnabled = true;
+  bool _scrollGesturesEnabled = true;
+  bool _tiltGesturesEnabled = true;
+  bool _zoomControlsEnabled = false;
+  bool _zoomGesturesEnabled = true;
+  bool _indoorViewEnabled = true;
+  bool _myLocationEnabled = true;
+  bool _myTrafficEnabled = false;
+  bool _myLocationButtonEnabled = true;
+  late ExampleGoogleMapController _controller;
+  bool _nightMode = false;
+
+  @override
+  void initState() {
+    super.initState();
+  }
+
+  @override
+  void dispose() {
+    super.dispose();
+  }
+
+  Widget _compassToggler() {
+    return TextButton(
+      child: Text('${_compassEnabled ? 'disable' : 'enable'} compass'),
+      onPressed: () {
+        setState(() {
+          _compassEnabled = !_compassEnabled;
+        });
+      },
+    );
+  }
+
+  Widget _mapToolbarToggler() {
+    return TextButton(
+      child: Text('${_mapToolbarEnabled ? 'disable' : 'enable'} map toolbar'),
+      onPressed: () {
+        setState(() {
+          _mapToolbarEnabled = !_mapToolbarEnabled;
+        });
+      },
+    );
+  }
+
+  Widget _latLngBoundsToggler() {
+    return TextButton(
+      child: Text(
+        _cameraTargetBounds.bounds == null
+            ? 'bound camera target'
+            : 'release camera target',
+      ),
+      onPressed: () {
+        setState(() {
+          _cameraTargetBounds = _cameraTargetBounds.bounds == null
+              ? CameraTargetBounds(sydneyBounds)
+              : CameraTargetBounds.unbounded;
+        });
+      },
+    );
+  }
+
+  Widget _zoomBoundsToggler() {
+    return TextButton(
+      child: Text(_minMaxZoomPreference.minZoom == null
+          ? 'bound zoom'
+          : 'release zoom'),
+      onPressed: () {
+        setState(() {
+          _minMaxZoomPreference = _minMaxZoomPreference.minZoom == null
+              ? const MinMaxZoomPreference(12.0, 16.0)
+              : MinMaxZoomPreference.unbounded;
+        });
+      },
+    );
+  }
+
+  Widget _mapTypeCycler() {
+    final MapType nextType =
+        MapType.values[(_mapType.index + 1) % MapType.values.length];
+    return TextButton(
+      child: Text('change map type to $nextType'),
+      onPressed: () {
+        setState(() {
+          _mapType = nextType;
+        });
+      },
+    );
+  }
+
+  Widget _rotateToggler() {
+    return TextButton(
+      child: Text('${_rotateGesturesEnabled ? 'disable' : 'enable'} rotate'),
+      onPressed: () {
+        setState(() {
+          _rotateGesturesEnabled = !_rotateGesturesEnabled;
+        });
+      },
+    );
+  }
+
+  Widget _scrollToggler() {
+    return TextButton(
+      child: Text('${_scrollGesturesEnabled ? 'disable' : 'enable'} scroll'),
+      onPressed: () {
+        setState(() {
+          _scrollGesturesEnabled = !_scrollGesturesEnabled;
+        });
+      },
+    );
+  }
+
+  Widget _tiltToggler() {
+    return TextButton(
+      child: Text('${_tiltGesturesEnabled ? 'disable' : 'enable'} tilt'),
+      onPressed: () {
+        setState(() {
+          _tiltGesturesEnabled = !_tiltGesturesEnabled;
+        });
+      },
+    );
+  }
+
+  Widget _zoomToggler() {
+    return TextButton(
+      child: Text('${_zoomGesturesEnabled ? 'disable' : 'enable'} zoom'),
+      onPressed: () {
+        setState(() {
+          _zoomGesturesEnabled = !_zoomGesturesEnabled;
+        });
+      },
+    );
+  }
+
+  Widget _zoomControlsToggler() {
+    return TextButton(
+      child:
+          Text('${_zoomControlsEnabled ? 'disable' : 'enable'} zoom controls'),
+      onPressed: () {
+        setState(() {
+          _zoomControlsEnabled = !_zoomControlsEnabled;
+        });
+      },
+    );
+  }
+
+  Widget _indoorViewToggler() {
+    return TextButton(
+      child: Text('${_indoorViewEnabled ? 'disable' : 'enable'} indoor'),
+      onPressed: () {
+        setState(() {
+          _indoorViewEnabled = !_indoorViewEnabled;
+        });
+      },
+    );
+  }
+
+  Widget _myLocationToggler() {
+    return TextButton(
+      child: Text(
+          '${_myLocationEnabled ? 'disable' : 'enable'} my location marker'),
+      onPressed: () {
+        setState(() {
+          _myLocationEnabled = !_myLocationEnabled;
+        });
+      },
+    );
+  }
+
+  Widget _myLocationButtonToggler() {
+    return TextButton(
+      child: Text(
+          '${_myLocationButtonEnabled ? 'disable' : 'enable'} my location button'),
+      onPressed: () {
+        setState(() {
+          _myLocationButtonEnabled = !_myLocationButtonEnabled;
+        });
+      },
+    );
+  }
+
+  Widget _myTrafficToggler() {
+    return TextButton(
+      child: Text('${_myTrafficEnabled ? 'disable' : 'enable'} my traffic'),
+      onPressed: () {
+        setState(() {
+          _myTrafficEnabled = !_myTrafficEnabled;
+        });
+      },
+    );
+  }
+
+  Future<String> _getFileData(String path) async {
+    return await rootBundle.loadString(path);
+  }
+
+  void _setMapStyle(String mapStyle) {
+    setState(() {
+      _nightMode = true;
+      _controller.setMapStyle(mapStyle);
+    });
+  }
+
+  // Should only be called if _isMapCreated is true.
+  Widget _nightModeToggler() {
+    assert(_isMapCreated);
+    return TextButton(
+      child: Text('${_nightMode ? 'disable' : 'enable'} night mode'),
+      onPressed: () {
+        if (_nightMode) {
+          setState(() {
+            _nightMode = false;
+            _controller.setMapStyle(null);
+          });
+        } else {
+          _getFileData('assets/night_mode.json').then(_setMapStyle);
+        }
+      },
+    );
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final ExampleGoogleMap googleMap = ExampleGoogleMap(
+      onMapCreated: onMapCreated,
+      initialCameraPosition: _kInitialPosition,
+      compassEnabled: _compassEnabled,
+      mapToolbarEnabled: _mapToolbarEnabled,
+      cameraTargetBounds: _cameraTargetBounds,
+      minMaxZoomPreference: _minMaxZoomPreference,
+      mapType: _mapType,
+      rotateGesturesEnabled: _rotateGesturesEnabled,
+      scrollGesturesEnabled: _scrollGesturesEnabled,
+      tiltGesturesEnabled: _tiltGesturesEnabled,
+      zoomGesturesEnabled: _zoomGesturesEnabled,
+      zoomControlsEnabled: _zoomControlsEnabled,
+      indoorViewEnabled: _indoorViewEnabled,
+      myLocationEnabled: _myLocationEnabled,
+      myLocationButtonEnabled: _myLocationButtonEnabled,
+      trafficEnabled: _myTrafficEnabled,
+      onCameraMove: _updateCameraPosition,
+    );
+
+    final List<Widget> columnChildren = <Widget>[
+      Padding(
+        padding: const EdgeInsets.all(10.0),
+        child: Center(
+          child: SizedBox(
+            width: 300.0,
+            height: 200.0,
+            child: googleMap,
+          ),
+        ),
+      ),
+    ];
+
+    if (_isMapCreated) {
+      columnChildren.add(
+        Expanded(
+          child: ListView(
+            children: <Widget>[
+              Text('camera bearing: ${_position.bearing}'),
+              Text(
+                  'camera target: ${_position.target.latitude.toStringAsFixed(4)},'
+                  '${_position.target.longitude.toStringAsFixed(4)}'),
+              Text('camera zoom: ${_position.zoom}'),
+              Text('camera tilt: ${_position.tilt}'),
+              Text(_isMoving ? '(Camera moving)' : '(Camera idle)'),
+              _compassToggler(),
+              _mapToolbarToggler(),
+              _latLngBoundsToggler(),
+              _mapTypeCycler(),
+              _zoomBoundsToggler(),
+              _rotateToggler(),
+              _scrollToggler(),
+              _tiltToggler(),
+              _zoomToggler(),
+              _zoomControlsToggler(),
+              _indoorViewToggler(),
+              _myLocationToggler(),
+              _myLocationButtonToggler(),
+              _myTrafficToggler(),
+              _nightModeToggler(),
+            ],
+          ),
+        ),
+      );
+    }
+    return Column(
+      mainAxisAlignment: MainAxisAlignment.start,
+      crossAxisAlignment: CrossAxisAlignment.stretch,
+      children: columnChildren,
+    );
+  }
+
+  void _updateCameraPosition(CameraPosition position) {
+    setState(() {
+      _position = position;
+    });
+  }
+
+  void onMapCreated(ExampleGoogleMapController controller) {
+    setState(() {
+      _controller = controller;
+      _isMapCreated = true;
+    });
+  }
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/marker_icons.dart b/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/marker_icons.dart
new file mode 100644
index 0000000..fe28eb6
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/marker_icons.dart
@@ -0,0 +1,98 @@
+// 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.
+
+// ignore_for_file: public_member_api_docs
+// ignore_for_file: unawaited_futures
+
+import 'package:flutter/material.dart';
+import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
+
+import 'example_google_map.dart';
+import 'page.dart';
+
+class MarkerIconsPage extends GoogleMapExampleAppPage {
+  const MarkerIconsPage({Key? key})
+      : super(const Icon(Icons.image), 'Marker icons', key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return const MarkerIconsBody();
+  }
+}
+
+class MarkerIconsBody extends StatefulWidget {
+  const MarkerIconsBody({Key? key}) : super(key: key);
+
+  @override
+  State<StatefulWidget> createState() => MarkerIconsBodyState();
+}
+
+const LatLng _kMapCenter = LatLng(52.4478, -3.5402);
+
+class MarkerIconsBodyState extends State<MarkerIconsBody> {
+  ExampleGoogleMapController? controller;
+  BitmapDescriptor? _markerIcon;
+
+  @override
+  Widget build(BuildContext context) {
+    _createMarkerImageFromAsset(context);
+    return Column(
+      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+      crossAxisAlignment: CrossAxisAlignment.stretch,
+      children: <Widget>[
+        Center(
+          child: SizedBox(
+            width: 350.0,
+            height: 300.0,
+            child: ExampleGoogleMap(
+              initialCameraPosition: const CameraPosition(
+                target: _kMapCenter,
+                zoom: 7.0,
+              ),
+              markers: <Marker>{_createMarker()},
+              onMapCreated: _onMapCreated,
+            ),
+          ),
+        )
+      ],
+    );
+  }
+
+  Marker _createMarker() {
+    if (_markerIcon != null) {
+      return Marker(
+        markerId: const MarkerId('marker_1'),
+        position: _kMapCenter,
+        icon: _markerIcon!,
+      );
+    } else {
+      return const Marker(
+        markerId: MarkerId('marker_1'),
+        position: _kMapCenter,
+      );
+    }
+  }
+
+  Future<void> _createMarkerImageFromAsset(BuildContext context) async {
+    if (_markerIcon == null) {
+      final ImageConfiguration imageConfiguration =
+          createLocalImageConfiguration(context, size: const Size.square(48));
+      BitmapDescriptor.fromAssetImage(
+              imageConfiguration, 'assets/red_square.png')
+          .then(_updateBitmap);
+    }
+  }
+
+  void _updateBitmap(BitmapDescriptor bitmap) {
+    setState(() {
+      _markerIcon = bitmap;
+    });
+  }
+
+  void _onMapCreated(ExampleGoogleMapController controllerParam) {
+    setState(() {
+      controller = controllerParam;
+    });
+  }
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/move_camera.dart b/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/move_camera.dart
new file mode 100644
index 0000000..7f44d89
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/move_camera.dart
@@ -0,0 +1,171 @@
+// 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.
+
+// ignore_for_file: public_member_api_docs
+
+import 'package:flutter/material.dart';
+import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
+
+import 'example_google_map.dart';
+import 'page.dart';
+
+class MoveCameraPage extends GoogleMapExampleAppPage {
+  const MoveCameraPage({Key? key})
+      : super(const Icon(Icons.map), 'Camera control', key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return const MoveCamera();
+  }
+}
+
+class MoveCamera extends StatefulWidget {
+  const MoveCamera({Key? key}) : super(key: key);
+  @override
+  State createState() => MoveCameraState();
+}
+
+class MoveCameraState extends State<MoveCamera> {
+  ExampleGoogleMapController? mapController;
+
+  // ignore: use_setters_to_change_properties
+  void _onMapCreated(ExampleGoogleMapController controller) {
+    mapController = controller;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return Column(
+      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+      crossAxisAlignment: CrossAxisAlignment.stretch,
+      children: <Widget>[
+        Center(
+          child: SizedBox(
+            width: 300.0,
+            height: 200.0,
+            child: ExampleGoogleMap(
+              onMapCreated: _onMapCreated,
+              initialCameraPosition:
+                  const CameraPosition(target: LatLng(0.0, 0.0)),
+            ),
+          ),
+        ),
+        Row(
+          mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+          children: <Widget>[
+            Column(
+              children: <Widget>[
+                TextButton(
+                  onPressed: () {
+                    mapController?.moveCamera(
+                      CameraUpdate.newCameraPosition(
+                        const CameraPosition(
+                          bearing: 270.0,
+                          target: LatLng(51.5160895, -0.1294527),
+                          tilt: 30.0,
+                          zoom: 17.0,
+                        ),
+                      ),
+                    );
+                  },
+                  child: const Text('newCameraPosition'),
+                ),
+                TextButton(
+                  onPressed: () {
+                    mapController?.moveCamera(
+                      CameraUpdate.newLatLng(
+                        const LatLng(56.1725505, 10.1850512),
+                      ),
+                    );
+                  },
+                  child: const Text('newLatLng'),
+                ),
+                TextButton(
+                  onPressed: () {
+                    mapController?.moveCamera(
+                      CameraUpdate.newLatLngBounds(
+                        LatLngBounds(
+                          southwest: const LatLng(-38.483935, 113.248673),
+                          northeast: const LatLng(-8.982446, 153.823821),
+                        ),
+                        10.0,
+                      ),
+                    );
+                  },
+                  child: const Text('newLatLngBounds'),
+                ),
+                TextButton(
+                  onPressed: () {
+                    mapController?.moveCamera(
+                      CameraUpdate.newLatLngZoom(
+                        const LatLng(37.4231613, -122.087159),
+                        11.0,
+                      ),
+                    );
+                  },
+                  child: const Text('newLatLngZoom'),
+                ),
+                TextButton(
+                  onPressed: () {
+                    mapController?.moveCamera(
+                      CameraUpdate.scrollBy(150.0, -225.0),
+                    );
+                  },
+                  child: const Text('scrollBy'),
+                ),
+              ],
+            ),
+            Column(
+              children: <Widget>[
+                TextButton(
+                  onPressed: () {
+                    mapController?.moveCamera(
+                      CameraUpdate.zoomBy(
+                        -0.5,
+                        const Offset(30.0, 20.0),
+                      ),
+                    );
+                  },
+                  child: const Text('zoomBy with focus'),
+                ),
+                TextButton(
+                  onPressed: () {
+                    mapController?.moveCamera(
+                      CameraUpdate.zoomBy(-0.5),
+                    );
+                  },
+                  child: const Text('zoomBy'),
+                ),
+                TextButton(
+                  onPressed: () {
+                    mapController?.moveCamera(
+                      CameraUpdate.zoomIn(),
+                    );
+                  },
+                  child: const Text('zoomIn'),
+                ),
+                TextButton(
+                  onPressed: () {
+                    mapController?.moveCamera(
+                      CameraUpdate.zoomOut(),
+                    );
+                  },
+                  child: const Text('zoomOut'),
+                ),
+                TextButton(
+                  onPressed: () {
+                    mapController?.moveCamera(
+                      CameraUpdate.zoomTo(16.0),
+                    );
+                  },
+                  child: const Text('zoomTo'),
+                ),
+              ],
+            ),
+          ],
+        )
+      ],
+    );
+  }
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/padding.dart b/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/padding.dart
new file mode 100644
index 0000000..d346549
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/padding.dart
@@ -0,0 +1,181 @@
+// 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.
+
+// ignore_for_file: public_member_api_docs
+
+import 'package:flutter/material.dart';
+import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
+
+import 'example_google_map.dart';
+import 'page.dart';
+
+class PaddingPage extends GoogleMapExampleAppPage {
+  const PaddingPage({Key? key})
+      : super(const Icon(Icons.map), 'Add padding to the map', key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return const MarkerIconsBody();
+  }
+}
+
+class MarkerIconsBody extends StatefulWidget {
+  const MarkerIconsBody({Key? key}) : super(key: key);
+
+  @override
+  State<StatefulWidget> createState() => MarkerIconsBodyState();
+}
+
+const LatLng _kMapCenter = LatLng(52.4478, -3.5402);
+
+class MarkerIconsBodyState extends State<MarkerIconsBody> {
+  ExampleGoogleMapController? controller;
+
+  EdgeInsets _padding = const EdgeInsets.all(0);
+
+  @override
+  Widget build(BuildContext context) {
+    final ExampleGoogleMap googleMap = ExampleGoogleMap(
+      onMapCreated: _onMapCreated,
+      initialCameraPosition: const CameraPosition(
+        target: _kMapCenter,
+        zoom: 7.0,
+      ),
+      padding: _padding,
+    );
+
+    final List<Widget> columnChildren = <Widget>[
+      Padding(
+        padding: const EdgeInsets.all(10.0),
+        child: Center(
+          child: SizedBox(
+            width: 300.0,
+            height: 200.0,
+            child: googleMap,
+          ),
+        ),
+      ),
+      const Padding(
+        padding: EdgeInsets.only(top: 20),
+        child: Center(
+          child: Text(
+            'Enter Padding Below',
+            style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
+          ),
+        ),
+      ),
+    ];
+
+    columnChildren.addAll(<Widget>[_paddingInput(), _buttons()]);
+
+    return Column(
+      mainAxisAlignment: MainAxisAlignment.start,
+      crossAxisAlignment: CrossAxisAlignment.stretch,
+      children: columnChildren,
+    );
+  }
+
+  void _onMapCreated(ExampleGoogleMapController controllerParam) {
+    setState(() {
+      controller = controllerParam;
+    });
+  }
+
+  final TextEditingController _topController = TextEditingController();
+  final TextEditingController _bottomController = TextEditingController();
+  final TextEditingController _leftController = TextEditingController();
+  final TextEditingController _rightController = TextEditingController();
+
+  Widget _paddingInput() {
+    return Padding(
+      padding: const EdgeInsets.all(16.0),
+      child: Row(
+        children: <Widget>[
+          Flexible(
+            flex: 2,
+            child: TextField(
+              controller: _topController,
+              keyboardType: TextInputType.number,
+              textAlign: TextAlign.center,
+              decoration: const InputDecoration(
+                hintText: 'Top',
+              ),
+            ),
+          ),
+          const Spacer(),
+          Flexible(
+            flex: 2,
+            child: TextField(
+              controller: _bottomController,
+              keyboardType: TextInputType.number,
+              textAlign: TextAlign.center,
+              decoration: const InputDecoration(
+                hintText: 'Bottom',
+              ),
+            ),
+          ),
+          const Spacer(),
+          Flexible(
+            flex: 2,
+            child: TextField(
+              controller: _leftController,
+              keyboardType: TextInputType.number,
+              textAlign: TextAlign.center,
+              decoration: const InputDecoration(
+                hintText: 'Left',
+              ),
+            ),
+          ),
+          const Spacer(),
+          Flexible(
+            flex: 2,
+            child: TextField(
+              controller: _rightController,
+              keyboardType: TextInputType.number,
+              textAlign: TextAlign.center,
+              decoration: const InputDecoration(
+                hintText: 'Right',
+              ),
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+
+  Widget _buttons() {
+    return Padding(
+      padding: const EdgeInsets.symmetric(horizontal: 16),
+      child: Row(
+        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+        children: <Widget>[
+          TextButton(
+            child: const Text('Set Padding'),
+            onPressed: () {
+              setState(() {
+                _padding = EdgeInsets.fromLTRB(
+                    double.tryParse(_leftController.value.text) ?? 0,
+                    double.tryParse(_topController.value.text) ?? 0,
+                    double.tryParse(_rightController.value.text) ?? 0,
+                    double.tryParse(_bottomController.value.text) ?? 0);
+              });
+            },
+          ),
+          TextButton(
+            child: const Text('Reset Padding'),
+            onPressed: () {
+              setState(() {
+                _topController.clear();
+                _bottomController.clear();
+                _leftController.clear();
+                _rightController.clear();
+                _padding = const EdgeInsets.all(0);
+              });
+            },
+          )
+        ],
+      ),
+    );
+  }
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/page.dart b/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/page.dart
new file mode 100644
index 0000000..eb01ab0
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/page.dart
@@ -0,0 +1,15 @@
+// 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.
+
+// ignore_for_file: public_member_api_docs
+
+import 'package:flutter/material.dart';
+
+abstract class GoogleMapExampleAppPage extends StatelessWidget {
+  const GoogleMapExampleAppPage(this.leading, this.title, {Key? key})
+      : super(key: key);
+
+  final Widget leading;
+  final String title;
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/place_circle.dart b/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/place_circle.dart
new file mode 100644
index 0000000..9dc5760
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/place_circle.dart
@@ -0,0 +1,232 @@
+// 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.
+
+// ignore_for_file: public_member_api_docs
+
+import 'package:flutter/material.dart';
+import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
+
+import 'example_google_map.dart';
+import 'page.dart';
+
+class PlaceCirclePage extends GoogleMapExampleAppPage {
+  const PlaceCirclePage({Key? key})
+      : super(const Icon(Icons.linear_scale), 'Place circle', key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return const PlaceCircleBody();
+  }
+}
+
+class PlaceCircleBody extends StatefulWidget {
+  const PlaceCircleBody({Key? key}) : super(key: key);
+
+  @override
+  State<StatefulWidget> createState() => PlaceCircleBodyState();
+}
+
+class PlaceCircleBodyState extends State<PlaceCircleBody> {
+  PlaceCircleBodyState();
+
+  ExampleGoogleMapController? controller;
+  Map<CircleId, Circle> circles = <CircleId, Circle>{};
+  int _circleIdCounter = 1;
+  CircleId? selectedCircle;
+
+  // Values when toggling circle color
+  int fillColorsIndex = 0;
+  int strokeColorsIndex = 0;
+  List<Color> colors = <Color>[
+    Colors.purple,
+    Colors.red,
+    Colors.green,
+    Colors.pink,
+  ];
+
+  // Values when toggling circle stroke width
+  int widthsIndex = 0;
+  List<int> widths = <int>[10, 20, 5];
+
+  // ignore: use_setters_to_change_properties
+  void _onMapCreated(ExampleGoogleMapController controller) {
+    this.controller = controller;
+  }
+
+  @override
+  void dispose() {
+    super.dispose();
+  }
+
+  void _onCircleTapped(CircleId circleId) {
+    setState(() {
+      selectedCircle = circleId;
+    });
+  }
+
+  void _remove(CircleId circleId) {
+    setState(() {
+      if (circles.containsKey(circleId)) {
+        circles.remove(circleId);
+      }
+      if (circleId == selectedCircle) {
+        selectedCircle = null;
+      }
+    });
+  }
+
+  void _add() {
+    final int circleCount = circles.length;
+
+    if (circleCount == 12) {
+      return;
+    }
+
+    final String circleIdVal = 'circle_id_$_circleIdCounter';
+    _circleIdCounter++;
+    final CircleId circleId = CircleId(circleIdVal);
+
+    final Circle circle = Circle(
+      circleId: circleId,
+      consumeTapEvents: true,
+      strokeColor: Colors.orange,
+      fillColor: Colors.green,
+      strokeWidth: 5,
+      center: _createCenter(),
+      radius: 50000,
+      onTap: () {
+        _onCircleTapped(circleId);
+      },
+    );
+
+    setState(() {
+      circles[circleId] = circle;
+    });
+  }
+
+  void _toggleVisible(CircleId circleId) {
+    final Circle circle = circles[circleId]!;
+    setState(() {
+      circles[circleId] = circle.copyWith(
+        visibleParam: !circle.visible,
+      );
+    });
+  }
+
+  void _changeFillColor(CircleId circleId) {
+    final Circle circle = circles[circleId]!;
+    setState(() {
+      circles[circleId] = circle.copyWith(
+        fillColorParam: colors[++fillColorsIndex % colors.length],
+      );
+    });
+  }
+
+  void _changeStrokeColor(CircleId circleId) {
+    final Circle circle = circles[circleId]!;
+    setState(() {
+      circles[circleId] = circle.copyWith(
+        strokeColorParam: colors[++strokeColorsIndex % colors.length],
+      );
+    });
+  }
+
+  void _changeStrokeWidth(CircleId circleId) {
+    final Circle circle = circles[circleId]!;
+    setState(() {
+      circles[circleId] = circle.copyWith(
+        strokeWidthParam: widths[++widthsIndex % widths.length],
+      );
+    });
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final CircleId? selectedId = selectedCircle;
+    return Column(
+      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+      crossAxisAlignment: CrossAxisAlignment.stretch,
+      children: <Widget>[
+        Center(
+          child: SizedBox(
+            width: 350.0,
+            height: 300.0,
+            child: ExampleGoogleMap(
+              initialCameraPosition: const CameraPosition(
+                target: LatLng(52.4478, -3.5402),
+                zoom: 7.0,
+              ),
+              circles: Set<Circle>.of(circles.values),
+              onMapCreated: _onMapCreated,
+            ),
+          ),
+        ),
+        Expanded(
+          child: SingleChildScrollView(
+            child: Row(
+              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+              children: <Widget>[
+                Row(
+                  children: <Widget>[
+                    Column(
+                      children: <Widget>[
+                        TextButton(
+                          onPressed: _add,
+                          child: const Text('add'),
+                        ),
+                        TextButton(
+                          onPressed: (selectedId == null)
+                              ? null
+                              : () => _remove(selectedId),
+                          child: const Text('remove'),
+                        ),
+                        TextButton(
+                          onPressed: (selectedId == null)
+                              ? null
+                              : () => _toggleVisible(selectedId),
+                          child: const Text('toggle visible'),
+                        ),
+                      ],
+                    ),
+                    Column(
+                      children: <Widget>[
+                        TextButton(
+                          onPressed: (selectedId == null)
+                              ? null
+                              : () => _changeStrokeWidth(selectedId),
+                          child: const Text('change stroke width'),
+                        ),
+                        TextButton(
+                          onPressed: (selectedId == null)
+                              ? null
+                              : () => _changeStrokeColor(selectedId),
+                          child: const Text('change stroke color'),
+                        ),
+                        TextButton(
+                          onPressed: (selectedId == null)
+                              ? null
+                              : () => _changeFillColor(selectedId),
+                          child: const Text('change fill color'),
+                        ),
+                      ],
+                    )
+                  ],
+                )
+              ],
+            ),
+          ),
+        ),
+      ],
+    );
+  }
+
+  LatLng _createCenter() {
+    final double offset = _circleIdCounter.ceilToDouble();
+    return _createLatLng(51.4816 + offset * 0.2, -3.1791);
+  }
+
+  LatLng _createLatLng(double lat, double lng) {
+    return LatLng(lat, lng);
+  }
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/place_marker.dart b/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/place_marker.dart
new file mode 100644
index 0000000..b7d2a69
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/place_marker.dart
@@ -0,0 +1,422 @@
+// 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.
+
+// ignore_for_file: public_member_api_docs
+
+import 'dart:async';
+import 'dart:math';
+import 'dart:typed_data';
+import 'dart:ui';
+
+import 'package:flutter/material.dart';
+import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
+
+import 'example_google_map.dart';
+import 'page.dart';
+
+class PlaceMarkerPage extends GoogleMapExampleAppPage {
+  const PlaceMarkerPage({Key? key})
+      : super(const Icon(Icons.place), 'Place marker', key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return const PlaceMarkerBody();
+  }
+}
+
+class PlaceMarkerBody extends StatefulWidget {
+  const PlaceMarkerBody({Key? key}) : super(key: key);
+
+  @override
+  State<StatefulWidget> createState() => PlaceMarkerBodyState();
+}
+
+typedef MarkerUpdateAction = Marker Function(Marker marker);
+
+class PlaceMarkerBodyState extends State<PlaceMarkerBody> {
+  PlaceMarkerBodyState();
+  static const LatLng center = LatLng(-33.86711, 151.1947171);
+
+  ExampleGoogleMapController? controller;
+  Map<MarkerId, Marker> markers = <MarkerId, Marker>{};
+  MarkerId? selectedMarker;
+  int _markerIdCounter = 1;
+  LatLng? markerPosition;
+
+  // ignore: use_setters_to_change_properties
+  void _onMapCreated(ExampleGoogleMapController controller) {
+    this.controller = controller;
+  }
+
+  @override
+  void dispose() {
+    super.dispose();
+  }
+
+  void _onMarkerTapped(MarkerId markerId) {
+    final Marker? tappedMarker = markers[markerId];
+    if (tappedMarker != null) {
+      setState(() {
+        final MarkerId? previousMarkerId = selectedMarker;
+        if (previousMarkerId != null && markers.containsKey(previousMarkerId)) {
+          final Marker resetOld = markers[previousMarkerId]!
+              .copyWith(iconParam: BitmapDescriptor.defaultMarker);
+          markers[previousMarkerId] = resetOld;
+        }
+        selectedMarker = markerId;
+        final Marker newMarker = tappedMarker.copyWith(
+          iconParam: BitmapDescriptor.defaultMarkerWithHue(
+            BitmapDescriptor.hueGreen,
+          ),
+        );
+        markers[markerId] = newMarker;
+
+        markerPosition = null;
+      });
+    }
+  }
+
+  Future<void> _onMarkerDrag(MarkerId markerId, LatLng newPosition) async {
+    setState(() {
+      markerPosition = newPosition;
+    });
+  }
+
+  Future<void> _onMarkerDragEnd(MarkerId markerId, LatLng newPosition) async {
+    final Marker? tappedMarker = markers[markerId];
+    if (tappedMarker != null) {
+      setState(() {
+        markerPosition = null;
+      });
+      await showDialog<void>(
+          context: context,
+          builder: (BuildContext context) {
+            return AlertDialog(
+                actions: <Widget>[
+                  TextButton(
+                    child: const Text('OK'),
+                    onPressed: () => Navigator.of(context).pop(),
+                  )
+                ],
+                content: Padding(
+                    padding: const EdgeInsets.symmetric(vertical: 66),
+                    child: Column(
+                      mainAxisSize: MainAxisSize.min,
+                      children: <Widget>[
+                        Text('Old position: ${tappedMarker.position}'),
+                        Text('New position: $newPosition'),
+                      ],
+                    )));
+          });
+    }
+  }
+
+  void _add() {
+    final int markerCount = markers.length;
+
+    if (markerCount == 12) {
+      return;
+    }
+
+    final String markerIdVal = 'marker_id_$_markerIdCounter';
+    _markerIdCounter++;
+    final MarkerId markerId = MarkerId(markerIdVal);
+
+    final Marker marker = Marker(
+      markerId: markerId,
+      position: LatLng(
+        center.latitude + sin(_markerIdCounter * pi / 6.0) / 20.0,
+        center.longitude + cos(_markerIdCounter * pi / 6.0) / 20.0,
+      ),
+      infoWindow: InfoWindow(title: markerIdVal, snippet: '*'),
+      onTap: () => _onMarkerTapped(markerId),
+      onDragEnd: (LatLng position) => _onMarkerDragEnd(markerId, position),
+      onDrag: (LatLng position) => _onMarkerDrag(markerId, position),
+    );
+
+    setState(() {
+      markers[markerId] = marker;
+    });
+  }
+
+  void _remove(MarkerId markerId) {
+    setState(() {
+      if (markers.containsKey(markerId)) {
+        markers.remove(markerId);
+      }
+    });
+  }
+
+  void _changePosition(MarkerId markerId) {
+    final Marker marker = markers[markerId]!;
+    final LatLng current = marker.position;
+    final Offset offset = Offset(
+      center.latitude - current.latitude,
+      center.longitude - current.longitude,
+    );
+    setState(() {
+      markers[markerId] = marker.copyWith(
+        positionParam: LatLng(
+          center.latitude + offset.dy,
+          center.longitude + offset.dx,
+        ),
+      );
+    });
+  }
+
+  void _changeAnchor(MarkerId markerId) {
+    final Marker marker = markers[markerId]!;
+    final Offset currentAnchor = marker.anchor;
+    final Offset newAnchor = Offset(1.0 - currentAnchor.dy, currentAnchor.dx);
+    setState(() {
+      markers[markerId] = marker.copyWith(
+        anchorParam: newAnchor,
+      );
+    });
+  }
+
+  Future<void> _changeInfoAnchor(MarkerId markerId) async {
+    final Marker marker = markers[markerId]!;
+    final Offset currentAnchor = marker.infoWindow.anchor;
+    final Offset newAnchor = Offset(1.0 - currentAnchor.dy, currentAnchor.dx);
+    setState(() {
+      markers[markerId] = marker.copyWith(
+        infoWindowParam: marker.infoWindow.copyWith(
+          anchorParam: newAnchor,
+        ),
+      );
+    });
+  }
+
+  Future<void> _toggleDraggable(MarkerId markerId) async {
+    final Marker marker = markers[markerId]!;
+    setState(() {
+      markers[markerId] = marker.copyWith(
+        draggableParam: !marker.draggable,
+      );
+    });
+  }
+
+  Future<void> _toggleFlat(MarkerId markerId) async {
+    final Marker marker = markers[markerId]!;
+    setState(() {
+      markers[markerId] = marker.copyWith(
+        flatParam: !marker.flat,
+      );
+    });
+  }
+
+  Future<void> _changeInfo(MarkerId markerId) async {
+    final Marker marker = markers[markerId]!;
+    final String newSnippet = '${marker.infoWindow.snippet!}*';
+    setState(() {
+      markers[markerId] = marker.copyWith(
+        infoWindowParam: marker.infoWindow.copyWith(
+          snippetParam: newSnippet,
+        ),
+      );
+    });
+  }
+
+  Future<void> _changeAlpha(MarkerId markerId) async {
+    final Marker marker = markers[markerId]!;
+    final double current = marker.alpha;
+    setState(() {
+      markers[markerId] = marker.copyWith(
+        alphaParam: current < 0.1 ? 1.0 : current * 0.75,
+      );
+    });
+  }
+
+  Future<void> _changeRotation(MarkerId markerId) async {
+    final Marker marker = markers[markerId]!;
+    final double current = marker.rotation;
+    setState(() {
+      markers[markerId] = marker.copyWith(
+        rotationParam: current == 330.0 ? 0.0 : current + 30.0,
+      );
+    });
+  }
+
+  Future<void> _toggleVisible(MarkerId markerId) async {
+    final Marker marker = markers[markerId]!;
+    setState(() {
+      markers[markerId] = marker.copyWith(
+        visibleParam: !marker.visible,
+      );
+    });
+  }
+
+  Future<void> _changeZIndex(MarkerId markerId) async {
+    final Marker marker = markers[markerId]!;
+    final double current = marker.zIndex;
+    setState(() {
+      markers[markerId] = marker.copyWith(
+        zIndexParam: current == 12.0 ? 0.0 : current + 1.0,
+      );
+    });
+  }
+
+  void _setMarkerIcon(MarkerId markerId, BitmapDescriptor assetIcon) {
+    final Marker marker = markers[markerId]!;
+    setState(() {
+      markers[markerId] = marker.copyWith(
+        iconParam: assetIcon,
+      );
+    });
+  }
+
+  Future<BitmapDescriptor> _getAssetIcon(BuildContext context) async {
+    final Completer<BitmapDescriptor> bitmapIcon =
+        Completer<BitmapDescriptor>();
+    final ImageConfiguration config = createLocalImageConfiguration(context);
+
+    const AssetImage('assets/red_square.png')
+        .resolve(config)
+        .addListener(ImageStreamListener((ImageInfo image, bool sync) async {
+      final ByteData? bytes =
+          await image.image.toByteData(format: ImageByteFormat.png);
+      if (bytes == null) {
+        bitmapIcon.completeError(Exception('Unable to encode icon'));
+        return;
+      }
+      final BitmapDescriptor bitmap =
+          BitmapDescriptor.fromBytes(bytes.buffer.asUint8List());
+      bitmapIcon.complete(bitmap);
+    }));
+
+    return await bitmapIcon.future;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final MarkerId? selectedId = selectedMarker;
+    return Stack(children: <Widget>[
+      Column(
+        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+        crossAxisAlignment: CrossAxisAlignment.stretch,
+        children: <Widget>[
+          Expanded(
+            child: ExampleGoogleMap(
+              onMapCreated: _onMapCreated,
+              initialCameraPosition: const CameraPosition(
+                target: LatLng(-33.852, 151.211),
+                zoom: 11.0,
+              ),
+              markers: Set<Marker>.of(markers.values),
+            ),
+          ),
+          Row(
+            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+            children: <Widget>[
+              TextButton(
+                onPressed: _add,
+                child: const Text('Add'),
+              ),
+              TextButton(
+                onPressed:
+                    selectedId == null ? null : () => _remove(selectedId),
+                child: const Text('Remove'),
+              ),
+            ],
+          ),
+          Wrap(
+            alignment: WrapAlignment.spaceEvenly,
+            children: <Widget>[
+              TextButton(
+                onPressed:
+                    selectedId == null ? null : () => _changeInfo(selectedId),
+                child: const Text('change info'),
+              ),
+              TextButton(
+                onPressed: selectedId == null
+                    ? null
+                    : () => _changeInfoAnchor(selectedId),
+                child: const Text('change info anchor'),
+              ),
+              TextButton(
+                onPressed:
+                    selectedId == null ? null : () => _changeAlpha(selectedId),
+                child: const Text('change alpha'),
+              ),
+              TextButton(
+                onPressed:
+                    selectedId == null ? null : () => _changeAnchor(selectedId),
+                child: const Text('change anchor'),
+              ),
+              TextButton(
+                onPressed: selectedId == null
+                    ? null
+                    : () => _toggleDraggable(selectedId),
+                child: const Text('toggle draggable'),
+              ),
+              TextButton(
+                onPressed:
+                    selectedId == null ? null : () => _toggleFlat(selectedId),
+                child: const Text('toggle flat'),
+              ),
+              TextButton(
+                onPressed: selectedId == null
+                    ? null
+                    : () => _changePosition(selectedId),
+                child: const Text('change position'),
+              ),
+              TextButton(
+                onPressed: selectedId == null
+                    ? null
+                    : () => _changeRotation(selectedId),
+                child: const Text('change rotation'),
+              ),
+              TextButton(
+                onPressed: selectedId == null
+                    ? null
+                    : () => _toggleVisible(selectedId),
+                child: const Text('toggle visible'),
+              ),
+              TextButton(
+                onPressed:
+                    selectedId == null ? null : () => _changeZIndex(selectedId),
+                child: const Text('change zIndex'),
+              ),
+              TextButton(
+                onPressed: selectedId == null
+                    ? null
+                    : () {
+                        _getAssetIcon(context).then(
+                          (BitmapDescriptor icon) {
+                            _setMarkerIcon(selectedId, icon);
+                          },
+                        );
+                      },
+                child: const Text('set marker icon'),
+              ),
+            ],
+          ),
+        ],
+      ),
+      Visibility(
+        visible: markerPosition != null,
+        child: Container(
+          color: Colors.white70,
+          height: 30,
+          padding: const EdgeInsets.only(left: 12, right: 12),
+          child: Row(
+            mainAxisAlignment: MainAxisAlignment.spaceAround,
+            mainAxisSize: MainAxisSize.max,
+            children: <Widget>[
+              if (markerPosition == null)
+                Container()
+              else
+                Expanded(child: Text('lat: ${markerPosition!.latitude}')),
+              if (markerPosition == null)
+                Container()
+              else
+                Expanded(child: Text('lng: ${markerPosition!.longitude}')),
+            ],
+          ),
+        ),
+      ),
+    ]);
+  }
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/place_polygon.dart b/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/place_polygon.dart
new file mode 100644
index 0000000..b41cb5d
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/place_polygon.dart
@@ -0,0 +1,306 @@
+// 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.
+
+// ignore_for_file: public_member_api_docs
+
+import 'package:flutter/material.dart';
+import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
+
+import 'example_google_map.dart';
+import 'page.dart';
+
+class PlacePolygonPage extends GoogleMapExampleAppPage {
+  const PlacePolygonPage({Key? key})
+      : super(const Icon(Icons.linear_scale), 'Place polygon', key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return const PlacePolygonBody();
+  }
+}
+
+class PlacePolygonBody extends StatefulWidget {
+  const PlacePolygonBody({Key? key}) : super(key: key);
+
+  @override
+  State<StatefulWidget> createState() => PlacePolygonBodyState();
+}
+
+class PlacePolygonBodyState extends State<PlacePolygonBody> {
+  PlacePolygonBodyState();
+
+  ExampleGoogleMapController? controller;
+  Map<PolygonId, Polygon> polygons = <PolygonId, Polygon>{};
+  Map<PolygonId, double> polygonOffsets = <PolygonId, double>{};
+  int _polygonIdCounter = 0;
+  PolygonId? selectedPolygon;
+
+  // Values when toggling polygon color
+  int strokeColorsIndex = 0;
+  int fillColorsIndex = 0;
+  List<Color> colors = <Color>[
+    Colors.purple,
+    Colors.red,
+    Colors.green,
+    Colors.pink,
+  ];
+
+  // Values when toggling polygon width
+  int widthsIndex = 0;
+  List<int> widths = <int>[10, 20, 5];
+
+  // ignore: use_setters_to_change_properties
+  void _onMapCreated(ExampleGoogleMapController controller) {
+    this.controller = controller;
+  }
+
+  @override
+  void dispose() {
+    super.dispose();
+  }
+
+  void _onPolygonTapped(PolygonId polygonId) {
+    setState(() {
+      selectedPolygon = polygonId;
+    });
+  }
+
+  void _remove(PolygonId polygonId) {
+    setState(() {
+      if (polygons.containsKey(polygonId)) {
+        polygons.remove(polygonId);
+      }
+      selectedPolygon = null;
+    });
+  }
+
+  void _add() {
+    final int polygonCount = polygons.length;
+
+    if (polygonCount == 12) {
+      return;
+    }
+
+    final String polygonIdVal = 'polygon_id_$_polygonIdCounter';
+    final PolygonId polygonId = PolygonId(polygonIdVal);
+
+    final Polygon polygon = Polygon(
+      polygonId: polygonId,
+      consumeTapEvents: true,
+      strokeColor: Colors.orange,
+      strokeWidth: 5,
+      fillColor: Colors.green,
+      points: _createPoints(),
+      onTap: () {
+        _onPolygonTapped(polygonId);
+      },
+    );
+
+    setState(() {
+      polygons[polygonId] = polygon;
+      polygonOffsets[polygonId] = _polygonIdCounter.ceilToDouble();
+      // increment _polygonIdCounter to have unique polygon id each time
+      _polygonIdCounter++;
+    });
+  }
+
+  void _toggleGeodesic(PolygonId polygonId) {
+    final Polygon polygon = polygons[polygonId]!;
+    setState(() {
+      polygons[polygonId] = polygon.copyWith(
+        geodesicParam: !polygon.geodesic,
+      );
+    });
+  }
+
+  void _toggleVisible(PolygonId polygonId) {
+    final Polygon polygon = polygons[polygonId]!;
+    setState(() {
+      polygons[polygonId] = polygon.copyWith(
+        visibleParam: !polygon.visible,
+      );
+    });
+  }
+
+  void _changeStrokeColor(PolygonId polygonId) {
+    final Polygon polygon = polygons[polygonId]!;
+    setState(() {
+      polygons[polygonId] = polygon.copyWith(
+        strokeColorParam: colors[++strokeColorsIndex % colors.length],
+      );
+    });
+  }
+
+  void _changeFillColor(PolygonId polygonId) {
+    final Polygon polygon = polygons[polygonId]!;
+    setState(() {
+      polygons[polygonId] = polygon.copyWith(
+        fillColorParam: colors[++fillColorsIndex % colors.length],
+      );
+    });
+  }
+
+  void _changeWidth(PolygonId polygonId) {
+    final Polygon polygon = polygons[polygonId]!;
+    setState(() {
+      polygons[polygonId] = polygon.copyWith(
+        strokeWidthParam: widths[++widthsIndex % widths.length],
+      );
+    });
+  }
+
+  void _addHoles(PolygonId polygonId) {
+    final Polygon polygon = polygons[polygonId]!;
+    setState(() {
+      polygons[polygonId] =
+          polygon.copyWith(holesParam: _createHoles(polygonId));
+    });
+  }
+
+  void _removeHoles(PolygonId polygonId) {
+    final Polygon polygon = polygons[polygonId]!;
+    setState(() {
+      polygons[polygonId] = polygon.copyWith(
+        holesParam: <List<LatLng>>[],
+      );
+    });
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final PolygonId? selectedId = selectedPolygon;
+    return Column(
+      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+      crossAxisAlignment: CrossAxisAlignment.stretch,
+      children: <Widget>[
+        Center(
+          child: SizedBox(
+            width: 350.0,
+            height: 300.0,
+            child: ExampleGoogleMap(
+              initialCameraPosition: const CameraPosition(
+                target: LatLng(52.4478, -3.5402),
+                zoom: 7.0,
+              ),
+              polygons: Set<Polygon>.of(polygons.values),
+              onMapCreated: _onMapCreated,
+            ),
+          ),
+        ),
+        Expanded(
+          child: SingleChildScrollView(
+            child: Row(
+              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+              children: <Widget>[
+                Row(
+                  children: <Widget>[
+                    Column(
+                      children: <Widget>[
+                        TextButton(
+                          onPressed: _add,
+                          child: const Text('add'),
+                        ),
+                        TextButton(
+                          onPressed: (selectedId == null)
+                              ? null
+                              : () => _remove(selectedId),
+                          child: const Text('remove'),
+                        ),
+                        TextButton(
+                          onPressed: (selectedId == null)
+                              ? null
+                              : () => _toggleVisible(selectedId),
+                          child: const Text('toggle visible'),
+                        ),
+                        TextButton(
+                          onPressed: (selectedId == null)
+                              ? null
+                              : () => _toggleGeodesic(selectedId),
+                          child: const Text('toggle geodesic'),
+                        ),
+                      ],
+                    ),
+                    Column(
+                      children: <Widget>[
+                        TextButton(
+                          onPressed: (selectedId == null)
+                              ? null
+                              : ((polygons[selectedId]!.holes.isNotEmpty)
+                                  ? null
+                                  : () => _addHoles(selectedId)),
+                          child: const Text('add holes'),
+                        ),
+                        TextButton(
+                          onPressed: (selectedId == null)
+                              ? null
+                              : ((polygons[selectedId]!.holes.isEmpty)
+                                  ? null
+                                  : () => _removeHoles(selectedId)),
+                          child: const Text('remove holes'),
+                        ),
+                        TextButton(
+                          onPressed: (selectedId == null)
+                              ? null
+                              : () => _changeWidth(selectedId),
+                          child: const Text('change stroke width'),
+                        ),
+                        TextButton(
+                          onPressed: (selectedId == null)
+                              ? null
+                              : () => _changeStrokeColor(selectedId),
+                          child: const Text('change stroke color'),
+                        ),
+                        TextButton(
+                          onPressed: (selectedId == null)
+                              ? null
+                              : () => _changeFillColor(selectedId),
+                          child: const Text('change fill color'),
+                        ),
+                      ],
+                    )
+                  ],
+                )
+              ],
+            ),
+          ),
+        ),
+      ],
+    );
+  }
+
+  List<LatLng> _createPoints() {
+    final List<LatLng> points = <LatLng>[];
+    final double offset = _polygonIdCounter.ceilToDouble();
+    points.add(_createLatLng(51.2395 + offset, -3.4314));
+    points.add(_createLatLng(53.5234 + offset, -3.5314));
+    points.add(_createLatLng(52.4351 + offset, -4.5235));
+    points.add(_createLatLng(52.1231 + offset, -5.0829));
+    return points;
+  }
+
+  List<List<LatLng>> _createHoles(PolygonId polygonId) {
+    final List<List<LatLng>> holes = <List<LatLng>>[];
+    final double offset = polygonOffsets[polygonId]!;
+
+    final List<LatLng> hole1 = <LatLng>[];
+    hole1.add(_createLatLng(51.8395 + offset, -3.8814));
+    hole1.add(_createLatLng(52.0234 + offset, -3.9914));
+    hole1.add(_createLatLng(52.1351 + offset, -4.4435));
+    hole1.add(_createLatLng(52.0231 + offset, -4.5829));
+    holes.add(hole1);
+
+    final List<LatLng> hole2 = <LatLng>[];
+    hole2.add(_createLatLng(52.2395 + offset, -3.6814));
+    hole2.add(_createLatLng(52.4234 + offset, -3.7914));
+    hole2.add(_createLatLng(52.5351 + offset, -4.2435));
+    hole2.add(_createLatLng(52.4231 + offset, -4.3829));
+    holes.add(hole2);
+
+    return holes;
+  }
+
+  LatLng _createLatLng(double lat, double lng) {
+    return LatLng(lat, lng);
+  }
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/place_polyline.dart b/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/place_polyline.dart
new file mode 100644
index 0000000..004206b
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/place_polyline.dart
@@ -0,0 +1,325 @@
+// 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.
+
+// ignore_for_file: public_member_api_docs
+
+import 'package:flutter/foundation.dart';
+import 'package:flutter/material.dart';
+import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
+
+import 'example_google_map.dart';
+import 'page.dart';
+
+class PlacePolylinePage extends GoogleMapExampleAppPage {
+  const PlacePolylinePage({Key? key})
+      : super(const Icon(Icons.linear_scale), 'Place polyline', key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return const PlacePolylineBody();
+  }
+}
+
+class PlacePolylineBody extends StatefulWidget {
+  const PlacePolylineBody({Key? key}) : super(key: key);
+
+  @override
+  State<StatefulWidget> createState() => PlacePolylineBodyState();
+}
+
+class PlacePolylineBodyState extends State<PlacePolylineBody> {
+  PlacePolylineBodyState();
+
+  ExampleGoogleMapController? controller;
+  Map<PolylineId, Polyline> polylines = <PolylineId, Polyline>{};
+  int _polylineIdCounter = 0;
+  PolylineId? selectedPolyline;
+
+  // Values when toggling polyline color
+  int colorsIndex = 0;
+  List<Color> colors = <Color>[
+    Colors.purple,
+    Colors.red,
+    Colors.green,
+    Colors.pink,
+  ];
+
+  // Values when toggling polyline width
+  int widthsIndex = 0;
+  List<int> widths = <int>[10, 20, 5];
+
+  int jointTypesIndex = 0;
+  List<JointType> jointTypes = <JointType>[
+    JointType.mitered,
+    JointType.bevel,
+    JointType.round
+  ];
+
+  // Values when toggling polyline end cap type
+  int endCapsIndex = 0;
+  List<Cap> endCaps = <Cap>[Cap.buttCap, Cap.squareCap, Cap.roundCap];
+
+  // Values when toggling polyline start cap type
+  int startCapsIndex = 0;
+  List<Cap> startCaps = <Cap>[Cap.buttCap, Cap.squareCap, Cap.roundCap];
+
+  // Values when toggling polyline pattern
+  int patternsIndex = 0;
+  List<List<PatternItem>> patterns = <List<PatternItem>>[
+    <PatternItem>[],
+    <PatternItem>[
+      PatternItem.dash(30.0),
+      PatternItem.gap(20.0),
+      PatternItem.dot,
+      PatternItem.gap(20.0)
+    ],
+    <PatternItem>[PatternItem.dash(30.0), PatternItem.gap(20.0)],
+    <PatternItem>[PatternItem.dot, PatternItem.gap(10.0)],
+  ];
+
+  // ignore: use_setters_to_change_properties
+  void _onMapCreated(ExampleGoogleMapController controller) {
+    this.controller = controller;
+  }
+
+  @override
+  void dispose() {
+    super.dispose();
+  }
+
+  void _onPolylineTapped(PolylineId polylineId) {
+    setState(() {
+      selectedPolyline = polylineId;
+    });
+  }
+
+  void _remove(PolylineId polylineId) {
+    setState(() {
+      if (polylines.containsKey(polylineId)) {
+        polylines.remove(polylineId);
+      }
+      selectedPolyline = null;
+    });
+  }
+
+  void _add() {
+    final int polylineCount = polylines.length;
+
+    if (polylineCount == 12) {
+      return;
+    }
+
+    final String polylineIdVal = 'polyline_id_$_polylineIdCounter';
+    _polylineIdCounter++;
+    final PolylineId polylineId = PolylineId(polylineIdVal);
+
+    final Polyline polyline = Polyline(
+      polylineId: polylineId,
+      consumeTapEvents: true,
+      color: Colors.orange,
+      width: 5,
+      points: _createPoints(),
+      onTap: () {
+        _onPolylineTapped(polylineId);
+      },
+    );
+
+    setState(() {
+      polylines[polylineId] = polyline;
+    });
+  }
+
+  void _toggleGeodesic(PolylineId polylineId) {
+    final Polyline polyline = polylines[polylineId]!;
+    setState(() {
+      polylines[polylineId] = polyline.copyWith(
+        geodesicParam: !polyline.geodesic,
+      );
+    });
+  }
+
+  void _toggleVisible(PolylineId polylineId) {
+    final Polyline polyline = polylines[polylineId]!;
+    setState(() {
+      polylines[polylineId] = polyline.copyWith(
+        visibleParam: !polyline.visible,
+      );
+    });
+  }
+
+  void _changeColor(PolylineId polylineId) {
+    final Polyline polyline = polylines[polylineId]!;
+    setState(() {
+      polylines[polylineId] = polyline.copyWith(
+        colorParam: colors[++colorsIndex % colors.length],
+      );
+    });
+  }
+
+  void _changeWidth(PolylineId polylineId) {
+    final Polyline polyline = polylines[polylineId]!;
+    setState(() {
+      polylines[polylineId] = polyline.copyWith(
+        widthParam: widths[++widthsIndex % widths.length],
+      );
+    });
+  }
+
+  void _changeJointType(PolylineId polylineId) {
+    final Polyline polyline = polylines[polylineId]!;
+    setState(() {
+      polylines[polylineId] = polyline.copyWith(
+        jointTypeParam: jointTypes[++jointTypesIndex % jointTypes.length],
+      );
+    });
+  }
+
+  void _changeEndCap(PolylineId polylineId) {
+    final Polyline polyline = polylines[polylineId]!;
+    setState(() {
+      polylines[polylineId] = polyline.copyWith(
+        endCapParam: endCaps[++endCapsIndex % endCaps.length],
+      );
+    });
+  }
+
+  void _changeStartCap(PolylineId polylineId) {
+    final Polyline polyline = polylines[polylineId]!;
+    setState(() {
+      polylines[polylineId] = polyline.copyWith(
+        startCapParam: startCaps[++startCapsIndex % startCaps.length],
+      );
+    });
+  }
+
+  void _changePattern(PolylineId polylineId) {
+    final Polyline polyline = polylines[polylineId]!;
+    setState(() {
+      polylines[polylineId] = polyline.copyWith(
+        patternsParam: patterns[++patternsIndex % patterns.length],
+      );
+    });
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final bool isIOS = !kIsWeb && defaultTargetPlatform == TargetPlatform.iOS;
+
+    final PolylineId? selectedId = selectedPolyline;
+
+    return Column(
+      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+      crossAxisAlignment: CrossAxisAlignment.stretch,
+      children: <Widget>[
+        Center(
+          child: SizedBox(
+            width: 350.0,
+            height: 300.0,
+            child: ExampleGoogleMap(
+              initialCameraPosition: const CameraPosition(
+                target: LatLng(53.1721, -3.5402),
+                zoom: 7.0,
+              ),
+              polylines: Set<Polyline>.of(polylines.values),
+              onMapCreated: _onMapCreated,
+            ),
+          ),
+        ),
+        Expanded(
+          child: SingleChildScrollView(
+            child: Row(
+              mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+              children: <Widget>[
+                Row(
+                  children: <Widget>[
+                    Column(
+                      children: <Widget>[
+                        TextButton(
+                          onPressed: _add,
+                          child: const Text('add'),
+                        ),
+                        TextButton(
+                          onPressed: (selectedId == null)
+                              ? null
+                              : () => _remove(selectedId),
+                          child: const Text('remove'),
+                        ),
+                        TextButton(
+                          onPressed: (selectedId == null)
+                              ? null
+                              : () => _toggleVisible(selectedId),
+                          child: const Text('toggle visible'),
+                        ),
+                        TextButton(
+                          onPressed: (selectedId == null)
+                              ? null
+                              : () => _toggleGeodesic(selectedId),
+                          child: const Text('toggle geodesic'),
+                        ),
+                      ],
+                    ),
+                    Column(
+                      children: <Widget>[
+                        TextButton(
+                          onPressed: (selectedId == null)
+                              ? null
+                              : () => _changeWidth(selectedId),
+                          child: const Text('change width'),
+                        ),
+                        TextButton(
+                          onPressed: (selectedId == null)
+                              ? null
+                              : () => _changeColor(selectedId),
+                          child: const Text('change color'),
+                        ),
+                        TextButton(
+                          onPressed: isIOS || (selectedId == null)
+                              ? null
+                              : () => _changeStartCap(selectedId),
+                          child: const Text('change start cap [Android only]'),
+                        ),
+                        TextButton(
+                          onPressed: isIOS || (selectedId == null)
+                              ? null
+                              : () => _changeEndCap(selectedId),
+                          child: const Text('change end cap [Android only]'),
+                        ),
+                        TextButton(
+                          onPressed: isIOS || (selectedId == null)
+                              ? null
+                              : () => _changeJointType(selectedId),
+                          child: const Text('change joint type [Android only]'),
+                        ),
+                        TextButton(
+                          onPressed: isIOS || (selectedId == null)
+                              ? null
+                              : () => _changePattern(selectedId),
+                          child: const Text('change pattern [Android only]'),
+                        ),
+                      ],
+                    )
+                  ],
+                )
+              ],
+            ),
+          ),
+        ),
+      ],
+    );
+  }
+
+  List<LatLng> _createPoints() {
+    final List<LatLng> points = <LatLng>[];
+    final double offset = _polylineIdCounter.ceilToDouble();
+    points.add(_createLatLng(51.4816 + offset, -3.1791));
+    points.add(_createLatLng(53.0430 + offset, -2.9925));
+    points.add(_createLatLng(53.1396 + offset, -4.2739));
+    points.add(_createLatLng(52.4153 + offset, -4.0829));
+    return points;
+  }
+
+  LatLng _createLatLng(double lat, double lng) {
+    return LatLng(lat, lng);
+  }
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/scrolling_map.dart b/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/scrolling_map.dart
new file mode 100644
index 0000000..7a9b75c
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/scrolling_map.dart
@@ -0,0 +1,114 @@
+// 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.
+
+// ignore_for_file: public_member_api_docs
+
+import 'package:flutter/foundation.dart';
+import 'package:flutter/gestures.dart';
+import 'package:flutter/material.dart';
+import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
+
+import 'example_google_map.dart';
+import 'page.dart';
+
+const LatLng _center = LatLng(32.080664, 34.9563837);
+
+class ScrollingMapPage extends GoogleMapExampleAppPage {
+  const ScrollingMapPage({Key? key})
+      : super(const Icon(Icons.map), 'Scrolling map', key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return const ScrollingMapBody();
+  }
+}
+
+class ScrollingMapBody extends StatelessWidget {
+  const ScrollingMapBody({Key? key}) : super(key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return ListView(
+      children: <Widget>[
+        Card(
+          child: Padding(
+            padding: const EdgeInsets.symmetric(vertical: 30.0),
+            child: Column(
+              children: <Widget>[
+                const Padding(
+                  padding: EdgeInsets.only(bottom: 12.0),
+                  child: Text('This map consumes all touch events.'),
+                ),
+                Center(
+                  child: SizedBox(
+                    width: 300.0,
+                    height: 300.0,
+                    child: ExampleGoogleMap(
+                      initialCameraPosition: const CameraPosition(
+                        target: _center,
+                        zoom: 11.0,
+                      ),
+                      gestureRecognizers: //
+                          <Factory<OneSequenceGestureRecognizer>>{
+                        Factory<OneSequenceGestureRecognizer>(
+                          () => EagerGestureRecognizer(),
+                        ),
+                      },
+                    ),
+                  ),
+                ),
+              ],
+            ),
+          ),
+        ),
+        Card(
+          child: Padding(
+            padding: const EdgeInsets.symmetric(vertical: 30.0),
+            child: Column(
+              children: <Widget>[
+                const Text("This map doesn't consume the vertical drags."),
+                const Padding(
+                  padding: EdgeInsets.only(bottom: 12.0),
+                  child:
+                      Text('It still gets other gestures (e.g scale or tap).'),
+                ),
+                Center(
+                  child: SizedBox(
+                    width: 300.0,
+                    height: 300.0,
+                    child: ExampleGoogleMap(
+                      initialCameraPosition: const CameraPosition(
+                        target: _center,
+                        zoom: 11.0,
+                      ),
+                      markers: <Marker>{
+                        Marker(
+                          markerId: const MarkerId('test_marker_id'),
+                          position: LatLng(
+                            _center.latitude,
+                            _center.longitude,
+                          ),
+                          infoWindow: const InfoWindow(
+                            title: 'An interesting location',
+                            snippet: '*',
+                          ),
+                        ),
+                      },
+                      gestureRecognizers: <
+                          Factory<OneSequenceGestureRecognizer>>{
+                        Factory<OneSequenceGestureRecognizer>(
+                          () => ScaleGestureRecognizer(),
+                        ),
+                      },
+                    ),
+                  ),
+                ),
+              ],
+            ),
+          ),
+        ),
+      ],
+    );
+  }
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/snapshot.dart b/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/snapshot.dart
new file mode 100644
index 0000000..56a90a8
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/snapshot.dart
@@ -0,0 +1,76 @@
+// 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.
+
+// ignore_for_file: public_member_api_docs
+
+import 'dart:typed_data';
+
+import 'package:flutter/material.dart';
+import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
+
+import 'example_google_map.dart';
+import 'page.dart';
+
+const CameraPosition _kInitialPosition =
+    CameraPosition(target: LatLng(-33.852, 151.211), zoom: 11.0);
+
+class SnapshotPage extends GoogleMapExampleAppPage {
+  const SnapshotPage({Key? key})
+      : super(const Icon(Icons.camera_alt), 'Take a snapshot of the map',
+            key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return _SnapshotBody();
+  }
+}
+
+class _SnapshotBody extends StatefulWidget {
+  @override
+  _SnapshotBodyState createState() => _SnapshotBodyState();
+}
+
+class _SnapshotBodyState extends State<_SnapshotBody> {
+  ExampleGoogleMapController? _mapController;
+  Uint8List? _imageBytes;
+
+  @override
+  Widget build(BuildContext context) {
+    return Padding(
+      padding: const EdgeInsets.all(16),
+      child: Column(
+        crossAxisAlignment: CrossAxisAlignment.stretch,
+        children: <Widget>[
+          SizedBox(
+            height: 180,
+            child: ExampleGoogleMap(
+              onMapCreated: onMapCreated,
+              initialCameraPosition: _kInitialPosition,
+            ),
+          ),
+          TextButton(
+            child: const Text('Take a snapshot'),
+            onPressed: () async {
+              final Uint8List? imageBytes =
+                  await _mapController?.takeSnapshot();
+              setState(() {
+                _imageBytes = imageBytes;
+              });
+            },
+          ),
+          Container(
+            decoration: BoxDecoration(color: Colors.blueGrey[50]),
+            height: 180,
+            child: _imageBytes != null ? Image.memory(_imageBytes!) : null,
+          ),
+        ],
+      ),
+    );
+  }
+
+  // ignore: use_setters_to_change_properties
+  void onMapCreated(ExampleGoogleMapController controller) {
+    _mapController = controller;
+  }
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/tile_overlay.dart b/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/tile_overlay.dart
new file mode 100644
index 0000000..555f037
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/lib/tile_overlay.dart
@@ -0,0 +1,156 @@
+// 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.
+
+// ignore_for_file: public_member_api_docs
+
+import 'dart:typed_data';
+import 'dart:ui' as ui;
+
+import 'package:flutter/material.dart';
+import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
+
+import 'example_google_map.dart';
+import 'page.dart';
+
+class TileOverlayPage extends GoogleMapExampleAppPage {
+  const TileOverlayPage({Key? key})
+      : super(const Icon(Icons.map), 'Tile overlay', key: key);
+
+  @override
+  Widget build(BuildContext context) {
+    return const TileOverlayBody();
+  }
+}
+
+class TileOverlayBody extends StatefulWidget {
+  const TileOverlayBody({Key? key}) : super(key: key);
+
+  @override
+  State<StatefulWidget> createState() => TileOverlayBodyState();
+}
+
+class TileOverlayBodyState extends State<TileOverlayBody> {
+  TileOverlayBodyState();
+
+  ExampleGoogleMapController? controller;
+  TileOverlay? _tileOverlay;
+
+  // ignore: use_setters_to_change_properties
+  void _onMapCreated(ExampleGoogleMapController controller) {
+    this.controller = controller;
+  }
+
+  @override
+  void dispose() {
+    super.dispose();
+  }
+
+  void _removeTileOverlay() {
+    setState(() {
+      _tileOverlay = null;
+    });
+  }
+
+  void _addTileOverlay() {
+    final TileOverlay tileOverlay = TileOverlay(
+      tileOverlayId: const TileOverlayId('tile_overlay_1'),
+      tileProvider: _DebugTileProvider(),
+    );
+    setState(() {
+      _tileOverlay = tileOverlay;
+    });
+  }
+
+  void _clearTileCache() {
+    if (_tileOverlay != null && controller != null) {
+      controller!.clearTileCache(_tileOverlay!.tileOverlayId);
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final Set<TileOverlay> overlays = <TileOverlay>{
+      if (_tileOverlay != null) _tileOverlay!,
+    };
+    return Column(
+      mainAxisSize: MainAxisSize.min,
+      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
+      crossAxisAlignment: CrossAxisAlignment.stretch,
+      children: <Widget>[
+        Center(
+          child: SizedBox(
+            width: 350.0,
+            height: 300.0,
+            child: ExampleGoogleMap(
+              initialCameraPosition: const CameraPosition(
+                target: LatLng(59.935460, 30.325177),
+                zoom: 7.0,
+              ),
+              tileOverlays: overlays,
+              onMapCreated: _onMapCreated,
+            ),
+          ),
+        ),
+        TextButton(
+          onPressed: _addTileOverlay,
+          child: const Text('Add tile overlay'),
+        ),
+        TextButton(
+          onPressed: _removeTileOverlay,
+          child: const Text('Remove tile overlay'),
+        ),
+        TextButton(
+          onPressed: _clearTileCache,
+          child: const Text('Clear tile cache'),
+        ),
+      ],
+    );
+  }
+}
+
+class _DebugTileProvider implements TileProvider {
+  _DebugTileProvider() {
+    boxPaint.isAntiAlias = true;
+    boxPaint.color = Colors.blue;
+    boxPaint.strokeWidth = 2.0;
+    boxPaint.style = PaintingStyle.stroke;
+  }
+
+  static const int width = 100;
+  static const int height = 100;
+  static final Paint boxPaint = Paint();
+  static const TextStyle textStyle = TextStyle(
+    color: Colors.red,
+    fontSize: 20,
+  );
+
+  @override
+  Future<Tile> getTile(int x, int y, int? zoom) async {
+    final ui.PictureRecorder recorder = ui.PictureRecorder();
+    final Canvas canvas = Canvas(recorder);
+    final TextSpan textSpan = TextSpan(
+      text: '$x,$y',
+      style: textStyle,
+    );
+    final TextPainter textPainter = TextPainter(
+      text: textSpan,
+      textDirection: TextDirection.ltr,
+    );
+    textPainter.layout(
+      minWidth: 0.0,
+      maxWidth: width.toDouble(),
+    );
+    const Offset offset = Offset(0, 0);
+    textPainter.paint(canvas, offset);
+    canvas.drawRect(
+        Rect.fromLTRB(0, 0, width.toDouble(), width.toDouble()), boxPaint);
+    final ui.Picture picture = recorder.endRecording();
+    final Uint8List byteData = await picture
+        .toImage(width, height)
+        .then((ui.Image image) =>
+            image.toByteData(format: ui.ImageByteFormat.png))
+        .then((ByteData? byteData) => byteData!.buffer.asUint8List());
+    return Tile(width, height, byteData);
+  }
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_ios/example/pubspec.yaml
new file mode 100644
index 0000000..b4fc5db
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/pubspec.yaml
@@ -0,0 +1,31 @@
+name: google_maps_flutter_example
+description: Demonstrates how to use the google_maps_flutter plugin.
+publish_to: none
+
+environment:
+  sdk: ">=2.14.0 <3.0.0"
+  flutter: ">=2.8.0"
+
+dependencies:
+  cupertino_icons: ^0.1.0
+  flutter:
+    sdk: flutter
+  flutter_plugin_android_lifecycle: ^2.0.1
+  google_maps_flutter_ios:
+    # When depending on this package from a real application you should use:
+    #   google_maps_flutter_ios: ^x.y.z
+    # See https://dart.dev/tools/pub/dependencies#version-constraints
+    # The example app is bundled with the plugin so we use a path dependency on
+    # the parent directory to use the current plugin's version.
+    path: ../
+
+dev_dependencies:
+  flutter_driver:
+    sdk: flutter
+  integration_test:
+    sdk: flutter
+
+flutter:
+  uses-material-design: true
+  assets:
+    - assets/
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/example/test_driver/integration_test.dart b/packages/google_maps_flutter/google_maps_flutter_ios/example/test_driver/integration_test.dart
new file mode 100644
index 0000000..4f10f2a
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/example/test_driver/integration_test.dart
@@ -0,0 +1,7 @@
+// 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 'package:integration_test/integration_test_driver.dart';
+
+Future<void> main() => integrationDriver();
diff --git a/packages/google_maps_flutter/google_maps_flutter/ios/Assets/.gitkeep b/packages/google_maps_flutter/google_maps_flutter_ios/ios/Assets/.gitkeep
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/ios/Assets/.gitkeep
rename to packages/google_maps_flutter/google_maps_flutter_ios/ios/Assets/.gitkeep
diff --git a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/FLTGoogleMapJSONConversions.h b/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/FLTGoogleMapJSONConversions.h
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/ios/Classes/FLTGoogleMapJSONConversions.h
rename to packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/FLTGoogleMapJSONConversions.h
diff --git a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/FLTGoogleMapJSONConversions.m b/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/FLTGoogleMapJSONConversions.m
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/ios/Classes/FLTGoogleMapJSONConversions.m
rename to packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/FLTGoogleMapJSONConversions.m
diff --git a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/FLTGoogleMapTileOverlayController.h b/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/FLTGoogleMapTileOverlayController.h
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/ios/Classes/FLTGoogleMapTileOverlayController.h
rename to packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/FLTGoogleMapTileOverlayController.h
diff --git a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/FLTGoogleMapTileOverlayController.m b/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/FLTGoogleMapTileOverlayController.m
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/ios/Classes/FLTGoogleMapTileOverlayController.m
rename to packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/FLTGoogleMapTileOverlayController.m
diff --git a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/FLTGoogleMapsPlugin.h b/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/FLTGoogleMapsPlugin.h
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/ios/Classes/FLTGoogleMapsPlugin.h
rename to packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/FLTGoogleMapsPlugin.h
diff --git a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/FLTGoogleMapsPlugin.m b/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/FLTGoogleMapsPlugin.m
similarity index 89%
rename from packages/google_maps_flutter/google_maps_flutter/ios/Classes/FLTGoogleMapsPlugin.m
rename to packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/FLTGoogleMapsPlugin.m
index d62f19c..70bde90 100644
--- a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/FLTGoogleMapsPlugin.m
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/FLTGoogleMapsPlugin.m
@@ -11,7 +11,7 @@
 + (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar> *)registrar {
   FLTGoogleMapFactory *googleMapFactory = [[FLTGoogleMapFactory alloc] initWithRegistrar:registrar];
   [registrar registerViewFactory:googleMapFactory
-                                withId:@"plugins.flutter.io/google_maps"
+                                withId:@"plugins.flutter.dev/google_maps_ios"
       gestureRecognizersBlockingPolicy:
           FlutterPlatformViewGestureRecognizersBlockingPolicyWaitUntilTouchesEnded];
 }
diff --git a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapCircleController.h b/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapCircleController.h
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapCircleController.h
rename to packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapCircleController.h
diff --git a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapCircleController.m b/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapCircleController.m
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapCircleController.m
rename to packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapCircleController.m
diff --git a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapController.h b/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapController.h
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapController.h
rename to packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapController.h
diff --git a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapController.m b/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapController.m
similarity index 99%
rename from packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapController.m
rename to packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapController.m
index 3538cc4..41c5d77 100644
--- a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapController.m
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapController.m
@@ -76,7 +76,7 @@
     // https://github.com/flutter/flutter/issues/104121
     [self interpretMapOptions:args[@"options"]];
     NSString *channelName =
-        [NSString stringWithFormat:@"plugins.flutter.io/google_maps_%lld", viewId];
+        [NSString stringWithFormat:@"plugins.flutter.dev/google_maps_ios_%lld", viewId];
     _channel = [FlutterMethodChannel methodChannelWithName:channelName
                                            binaryMessenger:registrar.messenger];
     __weak __typeof__(self) weakSelf = self;
diff --git a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapController_Test.h b/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapController_Test.h
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapController_Test.h
rename to packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapController_Test.h
diff --git a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapMarkerController.h b/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapMarkerController.h
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapMarkerController.h
rename to packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapMarkerController.h
diff --git a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapMarkerController.m b/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapMarkerController.m
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapMarkerController.m
rename to packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapMarkerController.m
diff --git a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapPolygonController.h b/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapPolygonController.h
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapPolygonController.h
rename to packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapPolygonController.h
diff --git a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapPolygonController.m b/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapPolygonController.m
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapPolygonController.m
rename to packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapPolygonController.m
diff --git a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapPolylineController.h b/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapPolylineController.h
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapPolylineController.h
rename to packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapPolylineController.h
diff --git a/packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapPolylineController.m b/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapPolylineController.m
similarity index 100%
rename from packages/google_maps_flutter/google_maps_flutter/ios/Classes/GoogleMapPolylineController.m
rename to packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/GoogleMapPolylineController.m
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/ios/Classes/google_maps_flutter_ios-umbrella.h
similarity index 63%
rename from packages/google_maps_flutter/google_maps_flutter/ios/Classes/google_maps_flutter-umbrella.h
rename to packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/google_maps_flutter_ios-umbrella.h
index 9969e71..791c3aa 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/ios/Classes/google_maps_flutter_ios-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_ios/FLTGoogleMapJSONConversions.h>
+#import <google_maps_flutter_ios/FLTGoogleMapTileOverlayController.h>
+#import <google_maps_flutter_ios/FLTGoogleMapsPlugin.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_ios/ios/Classes/google_maps_flutter_ios.modulemap b/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/google_maps_flutter_ios.modulemap
new file mode 100644
index 0000000..699e675
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/ios/Classes/google_maps_flutter_ios.modulemap
@@ -0,0 +1,10 @@
+framework module google_maps_flutter_ios {
+  umbrella header "google_maps_flutter_ios-umbrella.h"
+
+  export *
+  module * { export * }
+
+  explicit module Test {
+    header "GoogleMapController_Test.h"
+  }
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter/ios/google_maps_flutter.podspec b/packages/google_maps_flutter/google_maps_flutter_ios/ios/google_maps_flutter_ios.podspec
similarity index 88%
rename from packages/google_maps_flutter/google_maps_flutter/ios/google_maps_flutter.podspec
rename to packages/google_maps_flutter/google_maps_flutter_ios/ios/google_maps_flutter_ios.podspec
index e34919c..14be02f 100644
--- a/packages/google_maps_flutter/google_maps_flutter/ios/google_maps_flutter.podspec
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/ios/google_maps_flutter_ios.podspec
@@ -2,7 +2,7 @@
 # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html
 #
 Pod::Spec.new do |s|
-  s.name             = 'google_maps_flutter'
+  s.name             = 'google_maps_flutter_ios'
   s.version          = '0.0.1'
   s.summary          = 'Google Maps for Flutter'
   s.description      = <<-DESC
@@ -12,11 +12,11 @@
   s.homepage         = 'https://github.com/flutter/plugins'
   s.license          = { :type => 'BSD', :file => '../LICENSE' }
   s.author           = { 'Flutter Dev Team' => 'flutter-dev@googlegroups.com' }
-  s.source           = { :http => 'https://github.com/flutter/plugins/tree/main/packages/google_maps_flutter/google_maps_flutter' }
-  s.documentation_url = 'https://pub.dev/packages/google_maps_flutter'
+  s.source           = { :http => 'https://github.com/flutter/plugins/tree/main/packages/google_maps_flutter/google_maps_flutter/ios' }
+  s.documentation_url = 'https://pub.dev/packages/google_maps_flutter_ios'
   s.source_files = 'Classes/**/*.{h,m}'
   s.public_header_files = 'Classes/**/*.h'
-  s.module_map = 'Classes/google_maps_flutter.modulemap'
+  s.module_map = 'Classes/google_maps_flutter_ios.modulemap'
   s.dependency 'Flutter'
   s.dependency 'GoogleMaps'
   s.static_framework = true
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/lib/google_maps_flutter_ios.dart b/packages/google_maps_flutter/google_maps_flutter_ios/lib/google_maps_flutter_ios.dart
new file mode 100644
index 0000000..c4aabbb
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/lib/google_maps_flutter_ios.dart
@@ -0,0 +1,5 @@
+// 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.
+
+export 'src/google_maps_flutter_ios.dart';
diff --git a/packages/google_maps_flutter/google_maps_flutter/example/integration_test/google_map_inspector.dart b/packages/google_maps_flutter/google_maps_flutter_ios/lib/src/google_map_inspector_ios.dart
similarity index 67%
copy from packages/google_maps_flutter/google_maps_flutter/example/integration_test/google_map_inspector.dart
copy to packages/google_maps_flutter/google_maps_flutter_ios/lib/src/google_map_inspector_ios.dart
index 95ca969..8fae1a3 100644
--- a/packages/google_maps_flutter/google_maps_flutter/example/integration_test/google_map_inspector.dart
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/lib/src/google_map_inspector_ios.dart
@@ -2,66 +2,59 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'package:flutter/foundation.dart';
+import 'package:flutter/services.dart';
 import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
 
-/// A method-channel-based implementation of [GoogleMapsInspectorPlatform], for
-/// use in tests in conjunction with [MethodChannelGoogleMapsFlutter].
-// TODO(stuartmorgan): Move this into the platform implementations when
-// federating the mobile implementations.
-class MethodChannelGoogleMapsInspector extends GoogleMapsInspectorPlatform {
+/// An Android of implementation of [GoogleMapsInspectorPlatform].
+@visibleForTesting
+class GoogleMapsInspectorIOS extends GoogleMapsInspectorPlatform {
   /// Creates a method-channel-based inspector instance that gets the channel
-  /// for a given map ID from [mapsPlatform].
-  MethodChannelGoogleMapsInspector(MethodChannelGoogleMapsFlutter mapsPlatform)
-      : _mapsPlatform = mapsPlatform;
+  /// for a given map ID from [channelProvider].
+  GoogleMapsInspectorIOS(MethodChannel? Function(int mapId) channelProvider)
+      : _channelProvider = channelProvider;
 
-  final MethodChannelGoogleMapsFlutter _mapsPlatform;
+  final MethodChannel? Function(int mapId) _channelProvider;
 
   @override
   Future<bool> areBuildingsEnabled({required int mapId}) async {
-    return (await _mapsPlatform
-        .channel(mapId)
+    return (await _channelProvider(mapId)!
         .invokeMethod<bool>('map#isBuildingsEnabled'))!;
   }
 
   @override
   Future<bool> areRotateGesturesEnabled({required int mapId}) async {
-    return (await _mapsPlatform
-        .channel(mapId)
+    return (await _channelProvider(mapId)!
         .invokeMethod<bool>('map#isRotateGesturesEnabled'))!;
   }
 
   @override
   Future<bool> areScrollGesturesEnabled({required int mapId}) async {
-    return (await _mapsPlatform
-        .channel(mapId)
+    return (await _channelProvider(mapId)!
         .invokeMethod<bool>('map#isScrollGesturesEnabled'))!;
   }
 
   @override
   Future<bool> areTiltGesturesEnabled({required int mapId}) async {
-    return (await _mapsPlatform
-        .channel(mapId)
+    return (await _channelProvider(mapId)!
         .invokeMethod<bool>('map#isTiltGesturesEnabled'))!;
   }
 
   @override
   Future<bool> areZoomControlsEnabled({required int mapId}) async {
-    return (await _mapsPlatform
-        .channel(mapId)
+    return (await _channelProvider(mapId)!
         .invokeMethod<bool>('map#isZoomControlsEnabled'))!;
   }
 
   @override
   Future<bool> areZoomGesturesEnabled({required int mapId}) async {
-    return (await _mapsPlatform
-        .channel(mapId)
+    return (await _channelProvider(mapId)!
         .invokeMethod<bool>('map#isZoomGesturesEnabled'))!;
   }
 
   @override
   Future<MinMaxZoomPreference> getMinMaxZoomLevels({required int mapId}) async {
-    final List<double> zoomLevels = (await _mapsPlatform
-            .channel(mapId)
+    final List<double> zoomLevels = (await _channelProvider(mapId)!
             .invokeMethod<List<dynamic>>('map#getMinMaxZoomLevels'))!
         .cast<double>();
     return MinMaxZoomPreference(zoomLevels[0], zoomLevels[1]);
@@ -70,8 +63,7 @@
   @override
   Future<TileOverlay?> getTileOverlayInfo(TileOverlayId tileOverlayId,
       {required int mapId}) async {
-    final Map<String, Object?>? tileInfo = await _mapsPlatform
-        .channel(mapId)
+    final Map<String, Object?>? tileInfo = await _channelProvider(mapId)!
         .invokeMapMethod<String, dynamic>(
             'map#getTileOverlayInfo', <String, String>{
       'tileOverlayId': tileOverlayId.value,
@@ -91,36 +83,31 @@
 
   @override
   Future<bool> isCompassEnabled({required int mapId}) async {
-    return (await _mapsPlatform
-        .channel(mapId)
+    return (await _channelProvider(mapId)!
         .invokeMethod<bool>('map#isCompassEnabled'))!;
   }
 
   @override
   Future<bool> isLiteModeEnabled({required int mapId}) async {
-    return (await _mapsPlatform
-        .channel(mapId)
+    return (await _channelProvider(mapId)!
         .invokeMethod<bool>('map#isLiteModeEnabled'))!;
   }
 
   @override
   Future<bool> isMapToolbarEnabled({required int mapId}) async {
-    return (await _mapsPlatform
-        .channel(mapId)
+    return (await _channelProvider(mapId)!
         .invokeMethod<bool>('map#isMapToolbarEnabled'))!;
   }
 
   @override
   Future<bool> isMyLocationButtonEnabled({required int mapId}) async {
-    return (await _mapsPlatform
-        .channel(mapId)
+    return (await _channelProvider(mapId)!
         .invokeMethod<bool>('map#isMyLocationButtonEnabled'))!;
   }
 
   @override
   Future<bool> isTrafficEnabled({required int mapId}) async {
-    return (await _mapsPlatform
-        .channel(mapId)
+    return (await _channelProvider(mapId)!
         .invokeMethod<bool>('map#isTrafficEnabled'))!;
   }
 }
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/lib/src/google_maps_flutter_ios.dart b/packages/google_maps_flutter/google_maps_flutter_ios/lib/src/google_maps_flutter_ios.dart
new file mode 100644
index 0000000..5298377
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/lib/src/google_maps_flutter_ios.dart
@@ -0,0 +1,644 @@
+// 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 'dart:async';
+// TODO(a14n): remove this import once Flutter 3.1 or later reaches stable (including flutter/flutter#104231)
+// ignore: unnecessary_import
+import 'dart:typed_data';
+
+import 'package:flutter/foundation.dart';
+import 'package:flutter/gestures.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
+import 'package:stream_transform/stream_transform.dart';
+
+import 'google_map_inspector_ios.dart';
+
+// TODO(stuartmorgan): Remove the dependency on platform interface toJson
+// methods. Channel serialization details should all be package-internal.
+
+/// Error thrown when an unknown map ID is provided to a method channel API.
+class UnknownMapIDError extends Error {
+  /// Creates an assertion error with the provided [mapId] and optional
+  /// [message].
+  UnknownMapIDError(this.mapId, [this.message]);
+
+  /// The unknown ID.
+  final int mapId;
+
+  /// Message describing the assertion error.
+  final Object? message;
+
+  @override
+  String toString() {
+    if (message != null) {
+      return 'Unknown map ID $mapId: ${Error.safeToString(message)}';
+    }
+    return 'Unknown map ID $mapId';
+  }
+}
+
+/// An implementation of [GoogleMapsFlutterPlatform] for iOS.
+class GoogleMapsFlutterIOS extends GoogleMapsFlutterPlatform {
+  /// Registers the iOS implementation of GoogleMapsFlutterPlatform.
+  static void registerWith() {
+    GoogleMapsFlutterPlatform.instance = GoogleMapsFlutterIOS();
+  }
+
+  // Keep a collection of id -> channel
+  // Every method call passes the int mapId
+  final Map<int, MethodChannel> _channels = <int, MethodChannel>{};
+
+  /// Accesses the MethodChannel associated to the passed mapId.
+  MethodChannel _channel(int mapId) {
+    final MethodChannel? channel = _channels[mapId];
+    if (channel == null) {
+      throw UnknownMapIDError(mapId);
+    }
+    return channel;
+  }
+
+  // Keep a collection of mapId to a map of TileOverlays.
+  final Map<int, Map<TileOverlayId, TileOverlay>> _tileOverlays =
+      <int, Map<TileOverlayId, TileOverlay>>{};
+
+  /// Returns the channel for [mapId], creating it if it doesn't already exist.
+  @visibleForTesting
+  MethodChannel ensureChannelInitialized(int mapId) {
+    MethodChannel? channel = _channels[mapId];
+    if (channel == null) {
+      channel = MethodChannel('plugins.flutter.dev/google_maps_ios_$mapId');
+      channel.setMethodCallHandler(
+          (MethodCall call) => _handleMethodCall(call, mapId));
+      _channels[mapId] = channel;
+    }
+    return channel;
+  }
+
+  @override
+  Future<void> init(int mapId) {
+    final MethodChannel channel = ensureChannelInitialized(mapId);
+    return channel.invokeMethod<void>('map#waitForMap');
+  }
+
+  @override
+  void dispose({required int mapId}) {
+    // Noop!
+  }
+
+  // The controller we need to broadcast the different events coming
+  // from handleMethodCall.
+  //
+  // It is a `broadcast` because multiple controllers will connect to
+  // different stream views of this Controller.
+  final StreamController<MapEvent<Object?>> _mapEventStreamController =
+      StreamController<MapEvent<Object?>>.broadcast();
+
+  // Returns a filtered view of the events in the _controller, by mapId.
+  Stream<MapEvent<Object?>> _events(int mapId) =>
+      _mapEventStreamController.stream
+          .where((MapEvent<Object?> event) => event.mapId == mapId);
+
+  @override
+  Stream<CameraMoveStartedEvent> onCameraMoveStarted({required int mapId}) {
+    return _events(mapId).whereType<CameraMoveStartedEvent>();
+  }
+
+  @override
+  Stream<CameraMoveEvent> onCameraMove({required int mapId}) {
+    return _events(mapId).whereType<CameraMoveEvent>();
+  }
+
+  @override
+  Stream<CameraIdleEvent> onCameraIdle({required int mapId}) {
+    return _events(mapId).whereType<CameraIdleEvent>();
+  }
+
+  @override
+  Stream<MarkerTapEvent> onMarkerTap({required int mapId}) {
+    return _events(mapId).whereType<MarkerTapEvent>();
+  }
+
+  @override
+  Stream<InfoWindowTapEvent> onInfoWindowTap({required int mapId}) {
+    return _events(mapId).whereType<InfoWindowTapEvent>();
+  }
+
+  @override
+  Stream<MarkerDragStartEvent> onMarkerDragStart({required int mapId}) {
+    return _events(mapId).whereType<MarkerDragStartEvent>();
+  }
+
+  @override
+  Stream<MarkerDragEvent> onMarkerDrag({required int mapId}) {
+    return _events(mapId).whereType<MarkerDragEvent>();
+  }
+
+  @override
+  Stream<MarkerDragEndEvent> onMarkerDragEnd({required int mapId}) {
+    return _events(mapId).whereType<MarkerDragEndEvent>();
+  }
+
+  @override
+  Stream<PolylineTapEvent> onPolylineTap({required int mapId}) {
+    return _events(mapId).whereType<PolylineTapEvent>();
+  }
+
+  @override
+  Stream<PolygonTapEvent> onPolygonTap({required int mapId}) {
+    return _events(mapId).whereType<PolygonTapEvent>();
+  }
+
+  @override
+  Stream<CircleTapEvent> onCircleTap({required int mapId}) {
+    return _events(mapId).whereType<CircleTapEvent>();
+  }
+
+  @override
+  Stream<MapTapEvent> onTap({required int mapId}) {
+    return _events(mapId).whereType<MapTapEvent>();
+  }
+
+  @override
+  Stream<MapLongPressEvent> onLongPress({required int mapId}) {
+    return _events(mapId).whereType<MapLongPressEvent>();
+  }
+
+  Future<dynamic> _handleMethodCall(MethodCall call, int mapId) async {
+    switch (call.method) {
+      case 'camera#onMoveStarted':
+        _mapEventStreamController.add(CameraMoveStartedEvent(mapId));
+        break;
+      case 'camera#onMove':
+        _mapEventStreamController.add(CameraMoveEvent(
+          mapId,
+          CameraPosition.fromMap(call.arguments['position'])!,
+        ));
+        break;
+      case 'camera#onIdle':
+        _mapEventStreamController.add(CameraIdleEvent(mapId));
+        break;
+      case 'marker#onTap':
+        _mapEventStreamController.add(MarkerTapEvent(
+          mapId,
+          MarkerId(call.arguments['markerId'] as String),
+        ));
+        break;
+      case 'marker#onDragStart':
+        _mapEventStreamController.add(MarkerDragStartEvent(
+          mapId,
+          LatLng.fromJson(call.arguments['position'])!,
+          MarkerId(call.arguments['markerId'] as String),
+        ));
+        break;
+      case 'marker#onDrag':
+        _mapEventStreamController.add(MarkerDragEvent(
+          mapId,
+          LatLng.fromJson(call.arguments['position'])!,
+          MarkerId(call.arguments['markerId'] as String),
+        ));
+        break;
+      case 'marker#onDragEnd':
+        _mapEventStreamController.add(MarkerDragEndEvent(
+          mapId,
+          LatLng.fromJson(call.arguments['position'])!,
+          MarkerId(call.arguments['markerId'] as String),
+        ));
+        break;
+      case 'infoWindow#onTap':
+        _mapEventStreamController.add(InfoWindowTapEvent(
+          mapId,
+          MarkerId(call.arguments['markerId'] as String),
+        ));
+        break;
+      case 'polyline#onTap':
+        _mapEventStreamController.add(PolylineTapEvent(
+          mapId,
+          PolylineId(call.arguments['polylineId'] as String),
+        ));
+        break;
+      case 'polygon#onTap':
+        _mapEventStreamController.add(PolygonTapEvent(
+          mapId,
+          PolygonId(call.arguments['polygonId'] as String),
+        ));
+        break;
+      case 'circle#onTap':
+        _mapEventStreamController.add(CircleTapEvent(
+          mapId,
+          CircleId(call.arguments['circleId'] as String),
+        ));
+        break;
+      case 'map#onTap':
+        _mapEventStreamController.add(MapTapEvent(
+          mapId,
+          LatLng.fromJson(call.arguments['position'])!,
+        ));
+        break;
+      case 'map#onLongPress':
+        _mapEventStreamController.add(MapLongPressEvent(
+          mapId,
+          LatLng.fromJson(call.arguments['position'])!,
+        ));
+        break;
+      case 'tileOverlay#getTile':
+        final Map<TileOverlayId, TileOverlay>? tileOverlaysForThisMap =
+            _tileOverlays[mapId];
+        final String tileOverlayId = call.arguments['tileOverlayId'] as String;
+        final TileOverlay? tileOverlay =
+            tileOverlaysForThisMap?[TileOverlayId(tileOverlayId)];
+        final TileProvider? tileProvider = tileOverlay?.tileProvider;
+        if (tileProvider == null) {
+          return TileProvider.noTile.toJson();
+        }
+        final Tile tile = await tileProvider.getTile(
+          call.arguments['x'] as int,
+          call.arguments['y'] as int,
+          call.arguments['zoom'] as int?,
+        );
+        return tile.toJson();
+      default:
+        throw MissingPluginException();
+    }
+  }
+
+  @override
+  Future<void> updateMapOptions(
+    Map<String, dynamic> optionsUpdate, {
+    required int mapId,
+  }) {
+    assert(optionsUpdate != null);
+    return _channel(mapId).invokeMethod<void>(
+      'map#update',
+      <String, dynamic>{
+        'options': optionsUpdate,
+      },
+    );
+  }
+
+  @override
+  Future<void> updateMarkers(
+    MarkerUpdates markerUpdates, {
+    required int mapId,
+  }) {
+    assert(markerUpdates != null);
+    return _channel(mapId).invokeMethod<void>(
+      'markers#update',
+      markerUpdates.toJson(),
+    );
+  }
+
+  @override
+  Future<void> updatePolygons(
+    PolygonUpdates polygonUpdates, {
+    required int mapId,
+  }) {
+    assert(polygonUpdates != null);
+    return _channel(mapId).invokeMethod<void>(
+      'polygons#update',
+      polygonUpdates.toJson(),
+    );
+  }
+
+  @override
+  Future<void> updatePolylines(
+    PolylineUpdates polylineUpdates, {
+    required int mapId,
+  }) {
+    assert(polylineUpdates != null);
+    return _channel(mapId).invokeMethod<void>(
+      'polylines#update',
+      polylineUpdates.toJson(),
+    );
+  }
+
+  @override
+  Future<void> updateCircles(
+    CircleUpdates circleUpdates, {
+    required int mapId,
+  }) {
+    assert(circleUpdates != null);
+    return _channel(mapId).invokeMethod<void>(
+      'circles#update',
+      circleUpdates.toJson(),
+    );
+  }
+
+  @override
+  Future<void> updateTileOverlays({
+    required Set<TileOverlay> newTileOverlays,
+    required int mapId,
+  }) {
+    final Map<TileOverlayId, TileOverlay>? currentTileOverlays =
+        _tileOverlays[mapId];
+    final Set<TileOverlay> previousSet = currentTileOverlays != null
+        ? currentTileOverlays.values.toSet()
+        : <TileOverlay>{};
+    final _TileOverlayUpdates updates =
+        _TileOverlayUpdates.from(previousSet, newTileOverlays);
+    _tileOverlays[mapId] = keyTileOverlayId(newTileOverlays);
+    return _channel(mapId).invokeMethod<void>(
+      'tileOverlays#update',
+      updates.toJson(),
+    );
+  }
+
+  @override
+  Future<void> clearTileCache(
+    TileOverlayId tileOverlayId, {
+    required int mapId,
+  }) {
+    return _channel(mapId)
+        .invokeMethod<void>('tileOverlays#clearTileCache', <String, Object>{
+      'tileOverlayId': tileOverlayId.value,
+    });
+  }
+
+  @override
+  Future<void> animateCamera(
+    CameraUpdate cameraUpdate, {
+    required int mapId,
+  }) {
+    return _channel(mapId)
+        .invokeMethod<void>('camera#animate', <String, Object>{
+      'cameraUpdate': cameraUpdate.toJson(),
+    });
+  }
+
+  @override
+  Future<void> moveCamera(
+    CameraUpdate cameraUpdate, {
+    required int mapId,
+  }) {
+    return _channel(mapId).invokeMethod<void>('camera#move', <String, dynamic>{
+      'cameraUpdate': cameraUpdate.toJson(),
+    });
+  }
+
+  @override
+  Future<void> setMapStyle(
+    String? mapStyle, {
+    required int mapId,
+  }) async {
+    final List<dynamic> successAndError = (await _channel(mapId)
+        .invokeMethod<List<dynamic>>('map#setStyle', mapStyle))!;
+    final bool success = successAndError[0] as bool;
+    if (!success) {
+      throw MapStyleException(successAndError[1] as String);
+    }
+  }
+
+  @override
+  Future<LatLngBounds> getVisibleRegion({
+    required int mapId,
+  }) async {
+    final Map<String, dynamic> latLngBounds = (await _channel(mapId)
+        .invokeMapMethod<String, dynamic>('map#getVisibleRegion'))!;
+    final LatLng southwest = LatLng.fromJson(latLngBounds['southwest'])!;
+    final LatLng northeast = LatLng.fromJson(latLngBounds['northeast'])!;
+
+    return LatLngBounds(northeast: northeast, southwest: southwest);
+  }
+
+  @override
+  Future<ScreenCoordinate> getScreenCoordinate(
+    LatLng latLng, {
+    required int mapId,
+  }) async {
+    final Map<String, int> point = (await _channel(mapId)
+        .invokeMapMethod<String, int>(
+            'map#getScreenCoordinate', latLng.toJson()))!;
+
+    return ScreenCoordinate(x: point['x']!, y: point['y']!);
+  }
+
+  @override
+  Future<LatLng> getLatLng(
+    ScreenCoordinate screenCoordinate, {
+    required int mapId,
+  }) async {
+    final List<dynamic> latLng = (await _channel(mapId)
+        .invokeMethod<List<dynamic>>(
+            'map#getLatLng', screenCoordinate.toJson()))!;
+    return LatLng(latLng[0] as double, latLng[1] as double);
+  }
+
+  @override
+  Future<void> showMarkerInfoWindow(
+    MarkerId markerId, {
+    required int mapId,
+  }) {
+    assert(markerId != null);
+    return _channel(mapId).invokeMethod<void>(
+        'markers#showInfoWindow', <String, String>{'markerId': markerId.value});
+  }
+
+  @override
+  Future<void> hideMarkerInfoWindow(
+    MarkerId markerId, {
+    required int mapId,
+  }) {
+    assert(markerId != null);
+    return _channel(mapId).invokeMethod<void>(
+        'markers#hideInfoWindow', <String, String>{'markerId': markerId.value});
+  }
+
+  @override
+  Future<bool> isMarkerInfoWindowShown(
+    MarkerId markerId, {
+    required int mapId,
+  }) async {
+    assert(markerId != null);
+    return (await _channel(mapId).invokeMethod<bool>(
+        'markers#isInfoWindowShown',
+        <String, String>{'markerId': markerId.value}))!;
+  }
+
+  @override
+  Future<double> getZoomLevel({
+    required int mapId,
+  }) async {
+    return (await _channel(mapId).invokeMethod<double>('map#getZoomLevel'))!;
+  }
+
+  @override
+  Future<Uint8List?> takeSnapshot({
+    required int mapId,
+  }) {
+    return _channel(mapId).invokeMethod<Uint8List>('map#takeSnapshot');
+  }
+
+  Widget _buildView(
+    int creationId,
+    PlatformViewCreatedCallback onPlatformViewCreated, {
+    required MapWidgetConfiguration widgetConfiguration,
+    MapObjects mapObjects = const MapObjects(),
+    Map<String, dynamic> mapOptions = const <String, dynamic>{},
+  }) {
+    final Map<String, dynamic> creationParams = <String, dynamic>{
+      'initialCameraPosition':
+          widgetConfiguration.initialCameraPosition.toMap(),
+      'options': mapOptions,
+      'markersToAdd': serializeMarkerSet(mapObjects.markers),
+      'polygonsToAdd': serializePolygonSet(mapObjects.polygons),
+      'polylinesToAdd': serializePolylineSet(mapObjects.polylines),
+      'circlesToAdd': serializeCircleSet(mapObjects.circles),
+      'tileOverlaysToAdd': serializeTileOverlaySet(mapObjects.tileOverlays),
+    };
+
+    return UiKitView(
+      viewType: 'plugins.flutter.dev/google_maps_ios',
+      onPlatformViewCreated: onPlatformViewCreated,
+      gestureRecognizers: widgetConfiguration.gestureRecognizers,
+      creationParams: creationParams,
+      creationParamsCodec: const StandardMessageCodec(),
+    );
+  }
+
+  @override
+  Widget buildViewWithConfiguration(
+    int creationId,
+    PlatformViewCreatedCallback onPlatformViewCreated, {
+    required MapWidgetConfiguration widgetConfiguration,
+    MapConfiguration mapConfiguration = const MapConfiguration(),
+    MapObjects mapObjects = const MapObjects(),
+  }) {
+    return _buildView(
+      creationId,
+      onPlatformViewCreated,
+      widgetConfiguration: widgetConfiguration,
+      mapObjects: mapObjects,
+      mapOptions: _jsonForMapConfiguration(mapConfiguration),
+    );
+  }
+
+  @override
+  Widget buildViewWithTextDirection(
+    int creationId,
+    PlatformViewCreatedCallback onPlatformViewCreated, {
+    required CameraPosition initialCameraPosition,
+    required TextDirection textDirection,
+    Set<Marker> markers = const <Marker>{},
+    Set<Polygon> polygons = const <Polygon>{},
+    Set<Polyline> polylines = const <Polyline>{},
+    Set<Circle> circles = const <Circle>{},
+    Set<TileOverlay> tileOverlays = const <TileOverlay>{},
+    Set<Factory<OneSequenceGestureRecognizer>>? gestureRecognizers,
+    Map<String, dynamic> mapOptions = const <String, dynamic>{},
+  }) {
+    return _buildView(
+      creationId,
+      onPlatformViewCreated,
+      widgetConfiguration: MapWidgetConfiguration(
+          initialCameraPosition: initialCameraPosition,
+          textDirection: textDirection),
+      mapObjects: MapObjects(
+          markers: markers,
+          polygons: polygons,
+          polylines: polylines,
+          circles: circles,
+          tileOverlays: tileOverlays),
+      mapOptions: mapOptions,
+    );
+  }
+
+  @override
+  Widget buildView(
+    int creationId,
+    PlatformViewCreatedCallback onPlatformViewCreated, {
+    required CameraPosition initialCameraPosition,
+    Set<Marker> markers = const <Marker>{},
+    Set<Polygon> polygons = const <Polygon>{},
+    Set<Polyline> polylines = const <Polyline>{},
+    Set<Circle> circles = const <Circle>{},
+    Set<TileOverlay> tileOverlays = const <TileOverlay>{},
+    Set<Factory<OneSequenceGestureRecognizer>>? gestureRecognizers,
+    Map<String, dynamic> mapOptions = const <String, dynamic>{},
+  }) {
+    return buildViewWithTextDirection(
+      creationId,
+      onPlatformViewCreated,
+      initialCameraPosition: initialCameraPosition,
+      textDirection: TextDirection.ltr,
+      markers: markers,
+      polygons: polygons,
+      polylines: polylines,
+      circles: circles,
+      tileOverlays: tileOverlays,
+      gestureRecognizers: gestureRecognizers,
+      mapOptions: mapOptions,
+    );
+  }
+
+  @override
+  @visibleForTesting
+  void enableDebugInspection() {
+    GoogleMapsInspectorPlatform.instance =
+        GoogleMapsInspectorIOS((int mapId) => _channel(mapId));
+  }
+}
+
+Map<String, Object> _jsonForMapConfiguration(MapConfiguration config) {
+  final EdgeInsets? padding = config.padding;
+  return <String, Object>{
+    if (config.compassEnabled != null) 'compassEnabled': config.compassEnabled!,
+    if (config.mapToolbarEnabled != null)
+      'mapToolbarEnabled': config.mapToolbarEnabled!,
+    if (config.cameraTargetBounds != null)
+      'cameraTargetBounds': config.cameraTargetBounds!.toJson(),
+    if (config.mapType != null) 'mapType': config.mapType!.index,
+    if (config.minMaxZoomPreference != null)
+      'minMaxZoomPreference': config.minMaxZoomPreference!.toJson(),
+    if (config.rotateGesturesEnabled != null)
+      'rotateGesturesEnabled': config.rotateGesturesEnabled!,
+    if (config.scrollGesturesEnabled != null)
+      'scrollGesturesEnabled': config.scrollGesturesEnabled!,
+    if (config.tiltGesturesEnabled != null)
+      'tiltGesturesEnabled': config.tiltGesturesEnabled!,
+    if (config.zoomControlsEnabled != null)
+      'zoomControlsEnabled': config.zoomControlsEnabled!,
+    if (config.zoomGesturesEnabled != null)
+      'zoomGesturesEnabled': config.zoomGesturesEnabled!,
+    if (config.liteModeEnabled != null)
+      'liteModeEnabled': config.liteModeEnabled!,
+    if (config.trackCameraPosition != null)
+      'trackCameraPosition': config.trackCameraPosition!,
+    if (config.myLocationEnabled != null)
+      'myLocationEnabled': config.myLocationEnabled!,
+    if (config.myLocationButtonEnabled != null)
+      'myLocationButtonEnabled': config.myLocationButtonEnabled!,
+    if (padding != null)
+      'padding': <double>[
+        padding.top,
+        padding.left,
+        padding.bottom,
+        padding.right,
+      ],
+    if (config.indoorViewEnabled != null)
+      'indoorEnabled': config.indoorViewEnabled!,
+    if (config.trafficEnabled != null) 'trafficEnabled': config.trafficEnabled!,
+    if (config.buildingsEnabled != null)
+      'buildingsEnabled': config.buildingsEnabled!,
+  };
+}
+
+/// Update specification for a set of [TileOverlay]s.
+// TODO(stuartmorgan): Fix the missing export of this class in the platform
+// interface, and remove this copy.
+class _TileOverlayUpdates extends MapsObjectUpdates<TileOverlay> {
+  /// Computes [TileOverlayUpdates] given previous and current [TileOverlay]s.
+  _TileOverlayUpdates.from(Set<TileOverlay> previous, Set<TileOverlay> current)
+      : super.from(previous, current, objectName: 'tileOverlay');
+
+  /// Set of TileOverlays to be added in this update.
+  Set<TileOverlay> get tileOverlaysToAdd => objectsToAdd;
+
+  /// Set of TileOverlayIds to be removed in this update.
+  Set<TileOverlayId> get tileOverlayIdsToRemove =>
+      objectIdsToRemove.cast<TileOverlayId>();
+
+  /// Set of TileOverlays to be changed in this update.
+  Set<TileOverlay> get tileOverlaysToChange => objectsToChange;
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_ios/pubspec.yaml
new file mode 100644
index 0000000..f55da6e
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/pubspec.yaml
@@ -0,0 +1,29 @@
+name: google_maps_flutter_ios
+description: iOS implementation of the google_maps_flutter plugin.
+repository: https://github.com/flutter/plugins/tree/main/packages/google_maps_flutter/google_maps_flutter_ios
+issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+maps%22
+version: 2.1.10
+
+environment:
+  sdk: ">=2.14.0 <3.0.0"
+  flutter: ">=2.8.0"
+
+flutter:
+  plugin:
+    implements: google_maps_flutter
+    platforms:
+      ios:
+        pluginClass: FLTGoogleMapsPlugin
+        dartPluginClass: GoogleMapsFlutterIOS
+
+dependencies:
+  flutter:
+    sdk: flutter
+  google_maps_flutter_platform_interface: ^2.2.1
+  stream_transform: ^2.0.0
+
+dev_dependencies:
+  async: ^2.5.0
+  flutter_test:
+    sdk: flutter
+  plugin_platform_interface: ^2.0.0
diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/test/google_maps_flutter_ios_test.dart b/packages/google_maps_flutter/google_maps_flutter_ios/test/google_maps_flutter_ios_test.dart
new file mode 100644
index 0000000..136481c
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_ios/test/google_maps_flutter_ios_test.dart
@@ -0,0 +1,124 @@
+// 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 'package:async/async.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:google_maps_flutter_ios/google_maps_flutter_ios.dart';
+import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
+
+void main() {
+  TestWidgetsFlutterBinding.ensureInitialized();
+
+  late List<String> log;
+
+  setUp(() async {
+    log = <String>[];
+  });
+
+  /// Initializes a map with the given ID and canned responses, logging all
+  /// calls to [log].
+  void configureMockMap(
+    GoogleMapsFlutterIOS maps, {
+    required int mapId,
+    required Future<dynamic>? Function(MethodCall call) handler,
+  }) {
+    maps
+        .ensureChannelInitialized(mapId)
+        .setMockMethodCallHandler((MethodCall methodCall) {
+      log.add(methodCall.method);
+      return handler(methodCall);
+    });
+  }
+
+  Future<void> sendPlatformMessage(
+      int mapId, String method, Map<dynamic, dynamic> data) async {
+    final ByteData byteData =
+        const StandardMethodCodec().encodeMethodCall(MethodCall(method, data));
+    await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger
+        .handlePlatformMessage('plugins.flutter.dev/google_maps_ios_$mapId',
+            byteData, (ByteData? data) {});
+  }
+
+  test('registers instance', () async {
+    GoogleMapsFlutterIOS.registerWith();
+    expect(GoogleMapsFlutterPlatform.instance, isA<GoogleMapsFlutterIOS>());
+  });
+
+  // Calls each method that uses invokeMethod with a return type other than
+  // void to ensure that the casting/nullability handling succeeds.
+  //
+  // TODO(stuartmorgan): Remove this once there is real test coverage of
+  // each method, since that would cover this issue.
+  test('non-void invokeMethods handle types correctly', () async {
+    const int mapId = 0;
+    final GoogleMapsFlutterIOS maps = GoogleMapsFlutterIOS();
+    configureMockMap(maps, mapId: mapId,
+        handler: (MethodCall methodCall) async {
+      switch (methodCall.method) {
+        case 'map#getLatLng':
+          return <dynamic>[1.0, 2.0];
+        case 'markers#isInfoWindowShown':
+          return true;
+        case 'map#getZoomLevel':
+          return 2.5;
+        case 'map#takeSnapshot':
+          return null;
+      }
+    });
+
+    await maps.getLatLng(const ScreenCoordinate(x: 0, y: 0), mapId: mapId);
+    await maps.isMarkerInfoWindowShown(const MarkerId(''), mapId: mapId);
+    await maps.getZoomLevel(mapId: mapId);
+    await maps.takeSnapshot(mapId: mapId);
+    // Check that all the invokeMethod calls happened.
+    expect(log, <String>[
+      'map#getLatLng',
+      'markers#isInfoWindowShown',
+      'map#getZoomLevel',
+      'map#takeSnapshot',
+    ]);
+  });
+
+  test('markers send drag event to correct streams', () async {
+    const int mapId = 1;
+    final Map<dynamic, dynamic> jsonMarkerDragStartEvent = <dynamic, dynamic>{
+      'mapId': mapId,
+      'markerId': 'drag-start-marker',
+      'position': <double>[1.0, 1.0]
+    };
+    final Map<dynamic, dynamic> jsonMarkerDragEvent = <dynamic, dynamic>{
+      'mapId': mapId,
+      'markerId': 'drag-marker',
+      'position': <double>[1.0, 1.0]
+    };
+    final Map<dynamic, dynamic> jsonMarkerDragEndEvent = <dynamic, dynamic>{
+      'mapId': mapId,
+      'markerId': 'drag-end-marker',
+      'position': <double>[1.0, 1.0]
+    };
+
+    final GoogleMapsFlutterIOS maps = GoogleMapsFlutterIOS();
+    maps.ensureChannelInitialized(mapId);
+
+    final StreamQueue<MarkerDragStartEvent> markerDragStartStream =
+        StreamQueue<MarkerDragStartEvent>(maps.onMarkerDragStart(mapId: mapId));
+    final StreamQueue<MarkerDragEvent> markerDragStream =
+        StreamQueue<MarkerDragEvent>(maps.onMarkerDrag(mapId: mapId));
+    final StreamQueue<MarkerDragEndEvent> markerDragEndStream =
+        StreamQueue<MarkerDragEndEvent>(maps.onMarkerDragEnd(mapId: mapId));
+
+    await sendPlatformMessage(
+        mapId, 'marker#onDragStart', jsonMarkerDragStartEvent);
+    await sendPlatformMessage(mapId, 'marker#onDrag', jsonMarkerDragEvent);
+    await sendPlatformMessage(
+        mapId, 'marker#onDragEnd', jsonMarkerDragEndEvent);
+
+    expect((await markerDragStartStream.next).value.value,
+        equals('drag-start-marker'));
+    expect((await markerDragStream.next).value.value, equals('drag-marker'));
+    expect((await markerDragEndStream.next).value.value,
+        equals('drag-end-marker'));
+  });
+}