[google_maps_flutter] Fix TileOverlay cloning (#3771)

TileOverlay was not copying its TileProvider. During the NNBD migration,
map object updates were refactored to share code, and in that
refactoring TileOverlay update construction was aligned with other map
objects to use clones, so certain operations involving TileOverlays
started dropping the TileProvider.

Fixes https://github.com/flutter/flutter/issues/77500
diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter_platform_interface/CHANGELOG.md
index 1a62083..b6603d6 100644
--- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/CHANGELOG.md
+++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 2.0.4
+
+* Preserve the `TileProvider` when copying `TileOverlay`, fixing a
+  regression with tile overlays introduced in the null safety migration.
+
 ## 2.0.3
 
 * Fix type issues in `isMarkerInfoWindowShown` and `getZoomLevel` introduced
diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/tile_overlay.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/tile_overlay.dart
index 55dca55..8cdd2c4 100644
--- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/tile_overlay.dart
+++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/tile_overlay.dart
@@ -92,6 +92,7 @@
   /// unless overwritten by the specified parameters.
   TileOverlay copyWith({
     bool? fadeInParam,
+    TileProvider? tileProviderParam,
     double? transparencyParam,
     int? zIndexParam,
     bool? visibleParam,
@@ -100,6 +101,7 @@
     return TileOverlay(
       tileOverlayId: tileOverlayId,
       fadeIn: fadeInParam ?? fadeIn,
+      tileProvider: tileProviderParam ?? tileProvider,
       transparency: transparencyParam ?? transparency,
       zIndex: zIndexParam ?? zIndex,
       visible: visibleParam ?? visible,
@@ -137,6 +139,7 @@
     return other is TileOverlay &&
         tileOverlayId == other.tileOverlayId &&
         fadeIn == other.fadeIn &&
+        tileProvider == other.tileProvider &&
         transparency == other.transparency &&
         zIndex == other.zIndex &&
         visible == other.visible &&
@@ -144,6 +147,6 @@
   }
 
   @override
-  int get hashCode => hashValues(
-      tileOverlayId, fadeIn, transparency, zIndex, visible, tileSize);
+  int get hashCode => hashValues(tileOverlayId, fadeIn, tileProvider,
+      transparency, zIndex, visible, tileSize);
 }
diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_platform_interface/pubspec.yaml
index 6280991..4bce4aa 100644
--- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/pubspec.yaml
+++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/pubspec.yaml
@@ -3,7 +3,7 @@
 homepage: https://github.com/flutter/plugins/tree/master/packages/google_maps_flutter/google_maps_flutter_platform_interface
 # NOTE: We strongly prefer non-breaking changes, even at the expense of a
 # less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes
-version: 2.0.3
+version: 2.0.4
 
 dependencies:
   flutter:
diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/tile_overlay_test.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/tile_overlay_test.dart
index a17f86d..3a4c347 100644
--- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/tile_overlay_test.dart
+++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/tile_overlay_test.dart
@@ -7,6 +7,13 @@
 import 'package:flutter_test/flutter_test.dart';
 import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
 
+class _TestTileProvider extends TileProvider {
+  @override
+  Future<Tile> getTile(int x, int y, int? zoom) async {
+    return Tile(0, 0, null);
+  }
+}
+
 void main() {
   TestWidgetsFlutterBinding.ensureInitialized();
 
@@ -58,40 +65,65 @@
     });
 
     test('equality', () async {
-      const TileOverlay tileOverlay1 = TileOverlay(
+      final TileProvider tileProvider = _TestTileProvider();
+      final TileOverlay tileOverlay1 = TileOverlay(
           tileOverlayId: TileOverlayId('id1'),
           fadeIn: false,
-          tileProvider: null,
+          tileProvider: tileProvider,
           transparency: 0.1,
           zIndex: 1,
           visible: false,
           tileSize: 128);
-      const TileOverlay tileOverlay2 = TileOverlay(
+      final TileOverlay tileOverlaySameValues = TileOverlay(
           tileOverlayId: TileOverlayId('id1'),
           fadeIn: false,
-          tileProvider: null,
+          tileProvider: tileProvider,
           transparency: 0.1,
           zIndex: 1,
           visible: false,
           tileSize: 128);
-      const TileOverlay tileOverlay3 = TileOverlay(
+      final TileOverlay tileOverlayDifferentId = TileOverlay(
           tileOverlayId: TileOverlayId('id2'),
           fadeIn: false,
+          tileProvider: tileProvider,
+          transparency: 0.1,
+          zIndex: 1,
+          visible: false,
+          tileSize: 128);
+      final TileOverlay tileOverlayDifferentProvider = TileOverlay(
+          tileOverlayId: TileOverlayId('id1'),
+          fadeIn: false,
           tileProvider: null,
           transparency: 0.1,
           zIndex: 1,
           visible: false,
           tileSize: 128);
-      expect(tileOverlay1, tileOverlay2);
-      expect(tileOverlay1, isNot(tileOverlay3));
+      expect(tileOverlay1, tileOverlaySameValues);
+      expect(tileOverlay1, isNot(tileOverlayDifferentId));
+      expect(tileOverlay1, isNot(tileOverlayDifferentProvider));
+    });
+
+    test('clone', () async {
+      final TileProvider tileProvider = _TestTileProvider();
+      // Set non-default values for every parameter.
+      final TileOverlay tileOverlay = TileOverlay(
+          tileOverlayId: TileOverlayId('id1'),
+          fadeIn: false,
+          tileProvider: tileProvider,
+          transparency: 0.1,
+          zIndex: 1,
+          visible: false,
+          tileSize: 128);
+      expect(tileOverlay, tileOverlay.clone());
     });
 
     test('hashCode', () async {
+      final TileProvider tileProvider = _TestTileProvider();
       const TileOverlayId id = TileOverlayId('id1');
-      const TileOverlay tileOverlay = TileOverlay(
+      final TileOverlay tileOverlay = TileOverlay(
           tileOverlayId: id,
           fadeIn: false,
-          tileProvider: null,
+          tileProvider: tileProvider,
           transparency: 0.1,
           zIndex: 1,
           visible: false,
@@ -101,6 +133,7 @@
           hashValues(
               tileOverlay.tileOverlayId,
               tileOverlay.fadeIn,
+              tileOverlay.tileProvider,
               tileOverlay.transparency,
               tileOverlay.zIndex,
               tileOverlay.visible,