[google_maps_flutter] Add support to request map renderer for Android (#6619)

* [google_maps_flutter] support to request a specific map renderer for android

* [google_maps_flutter] Minor fixes to comments and error messages

* [google_maps_flutter] fix linting issues
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter_android/CHANGELOG.md
index 012a508..ac94475 100644
--- a/packages/google_maps_flutter/google_maps_flutter_android/CHANGELOG.md
+++ b/packages/google_maps_flutter/google_maps_flutter_android/CHANGELOG.md
@@ -1,5 +1,6 @@
-## NEXT
+## 2.4.0
 
+* Adds the ability to request a specific map renderer.
 * Updates code for new analysis options.
 
 ## 2.3.3
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/README.md b/packages/google_maps_flutter/google_maps_flutter_android/README.md
index 877b9bb..e07b0bc 100644
--- a/packages/google_maps_flutter/google_maps_flutter_android/README.md
+++ b/packages/google_maps_flutter/google_maps_flutter_android/README.md
@@ -48,7 +48,30 @@
 This mode will likely become the default in future versions if/when the
 missed updates issue can be resolved.
 
+## Map renderer
+
+This plugin supports the option to request a specific [map renderer][5].
+
+The renderer must be requested before creating GoogleMap instances, as the renderer can be initialized only once per application context.
+
+<?code-excerpt "readme_excerpts.dart (MapRenderer)"?>
+```dart
+AndroidMapRenderer mapRenderer = AndroidMapRenderer.platformDefault;
+// ···
+  final GoogleMapsFlutterPlatform mapsImplementation =
+      GoogleMapsFlutterPlatform.instance;
+  if (mapsImplementation is GoogleMapsFlutterAndroid) {
+    WidgetsFlutterBinding.ensureInitialized();
+    mapRenderer = await mapsImplementation
+        .initializeWithRenderer(AndroidMapRenderer.latest);
+  }
+```
+
+Available values are `AndroidMapRenderer.latest`, `AndroidMapRenderer.legacy`, `AndroidMapRenderer.platformDefault`.
+Note that getting the requested renderer as a response is not guaranteed.
+
 [1]: https://pub.dev/packages/google_maps_flutter
 [2]: https://flutter.dev/docs/development/packages-and-plugins/developing-packages#endorsed-federated-plugin
 [3]: https://docs.flutter.dev/development/platform-integration/android/platform-views
 [4]: https://github.com/flutter/flutter/issues/103686
+[5]: https://developers.google.com/maps/documentation/android-sdk/renderer
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/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
index ca9ac18..ffa2412 100644
--- a/packages/google_maps_flutter/google_maps_flutter_android/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
@@ -17,11 +17,15 @@
 
   private final BinaryMessenger binaryMessenger;
   private final LifecycleProvider lifecycleProvider;
+  private final GoogleMapInitializer googleMapInitializer;
 
-  GoogleMapFactory(BinaryMessenger binaryMessenger, LifecycleProvider lifecycleProvider) {
+  GoogleMapFactory(
+      BinaryMessenger binaryMessenger, Context context, LifecycleProvider lifecycleProvider) {
     super(StandardMessageCodec.INSTANCE);
+
     this.binaryMessenger = binaryMessenger;
     this.lifecycleProvider = lifecycleProvider;
+    this.googleMapInitializer = new GoogleMapInitializer(context, binaryMessenger);
   }
 
   @SuppressWarnings("unchecked")
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapInitializer.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapInitializer.java
new file mode 100644
index 0000000..a113c0a
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapInitializer.java
@@ -0,0 +1,109 @@
+// 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 android.content.Context;
+import androidx.annotation.VisibleForTesting;
+import com.google.android.gms.maps.MapsInitializer;
+import com.google.android.gms.maps.MapsInitializer.Renderer;
+import com.google.android.gms.maps.OnMapsSdkInitializedCallback;
+import io.flutter.plugin.common.BinaryMessenger;
+import io.flutter.plugin.common.MethodCall;
+import io.flutter.plugin.common.MethodChannel;
+
+/** GoogleMaps initializer used to initialize the Google Maps SDK with preferred settings. */
+final class GoogleMapInitializer
+    implements OnMapsSdkInitializedCallback, MethodChannel.MethodCallHandler {
+  private final MethodChannel methodChannel;
+  private final Context context;
+  private static MethodChannel.Result initializationResult;
+  private boolean rendererInitialized = false;
+
+  GoogleMapInitializer(Context context, BinaryMessenger binaryMessenger) {
+    this.context = context;
+
+    methodChannel =
+        new MethodChannel(binaryMessenger, "plugins.flutter.dev/google_maps_android_initializer");
+    methodChannel.setMethodCallHandler(this);
+  }
+
+  @Override
+  public void onMethodCall(MethodCall call, MethodChannel.Result result) {
+    switch (call.method) {
+      case "initializer#preferRenderer":
+        {
+          String preferredRenderer = (String) call.argument("value");
+          initializeWithPreferredRenderer(preferredRenderer, result);
+          break;
+        }
+      default:
+        result.notImplemented();
+    }
+  }
+
+  /**
+   * Initializes map renderer to with preferred renderer type. Renderer can be initialized only once
+   * per application context.
+   *
+   * <p>Supported renderer types are "latest", "legacy" and "default".
+   */
+  private void initializeWithPreferredRenderer(
+      String preferredRenderer, MethodChannel.Result result) {
+    if (rendererInitialized || initializationResult != null) {
+      result.error(
+          "Renderer already initialized", "Renderer initialization called multiple times", null);
+    } else {
+      initializationResult = result;
+      switch (preferredRenderer) {
+        case "latest":
+          initializeWithRendererRequest(Renderer.LATEST);
+          break;
+        case "legacy":
+          initializeWithRendererRequest(Renderer.LEGACY);
+          break;
+        case "default":
+          initializeWithRendererRequest(null);
+          break;
+        default:
+          initializationResult.error(
+              "Invalid renderer type",
+              "Renderer initialization called with invalid renderer type",
+              null);
+          initializationResult = null;
+      }
+    }
+  }
+
+  /**
+   * Initializes map renderer to with preferred renderer type.
+   *
+   * <p>This method is visible for testing purposes only and should never be used outside this
+   * class.
+   */
+  @VisibleForTesting
+  public void initializeWithRendererRequest(MapsInitializer.Renderer renderer) {
+    MapsInitializer.initialize(context, renderer, this);
+  }
+
+  /** Is called by Google Maps SDK to determine which version of the renderer was initialized. */
+  @Override
+  public void onMapsSdkInitialized(MapsInitializer.Renderer renderer) {
+    rendererInitialized = true;
+    if (initializationResult != null) {
+      switch (renderer) {
+        case LATEST:
+          initializationResult.success("latest");
+          break;
+        case LEGACY:
+          initializationResult.success("legacy");
+          break;
+        default:
+          initializationResult.error(
+              "Unknown renderer type", "Initialized with unknown renderer type", null);
+      }
+      initializationResult = null;
+    }
+  }
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/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
index 715b357..20fc15e 100644
--- a/packages/google_maps_flutter/google_maps_flutter_android/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
@@ -46,6 +46,7 @@
               VIEW_TYPE,
               new GoogleMapFactory(
                   registrar.messenger(),
+                  registrar.context(),
                   new LifecycleProvider() {
                     @Override
                     public Lifecycle getLifecycle() {
@@ -57,7 +58,10 @@
           .platformViewRegistry()
           .registerViewFactory(
               VIEW_TYPE,
-              new GoogleMapFactory(registrar.messenger(), new ProxyLifecycleProvider(activity)));
+              new GoogleMapFactory(
+                  registrar.messenger(),
+                  registrar.context(),
+                  new ProxyLifecycleProvider(activity)));
     }
   }
 
@@ -73,6 +77,7 @@
             VIEW_TYPE,
             new GoogleMapFactory(
                 binding.getBinaryMessenger(),
+                binding.getApplicationContext(),
                 new LifecycleProvider() {
                   @Nullable
                   @Override
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/GoogleMapInitializerTest.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/GoogleMapInitializerTest.java
new file mode 100644
index 0000000..2f9f5e5
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/test/java/io/flutter/plugins/googlemaps/GoogleMapInitializerTest.java
@@ -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.
+
+package io.flutter.plugins.googlemaps;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.os.Build;
+import androidx.test.core.app.ApplicationProvider;
+import com.google.android.gms.maps.MapsInitializer.Renderer;
+import io.flutter.plugin.common.BinaryMessenger;
+import io.flutter.plugin.common.MethodCall;
+import io.flutter.plugin.common.MethodChannel;
+import java.util.HashMap;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(sdk = Build.VERSION_CODES.P)
+public class GoogleMapInitializerTest {
+  private GoogleMapInitializer googleMapInitializer;
+
+  @Mock BinaryMessenger mockMessenger;
+
+  @Before
+  public void before() {
+    MockitoAnnotations.openMocks(this);
+    Context context = ApplicationProvider.getApplicationContext();
+    googleMapInitializer = spy(new GoogleMapInitializer(context, mockMessenger));
+  }
+
+  @Test
+  public void initializer_OnMapsSdkInitializedWithLatestRenderer() {
+    doNothing().when(googleMapInitializer).initializeWithRendererRequest(Renderer.LATEST);
+    MethodChannel.Result result = mock(MethodChannel.Result.class);
+    googleMapInitializer.onMethodCall(
+        new MethodCall(
+            "initializer#preferRenderer",
+            new HashMap<String, Object>() {
+              {
+                put("value", "latest");
+              }
+            }),
+        result);
+    googleMapInitializer.onMapsSdkInitialized(Renderer.LATEST);
+    verify(result, times(1)).success("latest");
+    verify(result, never()).error(any(), any(), any());
+  }
+
+  @Test
+  public void initializer_OnMapsSdkInitializedWithLegacyRenderer() {
+    doNothing().when(googleMapInitializer).initializeWithRendererRequest(Renderer.LEGACY);
+    MethodChannel.Result result = mock(MethodChannel.Result.class);
+    googleMapInitializer.onMethodCall(
+        new MethodCall(
+            "initializer#preferRenderer",
+            new HashMap<String, Object>() {
+              {
+                put("value", "legacy");
+              }
+            }),
+        result);
+    googleMapInitializer.onMapsSdkInitialized(Renderer.LEGACY);
+    verify(result, times(1)).success("legacy");
+    verify(result, never()).error(any(), any(), any());
+  }
+
+  @Test
+  public void initializer_onMethodCallWithUnknownRenderer() {
+    doNothing().when(googleMapInitializer).initializeWithRendererRequest(Renderer.LEGACY);
+    MethodChannel.Result result = mock(MethodChannel.Result.class);
+    googleMapInitializer.onMethodCall(
+        new MethodCall(
+            "initializer#preferRenderer",
+            new HashMap<String, Object>() {
+              {
+                put("value", "wrong_renderer");
+              }
+            }),
+        result);
+    verify(result, never()).success(any());
+    verify(result, times(1)).error(eq("Invalid renderer type"), any(), any());
+  }
+}
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_tests.dart
similarity index 99%
rename from packages/google_maps_flutter/google_maps_flutter_android/example/integration_test/google_maps_test.dart
rename to packages/google_maps_flutter/google_maps_flutter_android/example/integration_test/google_maps_tests.dart
index 0945740..bd72b7b 100644
--- 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_tests.dart
@@ -12,15 +12,13 @@
 import 'package:google_maps_flutter_android/google_maps_flutter_android.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();
+void googleMapsTests() {
   GoogleMapsFlutterPlatform.instance.enableDebugInspection();
 
   // Repeatedly checks an asynchronous value against a test condition, waiting
@@ -511,7 +509,9 @@
         await waitForValueMatchingPredicate<LatLngBounds>(
                 tester,
                 () => mapController.getVisibleRegion(),
-                (LatLngBounds bounds) => bounds != zeroLatLngBounds) ??
+                (LatLngBounds bounds) =>
+                    bounds != zeroLatLngBounds &&
+                    bounds.northeast != bounds.southwest) ??
             zeroLatLngBounds;
     expect(firstVisibleRegion, isNot(zeroLatLngBounds));
     expect(firstVisibleRegion.contains(_kInitialMapCenter), isTrue);
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/integration_test/latest_renderer_test.dart b/packages/google_maps_flutter/google_maps_flutter_android/example/integration_test/latest_renderer_test.dart
new file mode 100644
index 0000000..64bff8f
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/example/integration_test/latest_renderer_test.dart
@@ -0,0 +1,40 @@
+// 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/services.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';
+import 'package:integration_test/integration_test.dart';
+
+import 'google_maps_tests.dart' show googleMapsTests;
+
+void main() {
+  late AndroidMapRenderer initializedRenderer;
+  IntegrationTestWidgetsFlutterBinding.ensureInitialized();
+
+  setUpAll(() async {
+    final GoogleMapsFlutterAndroid instance =
+        GoogleMapsFlutterPlatform.instance as GoogleMapsFlutterAndroid;
+    initializedRenderer =
+        await instance.initializeWithRenderer(AndroidMapRenderer.latest);
+  });
+
+  testWidgets('initialized with latest renderer', (WidgetTester _) async {
+    expect(initializedRenderer, AndroidMapRenderer.latest);
+  });
+
+  testWidgets('throws PlatformException on multiple renderer initializations',
+      (WidgetTester _) async {
+    final GoogleMapsFlutterAndroid instance =
+        GoogleMapsFlutterPlatform.instance as GoogleMapsFlutterAndroid;
+    expect(
+        () async => instance.initializeWithRenderer(AndroidMapRenderer.latest),
+        throwsA(isA<PlatformException>().having((PlatformException e) => e.code,
+            'code', 'Renderer already initialized')));
+  });
+
+  // Run tests.
+  googleMapsTests();
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/integration_test/legacy_renderer_test.dart b/packages/google_maps_flutter/google_maps_flutter_android/example/integration_test/legacy_renderer_test.dart
new file mode 100644
index 0000000..95b1134
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_android/example/integration_test/legacy_renderer_test.dart
@@ -0,0 +1,40 @@
+// 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/services.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';
+import 'package:integration_test/integration_test.dart';
+
+import 'google_maps_tests.dart' show googleMapsTests;
+
+void main() {
+  late AndroidMapRenderer initializedRenderer;
+  IntegrationTestWidgetsFlutterBinding.ensureInitialized();
+
+  setUpAll(() async {
+    final GoogleMapsFlutterAndroid instance =
+        GoogleMapsFlutterPlatform.instance as GoogleMapsFlutterAndroid;
+    initializedRenderer =
+        await instance.initializeWithRenderer(AndroidMapRenderer.legacy);
+  });
+
+  testWidgets('initialized with legacy renderer', (WidgetTester _) async {
+    expect(initializedRenderer, AndroidMapRenderer.legacy);
+  });
+
+  testWidgets('throws PlatformException on multiple renderer initializations',
+      (WidgetTester _) async {
+    final GoogleMapsFlutterAndroid instance =
+        GoogleMapsFlutterPlatform.instance as GoogleMapsFlutterAndroid;
+    expect(
+        () async => instance.initializeWithRenderer(AndroidMapRenderer.legacy),
+        throwsA(isA<PlatformException>().having((PlatformException e) => e.code,
+            'code', 'Renderer already initialized')));
+  });
+
+  // Run tests.
+  googleMapsTests();
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/example/lib/readme_excerpts.dart b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/readme_excerpts.dart
index 5911c06..0f6b26d 100644
--- a/packages/google_maps_flutter/google_maps_flutter_android/example/lib/readme_excerpts.dart
+++ b/packages/google_maps_flutter/google_maps_flutter_android/example/lib/readme_excerpts.dart
@@ -2,6 +2,8 @@
 // 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';
 // #docregion DisplayMode
 import 'package:google_maps_flutter_android/google_maps_flutter_android.dart';
@@ -15,7 +17,44 @@
     mapsImplementation.useAndroidViewSurface = true;
   }
   // #enddocregion DisplayMode
-  runApp(const MaterialApp());
+  runApp(const MyApp());
   // #docregion DisplayMode
 }
 // #enddocregion DisplayMode
+
+class MyApp extends StatefulWidget {
+  const MyApp({Key? key}) : super(key: key);
+
+  @override
+  State<MyApp> createState() => _MyAppState();
+}
+
+class _MyAppState extends State<MyApp> {
+  // #docregion MapRenderer
+  AndroidMapRenderer mapRenderer = AndroidMapRenderer.platformDefault;
+  // #enddocregion MapRenderer
+
+  @override
+  Widget build(BuildContext context) {
+    return MaterialApp(
+      home: Scaffold(
+        appBar: AppBar(
+          title: const Text('README snippet app'),
+        ),
+        body: const Text('See example in main.dart'),
+      ),
+    );
+  }
+
+  Future<void> initializeLatestMapRenderer() async {
+    // #docregion MapRenderer
+    final GoogleMapsFlutterPlatform mapsImplementation =
+        GoogleMapsFlutterPlatform.instance;
+    if (mapsImplementation is GoogleMapsFlutterAndroid) {
+      WidgetsFlutterBinding.ensureInitialized();
+      mapRenderer = await mapsImplementation
+          .initializeWithRenderer(AndroidMapRenderer.latest);
+    }
+    // #enddocregion MapRenderer
+  }
+}
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
index 06c5bdc..11af3fe 100644
--- 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
@@ -41,6 +41,19 @@
   }
 }
 
+/// The possible android map renderer types that can be
+/// requested from the native Google Maps SDK.
+enum AndroidMapRenderer {
+  /// Latest renderer type.
+  latest,
+
+  /// Legacy renderer type.
+  legacy,
+
+  /// Requests the default map renderer type.
+  platformDefault,
+}
+
 /// An implementation of [GoogleMapsFlutterPlatform] for Android.
 class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform {
   /// Registers the Android implementation of GoogleMapsFlutterPlatform.
@@ -48,6 +61,10 @@
     GoogleMapsFlutterPlatform.instance = GoogleMapsFlutterAndroid();
   }
 
+  /// The method channel used to initialize the native Google Maps SDK.
+  final MethodChannel _initializerChannel = const MethodChannel(
+      'plugins.flutter.dev/google_maps_android_initializer');
+
   // Keep a collection of id -> channel
   // Every method call passes the int mapId
   final Map<int, MethodChannel> _channels = <int, MethodChannel>{};
@@ -480,6 +497,52 @@
   /// Currently defaults to true, but the default is subject to change.
   bool useAndroidViewSurface = true;
 
+  /// Requests Google Map Renderer with [AndroidMapRenderer] type.
+  ///
+  /// See https://pub.dev/packages/google_maps_flutter_android#map-renderer
+  /// for more information.
+  ///
+  /// The renderer must be requested before creating GoogleMap instances as the
+  /// renderer can be initialized only once per application context.
+  /// Throws a [PlatformException] if method is called multiple times.
+  ///
+  /// The returned [Future] completes after renderer has been initialized.
+  /// Initialized [AndroidMapRenderer] type is returned.
+  Future<AndroidMapRenderer> initializeWithRenderer(
+      AndroidMapRenderer? rendererType) async {
+    String preferredRenderer;
+    switch (rendererType) {
+      case AndroidMapRenderer.latest:
+        preferredRenderer = 'latest';
+        break;
+      case AndroidMapRenderer.legacy:
+        preferredRenderer = 'legacy';
+        break;
+      case AndroidMapRenderer.platformDefault:
+      default:
+        preferredRenderer = 'default';
+    }
+
+    final String? initializedRenderer = await _initializerChannel
+        .invokeMethod<String>('initializer#preferRenderer',
+            <String, dynamic>{'value': preferredRenderer});
+
+    if (initializedRenderer == null) {
+      throw AndroidMapRendererException('Failed to initialize map renderer.');
+    }
+
+    // Returns mapped [AndroidMapRenderer] enum type.
+    switch (initializedRenderer) {
+      case 'latest':
+        return AndroidMapRenderer.latest;
+      case 'legacy':
+        return AndroidMapRenderer.legacy;
+      default:
+        throw AndroidMapRendererException(
+            'Failed to initialize latest or legacy renderer, got $initializedRenderer.');
+    }
+  }
+
   Widget _buildView(
     int creationId,
     PlatformViewCreatedCallback onPlatformViewCreated, {
@@ -689,3 +752,16 @@
   /// Set of TileOverlays to be changed in this update.
   Set<TileOverlay> get tileOverlaysToChange => objectsToChange;
 }
+
+/// Thrown to indicate that a platform interaction failed to initialize renderer.
+class AndroidMapRendererException implements Exception {
+  /// Creates a [AndroidMapRendererException] with an optional human-readable
+  /// error message.
+  AndroidMapRendererException([this.message]);
+
+  /// A human-readable error message, possibly null.
+  final String? message;
+
+  @override
+  String toString() => 'AndroidMapRendererException($message)';
+}
diff --git a/packages/google_maps_flutter/google_maps_flutter_android/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_android/pubspec.yaml
index d20322b..bdf2f41 100644
--- a/packages/google_maps_flutter/google_maps_flutter_android/pubspec.yaml
+++ b/packages/google_maps_flutter/google_maps_flutter_android/pubspec.yaml
@@ -2,7 +2,7 @@
 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.3.3
+version: 2.4.0
 
 environment:
   sdk: ">=2.14.0 <3.0.0"