From 7a48bfb25774ae2e2c2a7519c714a5ab97ea9229 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Wed, 18 May 2022 17:59:06 -0700 Subject: [PATCH 01/12] Fix issue where markers aren't updated in Flutter v3.0.0 --- .../google_maps_flutter/CHANGELOG.md | 3 +- .../googlemaps/GoogleMapController.java | 33 +++++++++++++++++++ .../google_maps_flutter/pubspec.yaml | 2 +- 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md index 57d24e2196a5..c342e3af8be7 100644 --- a/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md +++ b/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md @@ -1,5 +1,6 @@ -## NEXT +## 2.1.6 +* Fixes issue in Flutter v3.0.0 where markers aren't updated. * Fixes iOS native unit tests on M1 devices. * Minor fixes for new analysis options. 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/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java index 9b8810354b8f..8c504d93c368 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/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java @@ -12,6 +12,7 @@ import android.graphics.Point; import android.os.Bundle; import android.util.Log; +import android.view.Choreographer; import android.view.View; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -151,6 +152,17 @@ public void onMapReady(GoogleMap googleMap) { updateInitialTileOverlays(); } + private static void postFrameCallback(Runnable f) { + Choreographer.getInstance().postFrameCallback( + new Choreographer.FrameCallback() { + @Override + public void doFrame(long frameTimeNanos) { + f.run(); + } + } + ); + } + @Override public void onMethodCall(MethodCall call, MethodChannel.Result result) { switch (call.method) { @@ -250,6 +262,27 @@ public void onSnapshotReady(Bitmap bitmap) { markersController.changeMarkers(markersToChange); List markerIdsToRemove = call.argument("markerIdsToRemove"); markersController.removeMarkers(markerIdsToRemove); + + // gmscore GL renderer uses a TextureView. + // Android platform views that are displayed as a texture after Flutter v3.0.0. + // require that the view hierarchy is notified after all drawing operations have been flushed. + // Since the GL renderer doesn't use standard Android views, and instead uses GL directly, + // we notify the view hierarchy by invalidating the view. + // Unfortunately, when OnMapLoadedCallback is fired, the texture may not have been updated yet. + // To workaround this limitation, wait two frames. + // This ensures that at least the frame budget (16.66ms at 60hz) have passed since the + // drawing operation was issued. + googleMap.setOnMapLoadedCallback(new GoogleMap.OnMapLoadedCallback() { + @Override + public void onMapLoaded() { + postFrameCallback(() -> { + postFrameCallback(() -> { + mapView.invalidate(); + }); + }); + } + }); + result.success(null); break; } diff --git a/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml index 831f3ccd2963..59ee23d0b260 100644 --- a/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml @@ -2,7 +2,7 @@ name: google_maps_flutter description: A Flutter plugin for integrating Google Maps in iOS and Android applications. repository: https://github.com/flutter/plugins/tree/main/packages/google_maps_flutter/google_maps_flutter issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+maps%22 -version: 2.1.5 +version: 2.1.6 environment: sdk: ">=2.14.0 <3.0.0" From 0df211313c76938cba2b152e98754ab87922220f Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Wed, 18 May 2022 18:18:40 -0700 Subject: [PATCH 02/12] format --- .../googlemaps/GoogleMapController.java | 37 ++++++++++--------- 1 file changed, 20 insertions(+), 17 deletions(-) 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/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java index 8c504d93c368..d6cfb2aadc64 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/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java @@ -153,14 +153,14 @@ public void onMapReady(GoogleMap googleMap) { } private static void postFrameCallback(Runnable f) { - Choreographer.getInstance().postFrameCallback( - new Choreographer.FrameCallback() { - @Override - public void doFrame(long frameTimeNanos) { - f.run(); - } - } - ); + Choreographer.getInstance() + .postFrameCallback( + new Choreographer.FrameCallback() { + @Override + public void doFrame(long frameTimeNanos) { + f.run(); + } + }); } @Override @@ -272,16 +272,19 @@ public void onSnapshotReady(Bitmap bitmap) { // To workaround this limitation, wait two frames. // This ensures that at least the frame budget (16.66ms at 60hz) have passed since the // drawing operation was issued. - googleMap.setOnMapLoadedCallback(new GoogleMap.OnMapLoadedCallback() { - @Override - public void onMapLoaded() { - postFrameCallback(() -> { - postFrameCallback(() -> { - mapView.invalidate(); - }); + googleMap.setOnMapLoadedCallback( + new GoogleMap.OnMapLoadedCallback() { + @Override + public void onMapLoaded() { + postFrameCallback( + () -> { + postFrameCallback( + () -> { + mapView.invalidate(); + }); + }); + } }); - } - }); result.success(null); break; From 28ae948e21b8ea00f3c01a8259d4fae0c5c2ecdc Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Wed, 18 May 2022 19:41:17 -0700 Subject: [PATCH 03/12] Add unit test --- .../googlemaps/GoogleMapController.java | 36 ++++++++++++------- .../googlemaps/GoogleMapControllerTest.java | 23 ++++++++++++ 2 files changed, 46 insertions(+), 13 deletions(-) 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/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java index d6cfb2aadc64..1e2d8b65fe0f 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/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java @@ -16,6 +16,7 @@ import android.view.View; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import androidx.lifecycle.DefaultLifecycleObserver; import androidx.lifecycle.Lifecycle; import androidx.lifecycle.LifecycleOwner; @@ -110,6 +111,11 @@ public View getView() { return mapView; } + @VisibleForTesting + /*package*/ void setView(MapView view) { + mapView = view; + } + void init() { lifecycleProvider.getLifecycle().addObserver(this); mapView.getMapAsync(this); @@ -272,19 +278,23 @@ public void onSnapshotReady(Bitmap bitmap) { // To workaround this limitation, wait two frames. // This ensures that at least the frame budget (16.66ms at 60hz) have passed since the // drawing operation was issued. - googleMap.setOnMapLoadedCallback( - new GoogleMap.OnMapLoadedCallback() { - @Override - public void onMapLoaded() { - postFrameCallback( - () -> { - postFrameCallback( - () -> { - mapView.invalidate(); - }); - }); - } - }); + if (googleMap != null) { + googleMap.setOnMapLoadedCallback( + new GoogleMap.OnMapLoadedCallback() { + @Override + public void onMapLoaded() { + postFrameCallback( + () -> { + postFrameCallback( + () -> { + if (mapView != null) { + mapView.invalidate(); + } + }); + }); + } + }); + } result.success(null); break; 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/src/test/java/io/flutter/plugins/googlemaps/GoogleMapControllerTest.java index 6bda085caf46..07da421047df 100644 --- 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/src/test/java/io/flutter/plugins/googlemaps/GoogleMapControllerTest.java @@ -4,6 +4,8 @@ package io.flutter.plugins.googlemaps; +import static org.mockito.Mockito.verify; +import static org.mockito.ArgumentMatchers.any; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; @@ -12,15 +14,21 @@ import androidx.activity.ComponentActivity; import androidx.test.core.app.ApplicationProvider; import com.google.android.gms.maps.GoogleMap; +import com.google.android.gms.maps.MapView; import io.flutter.plugin.common.BinaryMessenger; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; +import java.util.HashMap; +import io.flutter.plugin.common.MethodChannel; +import io.flutter.plugin.common.MethodCall; +import static org.mockito.Mockito.mock; @RunWith(RobolectricTestRunner.class) @Config(sdk = Build.VERSION_CODES.P) @@ -58,4 +66,19 @@ public void OnDestroyReleaseTheMap() throws InterruptedException { googleMapController.onDestroy(activity); assertNull(googleMapController.getView()); } + + @Test + public void InvalidateMapAfterMarkersUpdate() throws InterruptedException { + googleMapController.onMapReady(mockGoogleMap); + MethodChannel.Result result = mock(MethodChannel.Result.class); + googleMapController.onMethodCall(new MethodCall​("markers#update", new HashMap()), result); + + ArgumentCaptor argument = ArgumentCaptor.forClass(GoogleMap.OnMapLoadedCallback.class); + verify(mockGoogleMap).setOnMapLoadedCallback(argument.capture()); + + MapView mapView = mock(MapView.class); + googleMapController.setView(mapView); + argument.getValue().onMapLoaded(); + verify(mapView).invalidate(); + } } From c868b5a4f1633c09c4466abe3239fa1a5b20f69b Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Wed, 18 May 2022 20:08:38 -0700 Subject: [PATCH 04/12] remove character --- .../io/flutter/plugins/googlemaps/GoogleMapControllerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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/src/test/java/io/flutter/plugins/googlemaps/GoogleMapControllerTest.java index 07da421047df..cc21ebc650db 100644 --- 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/src/test/java/io/flutter/plugins/googlemaps/GoogleMapControllerTest.java @@ -71,7 +71,7 @@ public void OnDestroyReleaseTheMap() throws InterruptedException { public void InvalidateMapAfterMarkersUpdate() throws InterruptedException { googleMapController.onMapReady(mockGoogleMap); MethodChannel.Result result = mock(MethodChannel.Result.class); - googleMapController.onMethodCall(new MethodCall​("markers#update", new HashMap()), result); + googleMapController.onMethodCall(new MethodCall("markers#update", new HashMap()), result); ArgumentCaptor argument = ArgumentCaptor.forClass(GoogleMap.OnMapLoadedCallback.class); verify(mockGoogleMap).setOnMapLoadedCallback(argument.capture()); From 31d0cbada0814b8afe12a4657c39591dc907f8c6 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Wed, 18 May 2022 21:21:28 -0700 Subject: [PATCH 05/12] format --- .../googlemaps/GoogleMapControllerTest.java | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) 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/src/test/java/io/flutter/plugins/googlemaps/GoogleMapControllerTest.java index cc21ebc650db..c301232f218e 100644 --- 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/src/test/java/io/flutter/plugins/googlemaps/GoogleMapControllerTest.java @@ -4,10 +4,11 @@ package io.flutter.plugins.googlemaps; -import static org.mockito.Mockito.verify; -import static org.mockito.ArgumentMatchers.any; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; import android.content.Context; import android.os.Build; @@ -16,6 +17,9 @@ import com.google.android.gms.maps.GoogleMap; import com.google.android.gms.maps.MapView; 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; @@ -25,10 +29,6 @@ import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; import org.robolectric.annotation.Config; -import java.util.HashMap; -import io.flutter.plugin.common.MethodChannel; -import io.flutter.plugin.common.MethodCall; -import static org.mockito.Mockito.mock; @RunWith(RobolectricTestRunner.class) @Config(sdk = Build.VERSION_CODES.P) @@ -71,13 +71,17 @@ public void OnDestroyReleaseTheMap() throws InterruptedException { public void InvalidateMapAfterMarkersUpdate() throws InterruptedException { googleMapController.onMapReady(mockGoogleMap); MethodChannel.Result result = mock(MethodChannel.Result.class); - googleMapController.onMethodCall(new MethodCall("markers#update", new HashMap()), result); + googleMapController.onMethodCall( + new MethodCall("markers#update", new HashMap()), result); - ArgumentCaptor argument = ArgumentCaptor.forClass(GoogleMap.OnMapLoadedCallback.class); + ArgumentCaptor argument = + ArgumentCaptor.forClass(GoogleMap.OnMapLoadedCallback.class); verify(mockGoogleMap).setOnMapLoadedCallback(argument.capture()); MapView mapView = mock(MapView.class); googleMapController.setView(mapView); + + verify(mapView, never()).invalidate(); argument.getValue().onMapLoaded(); verify(mapView).invalidate(); } From 52b890eb79a12a30efa87685a28ed7b46e1fec3b Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Wed, 18 May 2022 21:29:42 -0700 Subject: [PATCH 06/12] Add one more test --- .../googlemaps/GoogleMapControllerTest.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) 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/src/test/java/io/flutter/plugins/googlemaps/GoogleMapControllerTest.java index c301232f218e..1b22c4594be7 100644 --- 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/src/test/java/io/flutter/plugins/googlemaps/GoogleMapControllerTest.java @@ -85,4 +85,23 @@ public void InvalidateMapAfterMarkersUpdate() throws InterruptedException { argument.getValue().onMapLoaded(); verify(mapView).invalidate(); } + + @Test + public void UpdateMarkersAfterControllerIsDestroyed() throws InterruptedException { + googleMapController.onMapReady(mockGoogleMap); + MethodChannel.Result result = mock(MethodChannel.Result.class); + googleMapController.onMethodCall( + new MethodCall("markers#update", new HashMap()), result); + + ArgumentCaptor argument = + ArgumentCaptor.forClass(GoogleMap.OnMapLoadedCallback.class); + verify(mockGoogleMap).setOnMapLoadedCallback(argument.capture()); + + MapView mapView = mock(MapView.class); + googleMapController.setView(mapView); + googleMapController.onDestroy(activity); + + argument.getValue().onMapLoaded(); + verify(mapView, never()).invalidate(); + } } From 083474677e6f145a3e68963ab98c063f1e094206 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Fri, 20 May 2022 09:59:30 -0700 Subject: [PATCH 07/12] comments --- .../google_maps_flutter/CHANGELOG.md | 2 +- .../googlemaps/GoogleMapController.java | 68 +++++++++++-------- 2 files changed, 41 insertions(+), 29 deletions(-) diff --git a/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md index c342e3af8be7..ffdfb3e5af00 100644 --- a/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md +++ b/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md @@ -1,6 +1,6 @@ ## 2.1.6 -* Fixes issue in Flutter v3.0.0 where markers aren't updated. +* Fixes issue in Flutter v3.0.0 where markers aren't updated on Android. * Fixes iOS native unit tests on M1 devices. * Minor fixes for new analysis options. 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/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java index 1e2d8b65fe0f..940bc59a1bac 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/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java @@ -133,6 +133,43 @@ private CameraPosition getCameraPosition() { return trackCameraPosition ? googleMap.getCameraPosition() : null; } + /** + * Invalidates the map view after the map has finished rendering. + * + *

gmscore GL renderer uses a {@link android.view.TextureView}. Android platform views that are + * displayed as a texture after Flutter v3.0.0. require that the view hierarchy is notified after + * all drawing operations have been flushed. + * + *

Since the GL renderer doesn't use standard Android views, and instead uses GL directly, we + * notify the view hierarchy by invalidating the view. + * + *

Unfortunately, when {@link GoogleMap.OnMapLoadedCallback} is fired, the texture may not have + * been updated yet. + * + *

To workaround this limitation, wait two frames. This ensures that at least the frame budget + * (16.66ms at 60hz) have passed since the drawing operation was issued. + */ + private void invalidateMapIfNeeded() { + if (googleMap == null) { + return; + } + googleMap.setOnMapLoadedCallback( + new GoogleMap.OnMapLoadedCallback() { + @Override + public void onMapLoaded() { + postFrameCallback( + () -> { + postFrameCallback( + () -> { + if (mapView != null) { + mapView.invalidate(); + } + }); + }); + } + }); + } + @Override public void onMapReady(GoogleMap googleMap) { this.googleMap = googleMap; @@ -268,34 +305,9 @@ public void onSnapshotReady(Bitmap bitmap) { markersController.changeMarkers(markersToChange); List markerIdsToRemove = call.argument("markerIdsToRemove"); markersController.removeMarkers(markerIdsToRemove); - - // gmscore GL renderer uses a TextureView. - // Android platform views that are displayed as a texture after Flutter v3.0.0. - // require that the view hierarchy is notified after all drawing operations have been flushed. - // Since the GL renderer doesn't use standard Android views, and instead uses GL directly, - // we notify the view hierarchy by invalidating the view. - // Unfortunately, when OnMapLoadedCallback is fired, the texture may not have been updated yet. - // To workaround this limitation, wait two frames. - // This ensures that at least the frame budget (16.66ms at 60hz) have passed since the - // drawing operation was issued. - if (googleMap != null) { - googleMap.setOnMapLoadedCallback( - new GoogleMap.OnMapLoadedCallback() { - @Override - public void onMapLoaded() { - postFrameCallback( - () -> { - postFrameCallback( - () -> { - if (mapView != null) { - mapView.invalidate(); - } - }); - }); - } - }); - } - + // Workaround for https://github.com/flutter/flutter/issues/103686. + // After Flutter 3.0.0, markers aren't updated until the next view invalidation. + invalidateMapIfNeeded(); result.success(null); break; } From e87f22426645bc1c2dc3ff9dc01174a0314ce34b Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Mon, 23 May 2022 10:39:27 -0700 Subject: [PATCH 08/12] consider other updates as well --- .../googlemaps/GoogleMapController.java | 28 ++++++++---- .../googlemaps/GoogleMapControllerTest.java | 45 ++++++++++++++++++- 2 files changed, 63 insertions(+), 10 deletions(-) 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/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java index 940bc59a1bac..635d0e4d2434 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/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java @@ -150,9 +150,10 @@ private CameraPosition getCameraPosition() { * (16.66ms at 60hz) have passed since the drawing operation was issued. */ private void invalidateMapIfNeeded() { - if (googleMap == null) { + if (googleMap == null || invalidatePending) { return; } + invalidatePending = true; googleMap.setOnMapLoadedCallback( new GoogleMap.OnMapLoadedCallback() { @Override @@ -161,6 +162,7 @@ public void onMapLoaded() { () -> { postFrameCallback( () -> { + invalidatePending = false; if (mapView != null) { mapView.invalidate(); } @@ -170,6 +172,8 @@ public void onMapLoaded() { }); } + private boolean invalidatePending = false; + @Override public void onMapReady(GoogleMap googleMap) { this.googleMap = googleMap; @@ -305,10 +309,8 @@ public void onSnapshotReady(Bitmap bitmap) { markersController.changeMarkers(markersToChange); List markerIdsToRemove = call.argument("markerIdsToRemove"); markersController.removeMarkers(markerIdsToRemove); - // Workaround for https://github.com/flutter/flutter/issues/103686. - // After Flutter 3.0.0, markers aren't updated until the next view invalidation. - invalidateMapIfNeeded(); result.success(null); + invalidateMapIfNeeded(); break; } case "markers#showInfoWindow": @@ -338,6 +340,7 @@ public void onSnapshotReady(Bitmap bitmap) { List polygonIdsToRemove = call.argument("polygonIdsToRemove"); polygonsController.removePolygons(polygonIdsToRemove); result.success(null); + invalidateMapIfNeeded(); break; } case "polylines#update": @@ -349,6 +352,7 @@ public void onSnapshotReady(Bitmap bitmap) { List polylineIdsToRemove = call.argument("polylineIdsToRemove"); polylinesController.removePolylines(polylineIdsToRemove); result.success(null); + invalidateMapIfNeeded(); break; } case "circles#update": @@ -360,6 +364,7 @@ public void onSnapshotReady(Bitmap bitmap) { List circleIdsToRemove = call.argument("circleIdsToRemove"); circlesController.removeCircles(circleIdsToRemove); result.success(null); + invalidateMapIfNeeded(); break; } case "map#isCompassEnabled": @@ -432,12 +437,16 @@ public void onSnapshotReady(Bitmap bitmap) { } case "map#setStyle": { - String mapStyle = (String) call.arguments; boolean mapStyleSet; - if (mapStyle == null) { - mapStyleSet = googleMap.setMapStyle(null); + if (call.arguments instanceof String) { + String mapStyle = (String) call.arguments; + if (mapStyle == null) { + mapStyleSet = googleMap.setMapStyle(null); + } else { + mapStyleSet = googleMap.setMapStyle(new MapStyleOptions(mapStyle)); + } } else { - mapStyleSet = googleMap.setMapStyle(new MapStyleOptions(mapStyle)); + mapStyleSet = googleMap.setMapStyle(null); } ArrayList mapStyleResult = new ArrayList<>(2); mapStyleResult.add(mapStyleSet); @@ -446,6 +455,7 @@ public void onSnapshotReady(Bitmap bitmap) { "Unable to set the map style. Please check console logs for errors."); } result.success(mapStyleResult); + invalidateMapIfNeeded(); break; } case "tileOverlays#update": @@ -457,12 +467,14 @@ public void onSnapshotReady(Bitmap bitmap) { List tileOverlaysToRemove = call.argument("tileOverlayIdsToRemove"); tileOverlaysController.removeTileOverlays(tileOverlaysToRemove); result.success(null); + invalidateMapIfNeeded(); break; } case "tileOverlays#clearTileCache": { String tileOverlayId = call.argument("tileOverlayId"); tileOverlaysController.clearTileCache(tileOverlayId); + invalidateMapIfNeeded(); result.success(null); break; } 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/src/test/java/io/flutter/plugins/googlemaps/GoogleMapControllerTest.java index 1b22c4594be7..d8082b57e3db 100644 --- 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/src/test/java/io/flutter/plugins/googlemaps/GoogleMapControllerTest.java @@ -68,11 +68,52 @@ public void OnDestroyReleaseTheMap() throws InterruptedException { } @Test - public void InvalidateMapAfterMarkersUpdate() throws InterruptedException { + public void InvalidateMapAfterMethodCalls() throws InterruptedException { + String[] methodsThatTriggerInvalidation = { + "markers#update", + "polygons#update", + "polylines#update", + "circles#update", + "map#setStyle", + "tileOverlays#update", + "tileOverlays#clearTileCache" + }; + + for (String methodName : methodsThatTriggerInvalidation) { + googleMapController = + new GoogleMapController(0, context, mockMessenger, activity::getLifecycle, null); + googleMapController.init(); + + mockGoogleMap = mock(GoogleMap.class); + googleMapController.onMapReady(mockGoogleMap); + + MethodChannel.Result result = mock(MethodChannel.Result.class); + System.out.println(methodName); + googleMapController.onMethodCall( + new MethodCall(methodName, new HashMap()), result); + + ArgumentCaptor argument = + ArgumentCaptor.forClass(GoogleMap.OnMapLoadedCallback.class); + verify(mockGoogleMap).setOnMapLoadedCallback(argument.capture()); + + MapView mapView = mock(MapView.class); + googleMapController.setView(mapView); + + verify(mapView, never()).invalidate(); + argument.getValue().onMapLoaded(); + verify(mapView).invalidate(); + } + } + + @Test + public void InvalidateMapOnceAfterMethodCall() throws InterruptedException { googleMapController.onMapReady(mockGoogleMap); + MethodChannel.Result result = mock(MethodChannel.Result.class); googleMapController.onMethodCall( new MethodCall("markers#update", new HashMap()), result); + googleMapController.onMethodCall( + new MethodCall("polygons#update", new HashMap()), result); ArgumentCaptor argument = ArgumentCaptor.forClass(GoogleMap.OnMapLoadedCallback.class); @@ -87,7 +128,7 @@ public void InvalidateMapAfterMarkersUpdate() throws InterruptedException { } @Test - public void UpdateMarkersAfterControllerIsDestroyed() throws InterruptedException { + public void MethodCalledAfterControllerIsDestroyed() throws InterruptedException { googleMapController.onMapReady(mockGoogleMap); MethodChannel.Result result = mock(MethodChannel.Result.class); googleMapController.onMethodCall( From a0938ba62d06397bae3eede689c7d53315352a19 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Mon, 23 May 2022 10:50:47 -0700 Subject: [PATCH 09/12] changelog --- packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md index ffdfb3e5af00..b4fb6622edc2 100644 --- a/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md +++ b/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md @@ -1,6 +1,6 @@ ## 2.1.6 -* Fixes issue in Flutter v3.0.0 where markers aren't updated on Android. +* Fixes issue in Flutter v3.0.0 where some updates to the map don't take effect on Android. * Fixes iOS native unit tests on M1 devices. * Minor fixes for new analysis options. From 47473d851b48161824c9e60d1be2c26468cb8295 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Mon, 23 May 2022 10:51:50 -0700 Subject: [PATCH 10/12] organize --- .../googlemaps/GoogleMapController.java | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) 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/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java index 635d0e4d2434..1e4baacbefd3 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/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java @@ -133,6 +133,8 @@ private CameraPosition getCameraPosition() { return trackCameraPosition ? googleMap.getCameraPosition() : null; } + private boolean invalidatePending = false; + /** * Invalidates the map view after the map has finished rendering. * @@ -172,7 +174,16 @@ public void onMapLoaded() { }); } - private boolean invalidatePending = false; + private static void postFrameCallback(Runnable f) { + Choreographer.getInstance() + .postFrameCallback( + new Choreographer.FrameCallback() { + @Override + public void doFrame(long frameTimeNanos) { + f.run(); + } + }); + } @Override public void onMapReady(GoogleMap googleMap) { @@ -199,17 +210,6 @@ public void onMapReady(GoogleMap googleMap) { updateInitialTileOverlays(); } - private static void postFrameCallback(Runnable f) { - Choreographer.getInstance() - .postFrameCallback( - new Choreographer.FrameCallback() { - @Override - public void doFrame(long frameTimeNanos) { - f.run(); - } - }); - } - @Override public void onMethodCall(MethodCall call, MethodChannel.Result result) { switch (call.method) { From 6b2c68d2990fe235a505c688cf0a5545e8a2433d Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Mon, 23 May 2022 11:11:07 -0700 Subject: [PATCH 11/12] loadedCallbackPending --- .../flutter/plugins/googlemaps/GoogleMapController.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java index 1e4baacbefd3..5fba9d81fa1f 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/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java @@ -133,7 +133,7 @@ private CameraPosition getCameraPosition() { return trackCameraPosition ? googleMap.getCameraPosition() : null; } - private boolean invalidatePending = false; + private boolean loadedCallbackPending = false; /** * Invalidates the map view after the map has finished rendering. @@ -152,19 +152,19 @@ private CameraPosition getCameraPosition() { * (16.66ms at 60hz) have passed since the drawing operation was issued. */ private void invalidateMapIfNeeded() { - if (googleMap == null || invalidatePending) { + if (googleMap == null || loadedCallbackPending) { return; } - invalidatePending = true; + loadedCallbackPending = true; googleMap.setOnMapLoadedCallback( new GoogleMap.OnMapLoadedCallback() { @Override public void onMapLoaded() { + loadedCallbackPending = false; postFrameCallback( () -> { postFrameCallback( () -> { - invalidatePending = false; if (mapView != null) { mapView.invalidate(); } From 6d1f27f83761e2e389b5bb98c9e7801dd6cd3a31 Mon Sep 17 00:00:00 2001 From: Emmanuel Garcia Date: Tue, 24 May 2022 11:13:39 -0700 Subject: [PATCH 12/12] prevent potential race --- .../plugins/googlemaps/GoogleMapController.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) 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/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java index 5fba9d81fa1f..2c2287cf59d4 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/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java @@ -303,6 +303,7 @@ public void onSnapshotReady(Bitmap bitmap) { } case "markers#update": { + invalidateMapIfNeeded(); List markersToAdd = call.argument("markersToAdd"); markersController.addMarkers(markersToAdd); List markersToChange = call.argument("markersToChange"); @@ -310,7 +311,6 @@ public void onSnapshotReady(Bitmap bitmap) { List markerIdsToRemove = call.argument("markerIdsToRemove"); markersController.removeMarkers(markerIdsToRemove); result.success(null); - invalidateMapIfNeeded(); break; } case "markers#showInfoWindow": @@ -333,6 +333,7 @@ public void onSnapshotReady(Bitmap bitmap) { } case "polygons#update": { + invalidateMapIfNeeded(); List polygonsToAdd = call.argument("polygonsToAdd"); polygonsController.addPolygons(polygonsToAdd); List polygonsToChange = call.argument("polygonsToChange"); @@ -340,11 +341,11 @@ public void onSnapshotReady(Bitmap bitmap) { List polygonIdsToRemove = call.argument("polygonIdsToRemove"); polygonsController.removePolygons(polygonIdsToRemove); result.success(null); - invalidateMapIfNeeded(); break; } case "polylines#update": { + invalidateMapIfNeeded(); List polylinesToAdd = call.argument("polylinesToAdd"); polylinesController.addPolylines(polylinesToAdd); List polylinesToChange = call.argument("polylinesToChange"); @@ -352,11 +353,11 @@ public void onSnapshotReady(Bitmap bitmap) { List polylineIdsToRemove = call.argument("polylineIdsToRemove"); polylinesController.removePolylines(polylineIdsToRemove); result.success(null); - invalidateMapIfNeeded(); break; } case "circles#update": { + invalidateMapIfNeeded(); List circlesToAdd = call.argument("circlesToAdd"); circlesController.addCircles(circlesToAdd); List circlesToChange = call.argument("circlesToChange"); @@ -364,7 +365,6 @@ public void onSnapshotReady(Bitmap bitmap) { List circleIdsToRemove = call.argument("circleIdsToRemove"); circlesController.removeCircles(circleIdsToRemove); result.success(null); - invalidateMapIfNeeded(); break; } case "map#isCompassEnabled": @@ -437,6 +437,7 @@ public void onSnapshotReady(Bitmap bitmap) { } case "map#setStyle": { + invalidateMapIfNeeded(); boolean mapStyleSet; if (call.arguments instanceof String) { String mapStyle = (String) call.arguments; @@ -455,11 +456,11 @@ public void onSnapshotReady(Bitmap bitmap) { "Unable to set the map style. Please check console logs for errors."); } result.success(mapStyleResult); - invalidateMapIfNeeded(); break; } case "tileOverlays#update": { + invalidateMapIfNeeded(); List> tileOverlaysToAdd = call.argument("tileOverlaysToAdd"); tileOverlaysController.addTileOverlays(tileOverlaysToAdd); List> tileOverlaysToChange = call.argument("tileOverlaysToChange"); @@ -467,14 +468,13 @@ public void onSnapshotReady(Bitmap bitmap) { List tileOverlaysToRemove = call.argument("tileOverlayIdsToRemove"); tileOverlaysController.removeTileOverlays(tileOverlaysToRemove); result.success(null); - invalidateMapIfNeeded(); break; } case "tileOverlays#clearTileCache": { + invalidateMapIfNeeded(); String tileOverlayId = call.argument("tileOverlayId"); tileOverlaysController.clearTileCache(tileOverlayId); - invalidateMapIfNeeded(); result.success(null); break; }