From c97767993c3a1e8c9d15c95dfa3246ce6507df63 Mon Sep 17 00:00:00 2001 From: math1man Date: Tue, 27 Oct 2020 16:46:49 -0700 Subject: [PATCH 1/9] GoogleMapController is now uniformly driven by implementing DefaultLifecycleObserver. That observer is registered to a lifecycle passed via the new LifecycleProvider interface, which has 3 implementations: 1. For v2 plugin registration, GoogleMapsPlugin implements the interface and holds the lifecycle in a field, which is itself controlled by ActivityAware methods. 2. For v1 plugin registration, if the activity implements LifecycleOwner, it's lifecycle is used directly. 3. For v1 plugin registration, if the activity does not implement LifecycleOwner, a proxy lifecycle is created via ActivityLifecycleCallbacks. I'm suspicious that there may still be a bug where a GoogleMapController instance can be shared across multiple activities and therefore leak the activity and lifecycle. --- .../plugins/googlemaps/GoogleMapBuilder.java | 15 +- .../googlemaps/GoogleMapController.java | 526 +++++++----------- .../plugins/googlemaps/GoogleMapFactory.java | 26 +- .../plugins/googlemaps/GoogleMapsPlugin.java | 207 ++++--- .../plugins/googlemaps/LifecycleProvider.java | 14 + 5 files changed, 325 insertions(+), 463 deletions(-) create mode 100644 packages/google_maps_flutter/google_maps_flutter/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/GoogleMapBuilder.java b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapBuilder.java index ca47be87f049..93a3c3ec9c28 100644 --- 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/src/main/java/io/flutter/plugins/googlemaps/GoogleMapBuilder.java @@ -4,17 +4,12 @@ package io.flutter.plugins.googlemaps; -import android.app.Application; import android.content.Context; import android.graphics.Rect; -import androidx.annotation.Nullable; -import androidx.lifecycle.Lifecycle; -import androidx.lifecycle.Lifecycle.State; import com.google.android.gms.maps.GoogleMapOptions; import com.google.android.gms.maps.model.CameraPosition; import com.google.android.gms.maps.model.LatLngBounds; import io.flutter.plugin.common.BinaryMessenger; -import io.flutter.plugin.common.PluginRegistry; class GoogleMapBuilder implements GoogleMapOptionsSink { private final GoogleMapOptions options = new GoogleMapOptions(); @@ -33,15 +28,11 @@ class GoogleMapBuilder implements GoogleMapOptionsSink { GoogleMapController build( int id, Context context, - State lifecycleState, BinaryMessenger binaryMessenger, - @Nullable Application application, - @Nullable Lifecycle lifecycle, - @Nullable PluginRegistry.Registrar registrar) { + LifecycleProvider lifecycleProvider) { final GoogleMapController controller = - new GoogleMapController( - id, context, binaryMessenger, application, lifecycle, registrar, options); - controller.init(lifecycleState); + new GoogleMapController(id, context, binaryMessenger, lifecycleProvider, options); + controller.init(); controller.setMyLocationEnabled(myLocationEnabled); controller.setMyLocationButtonEnabled(myLocationButtonEnabled); controller.setIndoorEnabled(indoorEnabled); 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 ea3322d4f3e8..ed624c1d998c 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 @@ -6,8 +6,6 @@ import android.Manifest; import android.annotation.SuppressLint; -import android.app.Activity; -import android.app.Application; import android.content.Context; import android.content.pm.PackageManager; import android.graphics.Bitmap; @@ -19,7 +17,6 @@ import androidx.annotation.Nullable; import androidx.lifecycle.DefaultLifecycleObserver; import androidx.lifecycle.Lifecycle; -import androidx.lifecycle.Lifecycle.State; import androidx.lifecycle.LifecycleOwner; import com.google.android.gms.maps.CameraUpdate; import com.google.android.gms.maps.GoogleMap; @@ -39,7 +36,6 @@ import io.flutter.plugin.common.BinaryMessenger; import io.flutter.plugin.common.MethodCall; import io.flutter.plugin.common.MethodChannel; -import io.flutter.plugin.common.PluginRegistry; import io.flutter.plugin.platform.PlatformView; import java.io.ByteArrayOutputStream; import java.util.ArrayList; @@ -50,8 +46,7 @@ /** Controller of a single GoogleMaps MapView instance. */ final class GoogleMapController - implements Application.ActivityLifecycleCallbacks, - DefaultLifecycleObserver, + implements DefaultLifecycleObserver, ActivityPluginBinding.OnSaveInstanceStateListener, GoogleMapOptionsSink, MethodChannel.MethodCallHandler, @@ -75,12 +70,8 @@ final class GoogleMapController private boolean disposed = false; private final float density; private MethodChannel.Result mapReadyResult; - @Nullable private final Lifecycle lifecycle; private final Context context; - // Do not use directly, use getApplication() instead to get correct application object for both v1 - // and v2 embedding. - @Nullable private final Application mApplication; - @Nullable private final PluginRegistry.Registrar registrar; // For v1 embedding only. + private final LifecycleProvider lifecycleProvider; private final MarkersController markersController; private final PolygonsController polygonsController; private final PolylinesController polylinesController; @@ -94,9 +85,7 @@ final class GoogleMapController int id, Context context, BinaryMessenger binaryMessenger, - @Nullable Application application, - @Nullable Lifecycle lifecycle, - @Nullable PluginRegistry.Registrar registrar, + LifecycleProvider lifecycleProvider, GoogleMapOptions options) { this.id = id; this.context = context; @@ -105,9 +94,7 @@ final class GoogleMapController this.density = context.getResources().getDisplayMetrics().density; methodChannel = new MethodChannel(binaryMessenger, "plugins.flutter.io/google_maps_" + id); methodChannel.setMethodCallHandler(this); - mApplication = application; - this.lifecycle = lifecycle; - this.registrar = registrar; + this.lifecycleProvider = lifecycleProvider; this.markersController = new MarkersController(methodChannel); this.polygonsController = new PolygonsController(methodChannel, density); this.polylinesController = new PolylinesController(methodChannel, density); @@ -119,30 +106,8 @@ public View getView() { return mapView; } - void init(State lifecycleState) { - switch (lifecycleState) { - case RESUMED: - mapView.onCreate(null); - mapView.onStart(); - mapView.onResume(); - break; - case STARTED: - mapView.onCreate(null); - mapView.onStart(); - break; - case CREATED: - mapView.onCreate(null); - break; - case DESTROYED: - case INITIALIZED: - // Nothing to do, the activity has been completely destroyed or not yet created. - break; - } - if (lifecycle != null) { - lifecycle.addObserver(this); - } else { - getApplication().registerActivityLifecycleCallbacks(this); - } + void init() { + lifecycleProvider.getLifecycle().addObserver(this); mapView.getMapAsync(this); } @@ -192,234 +157,234 @@ public void onMethodCall(MethodCall call, MethodChannel.Result result) { mapReadyResult = result; break; case "map#update": - { - Convert.interpretGoogleMapOptions(call.argument("options"), this); - result.success(Convert.cameraPositionToJson(getCameraPosition())); - break; - } + { + Convert.interpretGoogleMapOptions(call.argument("options"), this); + result.success(Convert.cameraPositionToJson(getCameraPosition())); + break; + } case "map#getVisibleRegion": - { - if (googleMap != null) { - LatLngBounds latLngBounds = googleMap.getProjection().getVisibleRegion().latLngBounds; - result.success(Convert.latlngBoundsToJson(latLngBounds)); - } else { - result.error( - "GoogleMap uninitialized", - "getVisibleRegion called prior to map initialization", - null); - } - break; + { + if (googleMap != null) { + LatLngBounds latLngBounds = googleMap.getProjection().getVisibleRegion().latLngBounds; + result.success(Convert.latlngBoundsToJson(latLngBounds)); + } else { + result.error( + "GoogleMap uninitialized", + "getVisibleRegion called prior to map initialization", + null); } + break; + } case "map#getScreenCoordinate": - { - if (googleMap != null) { - LatLng latLng = Convert.toLatLng(call.arguments); - Point screenLocation = googleMap.getProjection().toScreenLocation(latLng); - result.success(Convert.pointToJson(screenLocation)); - } else { - result.error( - "GoogleMap uninitialized", - "getScreenCoordinate called prior to map initialization", - null); - } - break; + { + if (googleMap != null) { + LatLng latLng = Convert.toLatLng(call.arguments); + Point screenLocation = googleMap.getProjection().toScreenLocation(latLng); + result.success(Convert.pointToJson(screenLocation)); + } else { + result.error( + "GoogleMap uninitialized", + "getScreenCoordinate called prior to map initialization", + null); } + break; + } case "map#getLatLng": - { - if (googleMap != null) { - Point point = Convert.toPoint(call.arguments); - LatLng latLng = googleMap.getProjection().fromScreenLocation(point); - result.success(Convert.latLngToJson(latLng)); - } else { - result.error( - "GoogleMap uninitialized", "getLatLng called prior to map initialization", null); - } - break; + { + if (googleMap != null) { + Point point = Convert.toPoint(call.arguments); + LatLng latLng = googleMap.getProjection().fromScreenLocation(point); + result.success(Convert.latLngToJson(latLng)); + } else { + result.error( + "GoogleMap uninitialized", "getLatLng called prior to map initialization", null); } + break; + } case "map#takeSnapshot": - { - if (googleMap != null) { - final MethodChannel.Result _result = result; - googleMap.snapshot( - new SnapshotReadyCallback() { - @Override - public void onSnapshotReady(Bitmap bitmap) { - ByteArrayOutputStream stream = new ByteArrayOutputStream(); - bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream); - byte[] byteArray = stream.toByteArray(); - bitmap.recycle(); - _result.success(byteArray); - } - }); - } else { - result.error("GoogleMap uninitialized", "takeSnapshot", null); - } - break; + { + if (googleMap != null) { + final MethodChannel.Result _result = result; + googleMap.snapshot( + new SnapshotReadyCallback() { + @Override + public void onSnapshotReady(Bitmap bitmap) { + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream); + byte[] byteArray = stream.toByteArray(); + bitmap.recycle(); + _result.success(byteArray); + } + }); + } else { + result.error("GoogleMap uninitialized", "takeSnapshot", null); } + break; + } case "camera#move": - { - final CameraUpdate cameraUpdate = - Convert.toCameraUpdate(call.argument("cameraUpdate"), density); - moveCamera(cameraUpdate); - result.success(null); - break; - } + { + final CameraUpdate cameraUpdate = + Convert.toCameraUpdate(call.argument("cameraUpdate"), density); + moveCamera(cameraUpdate); + result.success(null); + break; + } case "camera#animate": - { - final CameraUpdate cameraUpdate = - Convert.toCameraUpdate(call.argument("cameraUpdate"), density); - animateCamera(cameraUpdate); - result.success(null); - break; - } + { + final CameraUpdate cameraUpdate = + Convert.toCameraUpdate(call.argument("cameraUpdate"), density); + animateCamera(cameraUpdate); + result.success(null); + break; + } case "markers#update": - { - Object markersToAdd = call.argument("markersToAdd"); - markersController.addMarkers((List) markersToAdd); - Object markersToChange = call.argument("markersToChange"); - markersController.changeMarkers((List) markersToChange); - Object markerIdsToRemove = call.argument("markerIdsToRemove"); - markersController.removeMarkers((List) markerIdsToRemove); - result.success(null); - break; - } + { + Object markersToAdd = call.argument("markersToAdd"); + markersController.addMarkers((List) markersToAdd); + Object markersToChange = call.argument("markersToChange"); + markersController.changeMarkers((List) markersToChange); + Object markerIdsToRemove = call.argument("markerIdsToRemove"); + markersController.removeMarkers((List) markerIdsToRemove); + result.success(null); + break; + } case "markers#showInfoWindow": - { - Object markerId = call.argument("markerId"); - markersController.showMarkerInfoWindow((String) markerId, result); - break; - } + { + Object markerId = call.argument("markerId"); + markersController.showMarkerInfoWindow((String) markerId, result); + break; + } case "markers#hideInfoWindow": - { - Object markerId = call.argument("markerId"); - markersController.hideMarkerInfoWindow((String) markerId, result); - break; - } + { + Object markerId = call.argument("markerId"); + markersController.hideMarkerInfoWindow((String) markerId, result); + break; + } case "markers#isInfoWindowShown": - { - Object markerId = call.argument("markerId"); - markersController.isInfoWindowShown((String) markerId, result); - break; - } + { + Object markerId = call.argument("markerId"); + markersController.isInfoWindowShown((String) markerId, result); + break; + } case "polygons#update": - { - Object polygonsToAdd = call.argument("polygonsToAdd"); - polygonsController.addPolygons((List) polygonsToAdd); - Object polygonsToChange = call.argument("polygonsToChange"); - polygonsController.changePolygons((List) polygonsToChange); - Object polygonIdsToRemove = call.argument("polygonIdsToRemove"); - polygonsController.removePolygons((List) polygonIdsToRemove); - result.success(null); - break; - } + { + Object polygonsToAdd = call.argument("polygonsToAdd"); + polygonsController.addPolygons((List) polygonsToAdd); + Object polygonsToChange = call.argument("polygonsToChange"); + polygonsController.changePolygons((List) polygonsToChange); + Object polygonIdsToRemove = call.argument("polygonIdsToRemove"); + polygonsController.removePolygons((List) polygonIdsToRemove); + result.success(null); + break; + } case "polylines#update": - { - Object polylinesToAdd = call.argument("polylinesToAdd"); - polylinesController.addPolylines((List) polylinesToAdd); - Object polylinesToChange = call.argument("polylinesToChange"); - polylinesController.changePolylines((List) polylinesToChange); - Object polylineIdsToRemove = call.argument("polylineIdsToRemove"); - polylinesController.removePolylines((List) polylineIdsToRemove); - result.success(null); - break; - } + { + Object polylinesToAdd = call.argument("polylinesToAdd"); + polylinesController.addPolylines((List) polylinesToAdd); + Object polylinesToChange = call.argument("polylinesToChange"); + polylinesController.changePolylines((List) polylinesToChange); + Object polylineIdsToRemove = call.argument("polylineIdsToRemove"); + polylinesController.removePolylines((List) polylineIdsToRemove); + result.success(null); + break; + } case "circles#update": - { - Object circlesToAdd = call.argument("circlesToAdd"); - circlesController.addCircles((List) circlesToAdd); - Object circlesToChange = call.argument("circlesToChange"); - circlesController.changeCircles((List) circlesToChange); - Object circleIdsToRemove = call.argument("circleIdsToRemove"); - circlesController.removeCircles((List) circleIdsToRemove); - result.success(null); - break; - } + { + Object circlesToAdd = call.argument("circlesToAdd"); + circlesController.addCircles((List) circlesToAdd); + Object circlesToChange = call.argument("circlesToChange"); + circlesController.changeCircles((List) circlesToChange); + Object circleIdsToRemove = call.argument("circleIdsToRemove"); + circlesController.removeCircles((List) circleIdsToRemove); + result.success(null); + break; + } case "map#isCompassEnabled": - { - result.success(googleMap.getUiSettings().isCompassEnabled()); - break; - } + { + result.success(googleMap.getUiSettings().isCompassEnabled()); + break; + } case "map#isMapToolbarEnabled": - { - result.success(googleMap.getUiSettings().isMapToolbarEnabled()); - break; - } + { + result.success(googleMap.getUiSettings().isMapToolbarEnabled()); + break; + } case "map#getMinMaxZoomLevels": - { - List zoomLevels = new ArrayList<>(2); - zoomLevels.add(googleMap.getMinZoomLevel()); - zoomLevels.add(googleMap.getMaxZoomLevel()); - result.success(zoomLevels); - break; - } + { + List zoomLevels = new ArrayList<>(2); + zoomLevels.add(googleMap.getMinZoomLevel()); + zoomLevels.add(googleMap.getMaxZoomLevel()); + result.success(zoomLevels); + break; + } case "map#isZoomGesturesEnabled": - { - result.success(googleMap.getUiSettings().isZoomGesturesEnabled()); - break; - } + { + result.success(googleMap.getUiSettings().isZoomGesturesEnabled()); + break; + } case "map#isLiteModeEnabled": - { - result.success(options.getLiteMode()); - break; - } + { + result.success(options.getLiteMode()); + break; + } case "map#isZoomControlsEnabled": - { - result.success(googleMap.getUiSettings().isZoomControlsEnabled()); - break; - } + { + result.success(googleMap.getUiSettings().isZoomControlsEnabled()); + break; + } case "map#isScrollGesturesEnabled": - { - result.success(googleMap.getUiSettings().isScrollGesturesEnabled()); - break; - } + { + result.success(googleMap.getUiSettings().isScrollGesturesEnabled()); + break; + } case "map#isTiltGesturesEnabled": - { - result.success(googleMap.getUiSettings().isTiltGesturesEnabled()); - break; - } + { + result.success(googleMap.getUiSettings().isTiltGesturesEnabled()); + break; + } case "map#isRotateGesturesEnabled": - { - result.success(googleMap.getUiSettings().isRotateGesturesEnabled()); - break; - } + { + result.success(googleMap.getUiSettings().isRotateGesturesEnabled()); + break; + } case "map#isMyLocationButtonEnabled": - { - result.success(googleMap.getUiSettings().isMyLocationButtonEnabled()); - break; - } + { + result.success(googleMap.getUiSettings().isMyLocationButtonEnabled()); + break; + } case "map#isTrafficEnabled": - { - result.success(googleMap.isTrafficEnabled()); - break; - } + { + result.success(googleMap.isTrafficEnabled()); + break; + } case "map#isBuildingsEnabled": - { - result.success(googleMap.isBuildingsEnabled()); - break; - } + { + result.success(googleMap.isBuildingsEnabled()); + break; + } case "map#getZoomLevel": - { - result.success(googleMap.getCameraPosition().zoom); - break; - } + { + result.success(googleMap.getCameraPosition().zoom); + break; + } case "map#setStyle": - { - String mapStyle = (String) call.arguments; - boolean mapStyleSet; - if (mapStyle == null) { - mapStyleSet = googleMap.setMapStyle(null); - } else { - mapStyleSet = googleMap.setMapStyle(new MapStyleOptions(mapStyle)); - } - ArrayList mapStyleResult = new ArrayList<>(2); - mapStyleResult.add(mapStyleSet); - if (!mapStyleSet) { - mapStyleResult.add( - "Unable to set the map style. Please check console logs for errors."); - } - result.success(mapStyleResult); - break; + { + String mapStyle = (String) call.arguments; + boolean mapStyleSet; + if (mapStyle == null) { + mapStyleSet = googleMap.setMapStyle(null); + } else { + mapStyleSet = googleMap.setMapStyle(new MapStyleOptions(mapStyle)); } + ArrayList mapStyleResult = new ArrayList<>(2); + mapStyleResult.add(mapStyleSet); + if (!mapStyleSet) { + mapStyleResult.add( + "Unable to set the map style. Please check console logs for errors."); + } + result.success(mapStyleResult); + break; + } default: result.notImplemented(); } @@ -507,7 +472,10 @@ public void dispose() { methodChannel.setMethodCallHandler(null); setGoogleMapListener(null); destroyMapViewIfNecessary(); - getApplication().unregisterActivityLifecycleCallbacks(this); + Lifecycle lifecycle = lifecycleProvider.getLifecycle(); + if (lifecycle != null) { + lifecycle.removeObserver(this); + } } private void setGoogleMapListener(@Nullable GoogleMapListener listener) { @@ -537,64 +505,7 @@ public void onInputConnectionUnlocked() { // TODO(mklim): Remove this empty override once https://github.com/flutter/flutter/issues/40126 is fixed in stable. } - // Application.ActivityLifecycleCallbacks methods - @Override - public void onActivityCreated(Activity activity, Bundle savedInstanceState) { - if (disposed || activity.hashCode() != getActivityHashCode()) { - return; - } - mapView.onCreate(savedInstanceState); - } - - @Override - public void onActivityStarted(Activity activity) { - if (disposed || activity.hashCode() != getActivityHashCode()) { - return; - } - mapView.onStart(); - } - - @Override - public void onActivityResumed(Activity activity) { - if (disposed || activity.hashCode() != getActivityHashCode()) { - return; - } - mapView.onResume(); - } - - @Override - public void onActivityPaused(Activity activity) { - if (disposed || activity.hashCode() != getActivityHashCode()) { - return; - } - mapView.onPause(); - } - - @Override - public void onActivityStopped(Activity activity) { - if (disposed || activity.hashCode() != getActivityHashCode()) { - return; - } - mapView.onStop(); - } - - @Override - public void onActivitySaveInstanceState(Activity activity, Bundle outState) { - if (disposed || activity.hashCode() != getActivityHashCode()) { - return; - } - mapView.onSaveInstanceState(outState); - } - - @Override - public void onActivityDestroyed(Activity activity) { - if (disposed || activity.hashCode() != getActivityHashCode()) { - return; - } - destroyMapViewIfNecessary(); - } - - // DefaultLifecycleObserver and OnSaveInstanceStateListener + // DefaultLifecycleObserver @Override public void onCreate(@NonNull LifecycleOwner owner) { @@ -638,6 +549,7 @@ public void onStop(@NonNull LifecycleOwner owner) { @Override public void onDestroy(@NonNull LifecycleOwner owner) { + owner.getLifecycle().removeObserver(this); if (disposed) { return; } @@ -835,9 +747,9 @@ private void updateMyLocationSettings() { private boolean hasLocationPermission() { return checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) - == PackageManager.PERMISSION_GRANTED + == PackageManager.PERMISSION_GRANTED || checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) - == PackageManager.PERMISSION_GRANTED; + == PackageManager.PERMISSION_GRANTED; } private int checkSelfPermission(String permission) { @@ -848,24 +760,6 @@ private int checkSelfPermission(String permission) { permission, android.os.Process.myPid(), android.os.Process.myUid()); } - private int getActivityHashCode() { - if (registrar != null && registrar.activity() != null) { - return registrar.activity().hashCode(); - } else { - // TODO(cyanglaz): Remove `getActivityHashCode()` and use a cached hashCode when creating the view for V1 embedding. - // https://github.com/flutter/flutter/issues/69128 - return -1; - } - } - - private Application getApplication() { - if (registrar != null && registrar.activity() != null) { - return registrar.activity().getApplication(); - } else { - return mApplication; - } - } - private void destroyMapViewIfNecessary() { if (mapView == null) { return; 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/src/main/java/io/flutter/plugins/googlemaps/GoogleMapFactory.java index 7d665d4e2377..e56adbbb987a 100644 --- 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/src/main/java/io/flutter/plugins/googlemaps/GoogleMapFactory.java @@ -4,40 +4,23 @@ package io.flutter.plugins.googlemaps; -import android.app.Application; import android.content.Context; -import androidx.annotation.Nullable; -import androidx.lifecycle.Lifecycle; -import androidx.lifecycle.Lifecycle.State; import com.google.android.gms.maps.model.CameraPosition; import io.flutter.plugin.common.BinaryMessenger; -import io.flutter.plugin.common.PluginRegistry; import io.flutter.plugin.common.StandardMessageCodec; import io.flutter.plugin.platform.PlatformView; import io.flutter.plugin.platform.PlatformViewFactory; import java.util.Map; -import java.util.concurrent.atomic.AtomicReference; public class GoogleMapFactory extends PlatformViewFactory { - private final AtomicReference lifecycleState; private final BinaryMessenger binaryMessenger; - @Nullable private final Application application; - @Nullable private final Lifecycle lifecycle; - @Nullable private final PluginRegistry.Registrar registrar; // V1 embedding only. + private final LifecycleProvider lifecycleProvider; - GoogleMapFactory( - AtomicReference lifecycleState, - BinaryMessenger binaryMessenger, - @Nullable Application application, - @Nullable Lifecycle lifecycle, - @Nullable PluginRegistry.Registrar registrar) { + GoogleMapFactory(BinaryMessenger binaryMessenger, LifecycleProvider lifecycleProvider) { super(StandardMessageCodec.INSTANCE); - this.lifecycleState = lifecycleState; this.binaryMessenger = binaryMessenger; - this.application = application; - this.lifecycle = lifecycle; - this.registrar = registrar; + this.lifecycleProvider = lifecycleProvider; } @SuppressWarnings("unchecked") @@ -63,7 +46,6 @@ public PlatformView create(Context context, int id, Object args) { if (params.containsKey("circlesToAdd")) { builder.setInitialCircles(params.get("circlesToAdd")); } - return builder.build( - id, context, lifecycleState.get(), binaryMessenger, application, lifecycle, registrar); + return builder.build(id, context, binaryMessenger, lifecycleProvider); } } 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/src/main/java/io/flutter/plugins/googlemaps/GoogleMapsPlugin.java index 811ea97e4b80..01ca450a66cb 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/src/main/java/io/flutter/plugins/googlemaps/GoogleMapsPlugin.java @@ -6,23 +6,21 @@ import static androidx.lifecycle.Lifecycle.State.CREATED; import static androidx.lifecycle.Lifecycle.State.DESTROYED; -import static androidx.lifecycle.Lifecycle.State.INITIALIZED; import static androidx.lifecycle.Lifecycle.State.RESUMED; import static androidx.lifecycle.Lifecycle.State.STARTED; import android.app.Activity; -import android.app.Application; +import android.app.Application.ActivityLifecycleCallbacks; import android.os.Bundle; import androidx.annotation.NonNull; -import androidx.lifecycle.DefaultLifecycleObserver; +import androidx.annotation.Nullable; import androidx.lifecycle.Lifecycle; -import androidx.lifecycle.Lifecycle.State; import androidx.lifecycle.LifecycleOwner; +import androidx.lifecycle.LifecycleRegistry; import io.flutter.embedding.engine.plugins.FlutterPlugin; import io.flutter.embedding.engine.plugins.activity.ActivityAware; import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; import io.flutter.embedding.engine.plugins.lifecycle.FlutterLifecycleAdapter; -import java.util.concurrent.atomic.AtomicReference; /** * Plugin for controlling a set of GoogleMap views to be shown as overlays on top of the Flutter @@ -30,170 +28,153 @@ * the map. A Texture drawn using GoogleMap bitmap snapshots can then be shown instead of the * overlay. */ -public class GoogleMapsPlugin - implements Application.ActivityLifecycleCallbacks, - FlutterPlugin, - ActivityAware, - DefaultLifecycleObserver { - private final AtomicReference state = new AtomicReference<>(INITIALIZED); - private int registrarActivityHashCode; - private FlutterPluginBinding pluginBinding; - private Lifecycle lifecycle; +public class GoogleMapsPlugin implements FlutterPlugin, ActivityAware, LifecycleProvider { + @Nullable private Lifecycle lifecycle; private static final String VIEW_TYPE = "plugins.flutter.io/google_maps"; @SuppressWarnings("deprecation") - public static void registerWith(io.flutter.plugin.common.PluginRegistry.Registrar registrar) { - if (registrar.activity() == null) { + public static void registerWith( + final io.flutter.plugin.common.PluginRegistry.Registrar registrar) { + final Activity activity = registrar.activity(); + if (activity == null) { // When a background flutter view tries to register the plugin, the registrar has no activity. // We stop the registration process as this plugin is foreground only. return; } - final GoogleMapsPlugin plugin = new GoogleMapsPlugin(registrar.activity()); - registrar.activity().getApplication().registerActivityLifecycleCallbacks(plugin); - registrar - .platformViewRegistry() - .registerViewFactory( - VIEW_TYPE, - new GoogleMapFactory(plugin.state, registrar.messenger(), null, null, registrar)); + if (activity instanceof LifecycleOwner) { + registrar + .platformViewRegistry() + .registerViewFactory( + VIEW_TYPE, + new GoogleMapFactory( + registrar.messenger(), + new LifecycleProvider() { + @Override + public Lifecycle getLifecycle() { + return ((LifecycleOwner) activity).getLifecycle(); + } + } + )); + } else { + ProxyLifecycleProvider proxyLifecycleProvider = new ProxyLifecycleProvider(activity); + activity.getApplication().registerActivityLifecycleCallbacks(proxyLifecycleProvider); + registrar + .platformViewRegistry() + .registerViewFactory( + VIEW_TYPE, new GoogleMapFactory(registrar.messenger(), proxyLifecycleProvider)); + } } public GoogleMapsPlugin() {} - private GoogleMapsPlugin(Activity activity) { - this.registrarActivityHashCode = activity.hashCode(); - } - // FlutterPlugin @Override public void onAttachedToEngine(FlutterPluginBinding binding) { - pluginBinding = binding; + binding + .getPlatformViewRegistry() + .registerViewFactory( + VIEW_TYPE, new GoogleMapFactory(binding.getBinaryMessenger(), this)); } @Override - public void onDetachedFromEngine(FlutterPluginBinding binding) { - pluginBinding = null; - } + public void onDetachedFromEngine(FlutterPluginBinding binding) {} // ActivityAware @Override public void onAttachedToActivity(ActivityPluginBinding binding) { lifecycle = FlutterLifecycleAdapter.getActivityLifecycle(binding); - lifecycle.addObserver(this); - pluginBinding - .getPlatformViewRegistry() - .registerViewFactory( - VIEW_TYPE, - new GoogleMapFactory( - state, - pluginBinding.getBinaryMessenger(), - binding.getActivity().getApplication(), - lifecycle, - null)); } @Override public void onDetachedFromActivity() { - lifecycle.removeObserver(this); lifecycle = null; } - @Override - public void onDetachedFromActivityForConfigChanges() { - this.onDetachedFromActivity(); - } - @Override public void onReattachedToActivityForConfigChanges(ActivityPluginBinding binding) { - lifecycle = FlutterLifecycleAdapter.getActivityLifecycle(binding); - lifecycle.addObserver(this); - } - - // DefaultLifecycleObserver methods - - @Override - public void onCreate(@NonNull LifecycleOwner owner) { - state.set(CREATED); + onAttachedToActivity(binding); } @Override - public void onStart(@NonNull LifecycleOwner owner) { - state.set(STARTED); + public void onDetachedFromActivityForConfigChanges() { + onDetachedFromActivity(); } + @Nullable @Override - public void onResume(@NonNull LifecycleOwner owner) { - state.set(RESUMED); + public Lifecycle getLifecycle() { + return lifecycle; } - @Override - public void onPause(@NonNull LifecycleOwner owner) { - state.set(STARTED); - } + private static final class ProxyLifecycleProvider + implements ActivityLifecycleCallbacks, LifecycleOwner, LifecycleProvider { - @Override - public void onStop(@NonNull LifecycleOwner owner) { - state.set(CREATED); - } + private final LifecycleRegistry lifecycle = new LifecycleRegistry(this); + private final int registrarActivityHashCode; - @Override - public void onDestroy(@NonNull LifecycleOwner owner) { - state.set(DESTROYED); - } - - // Application.ActivityLifecycleCallbacks methods + private ProxyLifecycleProvider(Activity activity) { + this.registrarActivityHashCode = activity.hashCode(); + } - @Override - public void onActivityCreated(Activity activity, Bundle savedInstanceState) { - if (activity.hashCode() != registrarActivityHashCode) { - return; + @Override + public void onActivityCreated(Activity activity, Bundle savedInstanceState) { + if (activity.hashCode() != registrarActivityHashCode) { + return; + } + lifecycle.setCurrentState(CREATED); } - state.set(CREATED); - } - @Override - public void onActivityStarted(Activity activity) { - if (activity.hashCode() != registrarActivityHashCode) { - return; + @Override + public void onActivityStarted(Activity activity) { + if (activity.hashCode() != registrarActivityHashCode) { + return; + } + lifecycle.setCurrentState(STARTED); } - state.set(STARTED); - } - @Override - public void onActivityResumed(Activity activity) { - if (activity.hashCode() != registrarActivityHashCode) { - return; + @Override + public void onActivityResumed(Activity activity) { + if (activity.hashCode() != registrarActivityHashCode) { + return; + } + lifecycle.setCurrentState(RESUMED); } - state.set(RESUMED); - } - @Override - public void onActivityPaused(Activity activity) { - if (activity.hashCode() != registrarActivityHashCode) { - return; + @Override + public void onActivityPaused(Activity activity) { + if (activity.hashCode() != registrarActivityHashCode) { + return; + } + lifecycle.setCurrentState(STARTED); } - state.set(STARTED); - } - @Override - public void onActivityStopped(Activity activity) { - if (activity.hashCode() != registrarActivityHashCode) { - return; + @Override + public void onActivityStopped(Activity activity) { + if (activity.hashCode() != registrarActivityHashCode) { + return; + } + lifecycle.setCurrentState(CREATED); } - state.set(CREATED); - } - @Override - public void onActivitySaveInstanceState(Activity activity, Bundle outState) {} + @Override + public void onActivitySaveInstanceState(Activity activity, Bundle outState) {} - @Override - public void onActivityDestroyed(Activity activity) { - if (activity.hashCode() != registrarActivityHashCode) { - return; + @Override + public void onActivityDestroyed(Activity activity) { + if (activity.hashCode() != registrarActivityHashCode) { + return; + } + activity.getApplication().unregisterActivityLifecycleCallbacks(this); + lifecycle.setCurrentState(DESTROYED); + } + + @NonNull + @Override + public Lifecycle getLifecycle() { + return lifecycle; } - activity.getApplication().unregisterActivityLifecycleCallbacks(this); - state.set(DESTROYED); } } 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/src/main/java/io/flutter/plugins/googlemaps/LifecycleProvider.java new file mode 100644 index 000000000000..154de200fd86 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/LifecycleProvider.java @@ -0,0 +1,14 @@ +// Copyright 2019 The Chromium 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.annotation.Nullable; +import androidx.lifecycle.Lifecycle; + +interface LifecycleProvider { + + @Nullable + Lifecycle getLifecycle(); +} From e934a18b284d44bd3fec6098c3ac1dcbc91c7a3a Mon Sep 17 00:00:00 2001 From: math1man Date: Tue, 27 Oct 2020 16:50:00 -0700 Subject: [PATCH 2/9] Update CHANGELOG.md and version --- .../google_maps_flutter/CHANGELOG.md | 10 ++++++++++ .../google_maps_flutter/pubspec.yaml | 2 +- 2 files changed, 11 insertions(+), 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 afe8638e0fe1..4c0461b7f94f 100644 --- a/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md +++ b/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md @@ -1,3 +1,13 @@ +## 1.1.0 + +Overhaul lifecycle management in GoogleMapsPlugin. + +GoogleMapController is now uniformly driven by implementing `DefaultLifecycleObserver`. That observer is registered to a lifecycle from one of three sources: + +1. For v2 plugin registration, `GoogleMapsPlugin` obtains the lifecycle via `ActivityAware` methods. +2. For v1 plugin registration, if the activity implements `LifecycleOwner`, it's lifecycle is used directly. +3. For v1 plugin registration, if the activity does not implement `LifecycleOwner`, a proxy lifecycle is created and driven via `ActivityLifecycleCallbacks`. + ## 1.0.4 * Cleanup of Android code: diff --git a/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml index 1c6ba4a94871..1ed28725f10d 100644 --- a/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml @@ -1,7 +1,7 @@ name: google_maps_flutter description: A Flutter plugin for integrating Google Maps in iOS and Android applications. homepage: https://github.com/flutter/plugins/tree/master/packages/google_maps_flutter/google_maps_flutter -version: 1.0.4 +version: 1.1.0 dependencies: flutter: From 96cf26b25a79e2de851ff38a78710e40447385b8 Mon Sep 17 00:00:00 2001 From: math1man Date: Wed, 28 Oct 2020 09:50:20 -0700 Subject: [PATCH 3/9] Fix formatting issues --- .../googlemaps/GoogleMapController.java | 396 +++++++++--------- .../plugins/googlemaps/GoogleMapsPlugin.java | 6 +- 2 files changed, 200 insertions(+), 202 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 ed624c1d998c..33cacffc88d4 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 @@ -157,234 +157,234 @@ public void onMethodCall(MethodCall call, MethodChannel.Result result) { mapReadyResult = result; break; case "map#update": - { - Convert.interpretGoogleMapOptions(call.argument("options"), this); - result.success(Convert.cameraPositionToJson(getCameraPosition())); - break; - } + { + Convert.interpretGoogleMapOptions(call.argument("options"), this); + result.success(Convert.cameraPositionToJson(getCameraPosition())); + break; + } case "map#getVisibleRegion": - { - if (googleMap != null) { - LatLngBounds latLngBounds = googleMap.getProjection().getVisibleRegion().latLngBounds; - result.success(Convert.latlngBoundsToJson(latLngBounds)); - } else { - result.error( - "GoogleMap uninitialized", - "getVisibleRegion called prior to map initialization", - null); + { + if (googleMap != null) { + LatLngBounds latLngBounds = googleMap.getProjection().getVisibleRegion().latLngBounds; + result.success(Convert.latlngBoundsToJson(latLngBounds)); + } else { + result.error( + "GoogleMap uninitialized", + "getVisibleRegion called prior to map initialization", + null); + } + break; } - break; - } case "map#getScreenCoordinate": - { - if (googleMap != null) { - LatLng latLng = Convert.toLatLng(call.arguments); - Point screenLocation = googleMap.getProjection().toScreenLocation(latLng); - result.success(Convert.pointToJson(screenLocation)); - } else { - result.error( - "GoogleMap uninitialized", - "getScreenCoordinate called prior to map initialization", - null); + { + if (googleMap != null) { + LatLng latLng = Convert.toLatLng(call.arguments); + Point screenLocation = googleMap.getProjection().toScreenLocation(latLng); + result.success(Convert.pointToJson(screenLocation)); + } else { + result.error( + "GoogleMap uninitialized", + "getScreenCoordinate called prior to map initialization", + null); + } + break; } - break; - } case "map#getLatLng": - { - if (googleMap != null) { - Point point = Convert.toPoint(call.arguments); - LatLng latLng = googleMap.getProjection().fromScreenLocation(point); - result.success(Convert.latLngToJson(latLng)); - } else { - result.error( - "GoogleMap uninitialized", "getLatLng called prior to map initialization", null); + { + if (googleMap != null) { + Point point = Convert.toPoint(call.arguments); + LatLng latLng = googleMap.getProjection().fromScreenLocation(point); + result.success(Convert.latLngToJson(latLng)); + } else { + result.error( + "GoogleMap uninitialized", "getLatLng called prior to map initialization", null); + } + break; } - break; - } case "map#takeSnapshot": - { - if (googleMap != null) { - final MethodChannel.Result _result = result; - googleMap.snapshot( - new SnapshotReadyCallback() { - @Override - public void onSnapshotReady(Bitmap bitmap) { - ByteArrayOutputStream stream = new ByteArrayOutputStream(); - bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream); - byte[] byteArray = stream.toByteArray(); - bitmap.recycle(); - _result.success(byteArray); - } - }); - } else { - result.error("GoogleMap uninitialized", "takeSnapshot", null); + { + if (googleMap != null) { + final MethodChannel.Result _result = result; + googleMap.snapshot( + new SnapshotReadyCallback() { + @Override + public void onSnapshotReady(Bitmap bitmap) { + ByteArrayOutputStream stream = new ByteArrayOutputStream(); + bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream); + byte[] byteArray = stream.toByteArray(); + bitmap.recycle(); + _result.success(byteArray); + } + }); + } else { + result.error("GoogleMap uninitialized", "takeSnapshot", null); + } + break; } - break; - } case "camera#move": - { - final CameraUpdate cameraUpdate = - Convert.toCameraUpdate(call.argument("cameraUpdate"), density); - moveCamera(cameraUpdate); - result.success(null); - break; - } + { + final CameraUpdate cameraUpdate = + Convert.toCameraUpdate(call.argument("cameraUpdate"), density); + moveCamera(cameraUpdate); + result.success(null); + break; + } case "camera#animate": - { - final CameraUpdate cameraUpdate = - Convert.toCameraUpdate(call.argument("cameraUpdate"), density); - animateCamera(cameraUpdate); - result.success(null); - break; - } + { + final CameraUpdate cameraUpdate = + Convert.toCameraUpdate(call.argument("cameraUpdate"), density); + animateCamera(cameraUpdate); + result.success(null); + break; + } case "markers#update": - { - Object markersToAdd = call.argument("markersToAdd"); - markersController.addMarkers((List) markersToAdd); - Object markersToChange = call.argument("markersToChange"); - markersController.changeMarkers((List) markersToChange); - Object markerIdsToRemove = call.argument("markerIdsToRemove"); - markersController.removeMarkers((List) markerIdsToRemove); - result.success(null); - break; - } + { + Object markersToAdd = call.argument("markersToAdd"); + markersController.addMarkers((List) markersToAdd); + Object markersToChange = call.argument("markersToChange"); + markersController.changeMarkers((List) markersToChange); + Object markerIdsToRemove = call.argument("markerIdsToRemove"); + markersController.removeMarkers((List) markerIdsToRemove); + result.success(null); + break; + } case "markers#showInfoWindow": - { - Object markerId = call.argument("markerId"); - markersController.showMarkerInfoWindow((String) markerId, result); - break; - } + { + Object markerId = call.argument("markerId"); + markersController.showMarkerInfoWindow((String) markerId, result); + break; + } case "markers#hideInfoWindow": - { - Object markerId = call.argument("markerId"); - markersController.hideMarkerInfoWindow((String) markerId, result); - break; - } + { + Object markerId = call.argument("markerId"); + markersController.hideMarkerInfoWindow((String) markerId, result); + break; + } case "markers#isInfoWindowShown": - { - Object markerId = call.argument("markerId"); - markersController.isInfoWindowShown((String) markerId, result); - break; - } + { + Object markerId = call.argument("markerId"); + markersController.isInfoWindowShown((String) markerId, result); + break; + } case "polygons#update": - { - Object polygonsToAdd = call.argument("polygonsToAdd"); - polygonsController.addPolygons((List) polygonsToAdd); - Object polygonsToChange = call.argument("polygonsToChange"); - polygonsController.changePolygons((List) polygonsToChange); - Object polygonIdsToRemove = call.argument("polygonIdsToRemove"); - polygonsController.removePolygons((List) polygonIdsToRemove); - result.success(null); - break; - } + { + Object polygonsToAdd = call.argument("polygonsToAdd"); + polygonsController.addPolygons((List) polygonsToAdd); + Object polygonsToChange = call.argument("polygonsToChange"); + polygonsController.changePolygons((List) polygonsToChange); + Object polygonIdsToRemove = call.argument("polygonIdsToRemove"); + polygonsController.removePolygons((List) polygonIdsToRemove); + result.success(null); + break; + } case "polylines#update": - { - Object polylinesToAdd = call.argument("polylinesToAdd"); - polylinesController.addPolylines((List) polylinesToAdd); - Object polylinesToChange = call.argument("polylinesToChange"); - polylinesController.changePolylines((List) polylinesToChange); - Object polylineIdsToRemove = call.argument("polylineIdsToRemove"); - polylinesController.removePolylines((List) polylineIdsToRemove); - result.success(null); - break; - } + { + Object polylinesToAdd = call.argument("polylinesToAdd"); + polylinesController.addPolylines((List) polylinesToAdd); + Object polylinesToChange = call.argument("polylinesToChange"); + polylinesController.changePolylines((List) polylinesToChange); + Object polylineIdsToRemove = call.argument("polylineIdsToRemove"); + polylinesController.removePolylines((List) polylineIdsToRemove); + result.success(null); + break; + } case "circles#update": - { - Object circlesToAdd = call.argument("circlesToAdd"); - circlesController.addCircles((List) circlesToAdd); - Object circlesToChange = call.argument("circlesToChange"); - circlesController.changeCircles((List) circlesToChange); - Object circleIdsToRemove = call.argument("circleIdsToRemove"); - circlesController.removeCircles((List) circleIdsToRemove); - result.success(null); - break; - } + { + Object circlesToAdd = call.argument("circlesToAdd"); + circlesController.addCircles((List) circlesToAdd); + Object circlesToChange = call.argument("circlesToChange"); + circlesController.changeCircles((List) circlesToChange); + Object circleIdsToRemove = call.argument("circleIdsToRemove"); + circlesController.removeCircles((List) circleIdsToRemove); + result.success(null); + break; + } case "map#isCompassEnabled": - { - result.success(googleMap.getUiSettings().isCompassEnabled()); - break; - } + { + result.success(googleMap.getUiSettings().isCompassEnabled()); + break; + } case "map#isMapToolbarEnabled": - { - result.success(googleMap.getUiSettings().isMapToolbarEnabled()); - break; - } + { + result.success(googleMap.getUiSettings().isMapToolbarEnabled()); + break; + } case "map#getMinMaxZoomLevels": - { - List zoomLevels = new ArrayList<>(2); - zoomLevels.add(googleMap.getMinZoomLevel()); - zoomLevels.add(googleMap.getMaxZoomLevel()); - result.success(zoomLevels); - break; - } + { + List zoomLevels = new ArrayList<>(2); + zoomLevels.add(googleMap.getMinZoomLevel()); + zoomLevels.add(googleMap.getMaxZoomLevel()); + result.success(zoomLevels); + break; + } case "map#isZoomGesturesEnabled": - { - result.success(googleMap.getUiSettings().isZoomGesturesEnabled()); - break; - } + { + result.success(googleMap.getUiSettings().isZoomGesturesEnabled()); + break; + } case "map#isLiteModeEnabled": - { - result.success(options.getLiteMode()); - break; - } + { + result.success(options.getLiteMode()); + break; + } case "map#isZoomControlsEnabled": - { - result.success(googleMap.getUiSettings().isZoomControlsEnabled()); - break; - } + { + result.success(googleMap.getUiSettings().isZoomControlsEnabled()); + break; + } case "map#isScrollGesturesEnabled": - { - result.success(googleMap.getUiSettings().isScrollGesturesEnabled()); - break; - } + { + result.success(googleMap.getUiSettings().isScrollGesturesEnabled()); + break; + } case "map#isTiltGesturesEnabled": - { - result.success(googleMap.getUiSettings().isTiltGesturesEnabled()); - break; - } + { + result.success(googleMap.getUiSettings().isTiltGesturesEnabled()); + break; + } case "map#isRotateGesturesEnabled": - { - result.success(googleMap.getUiSettings().isRotateGesturesEnabled()); - break; - } + { + result.success(googleMap.getUiSettings().isRotateGesturesEnabled()); + break; + } case "map#isMyLocationButtonEnabled": - { - result.success(googleMap.getUiSettings().isMyLocationButtonEnabled()); - break; - } + { + result.success(googleMap.getUiSettings().isMyLocationButtonEnabled()); + break; + } case "map#isTrafficEnabled": - { - result.success(googleMap.isTrafficEnabled()); - break; - } + { + result.success(googleMap.isTrafficEnabled()); + break; + } case "map#isBuildingsEnabled": - { - result.success(googleMap.isBuildingsEnabled()); - break; - } + { + result.success(googleMap.isBuildingsEnabled()); + break; + } case "map#getZoomLevel": - { - result.success(googleMap.getCameraPosition().zoom); - break; - } - case "map#setStyle": - { - String mapStyle = (String) call.arguments; - boolean mapStyleSet; - if (mapStyle == null) { - mapStyleSet = googleMap.setMapStyle(null); - } else { - mapStyleSet = googleMap.setMapStyle(new MapStyleOptions(mapStyle)); + { + result.success(googleMap.getCameraPosition().zoom); + break; } - ArrayList mapStyleResult = new ArrayList<>(2); - mapStyleResult.add(mapStyleSet); - if (!mapStyleSet) { - mapStyleResult.add( - "Unable to set the map style. Please check console logs for errors."); + case "map#setStyle": + { + String mapStyle = (String) call.arguments; + boolean mapStyleSet; + if (mapStyle == null) { + mapStyleSet = googleMap.setMapStyle(null); + } else { + mapStyleSet = googleMap.setMapStyle(new MapStyleOptions(mapStyle)); + } + ArrayList mapStyleResult = new ArrayList<>(2); + mapStyleResult.add(mapStyleSet); + if (!mapStyleSet) { + mapStyleResult.add( + "Unable to set the map style. Please check console logs for errors."); + } + result.success(mapStyleResult); + break; } - result.success(mapStyleResult); - break; - } default: result.notImplemented(); } @@ -747,9 +747,9 @@ private void updateMyLocationSettings() { private boolean hasLocationPermission() { return checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) - == PackageManager.PERMISSION_GRANTED + == PackageManager.PERMISSION_GRANTED || checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) - == PackageManager.PERMISSION_GRANTED; + == PackageManager.PERMISSION_GRANTED; } private int checkSelfPermission(String permission) { 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/src/main/java/io/flutter/plugins/googlemaps/GoogleMapsPlugin.java index 01ca450a66cb..ae828fcf0a86 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/src/main/java/io/flutter/plugins/googlemaps/GoogleMapsPlugin.java @@ -54,8 +54,7 @@ public static void registerWith( public Lifecycle getLifecycle() { return ((LifecycleOwner) activity).getLifecycle(); } - } - )); + })); } else { ProxyLifecycleProvider proxyLifecycleProvider = new ProxyLifecycleProvider(activity); activity.getApplication().registerActivityLifecycleCallbacks(proxyLifecycleProvider); @@ -74,8 +73,7 @@ public GoogleMapsPlugin() {} public void onAttachedToEngine(FlutterPluginBinding binding) { binding .getPlatformViewRegistry() - .registerViewFactory( - VIEW_TYPE, new GoogleMapFactory(binding.getBinaryMessenger(), this)); + .registerViewFactory(VIEW_TYPE, new GoogleMapFactory(binding.getBinaryMessenger(), this)); } @Override From 62476dccd675511ff2aaedb0eedccc125f0851fc Mon Sep 17 00:00:00 2001 From: math1man Date: Wed, 28 Oct 2020 10:05:43 -0700 Subject: [PATCH 4/9] Update GoogleMapControllerTest --- .../googlemaps/GoogleMapControllerTest.java | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/packages/google_maps_flutter/google_maps_flutter/example/android/app/src/test/java/io/flutter/plugins/googlemaps/GoogleMapControllerTest.java b/packages/google_maps_flutter/google_maps_flutter/example/android/app/src/test/java/io/flutter/plugins/googlemaps/GoogleMapControllerTest.java index 09f0c78da74a..1b71d20f36b7 100644 --- a/packages/google_maps_flutter/google_maps_flutter/example/android/app/src/test/java/io/flutter/plugins/googlemaps/GoogleMapControllerTest.java +++ b/packages/google_maps_flutter/google_maps_flutter/example/android/app/src/test/java/io/flutter/plugins/googlemaps/GoogleMapControllerTest.java @@ -5,8 +5,7 @@ import android.app.Application; import android.content.Context; -import androidx.lifecycle.Lifecycle.State; -import androidx.lifecycle.LifecycleOwner; +import androidx.activity.ComponentActivity; import androidx.test.core.app.ApplicationProvider; import com.google.android.gms.maps.GoogleMap; import io.flutter.plugin.common.BinaryMessenger; @@ -15,27 +14,27 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; @RunWith(RobolectricTestRunner.class) public class GoogleMapControllerTest { private Context context; - private Application application; + private ComponentActivity activity; private GoogleMapController googleMapController; @Mock BinaryMessenger mockMessenger; @Mock GoogleMap mockGoogleMap; - @Mock LifecycleOwner lifecycleOwner; @Before public void before() { MockitoAnnotations.initMocks(this); context = ApplicationProvider.getApplicationContext(); - application = ApplicationProvider.getApplicationContext(); + activity = Robolectric.setupActivity(ComponentActivity.class); googleMapController = - new GoogleMapController(0, context, mockMessenger, application, null, null, null); - googleMapController.init(State.CREATED); + new GoogleMapController(0, context, mockMessenger, activity::getLifecycle, null); + googleMapController.init(); } @Test @@ -50,7 +49,7 @@ public void DisposeReleaseTheMap() throws InterruptedException { public void OnDestroyReleaseTheMap() throws InterruptedException { googleMapController.onMapReady(mockGoogleMap); assertTrue(googleMapController != null); - googleMapController.onDestroy(lifecycleOwner); + googleMapController.onDestroy(activity); assertNull(googleMapController.getView()); } } From b920973a76035ac2f44051457284d4b190aef945 Mon Sep 17 00:00:00 2001 From: math1man Date: Wed, 28 Oct 2020 10:25:15 -0700 Subject: [PATCH 5/9] Remove extraneous import --- .../io/flutter/plugins/googlemaps/GoogleMapControllerTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/google_maps_flutter/google_maps_flutter/example/android/app/src/test/java/io/flutter/plugins/googlemaps/GoogleMapControllerTest.java b/packages/google_maps_flutter/google_maps_flutter/example/android/app/src/test/java/io/flutter/plugins/googlemaps/GoogleMapControllerTest.java index 1b71d20f36b7..94b93473a5e1 100644 --- a/packages/google_maps_flutter/google_maps_flutter/example/android/app/src/test/java/io/flutter/plugins/googlemaps/GoogleMapControllerTest.java +++ b/packages/google_maps_flutter/google_maps_flutter/example/android/app/src/test/java/io/flutter/plugins/googlemaps/GoogleMapControllerTest.java @@ -3,7 +3,6 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -import android.app.Application; import android.content.Context; import androidx.activity.ComponentActivity; import androidx.test.core.app.ApplicationProvider; From ea016b880705ab3f2eaa94cf0f92f1a67cafed26 Mon Sep 17 00:00:00 2001 From: math1man Date: Wed, 28 Oct 2020 12:49:45 -0700 Subject: [PATCH 6/9] Update GoogleMapsPlugin.ProxyLifecycleProvider to use handleLifecycleEvent() rather than moveToState(). --- .../plugins/googlemaps/GoogleMapsPlugin.java | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) 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/src/main/java/io/flutter/plugins/googlemaps/GoogleMapsPlugin.java index ae828fcf0a86..8a37c9f00b6c 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/src/main/java/io/flutter/plugins/googlemaps/GoogleMapsPlugin.java @@ -4,17 +4,13 @@ package io.flutter.plugins.googlemaps; -import static androidx.lifecycle.Lifecycle.State.CREATED; -import static androidx.lifecycle.Lifecycle.State.DESTROYED; -import static androidx.lifecycle.Lifecycle.State.RESUMED; -import static androidx.lifecycle.Lifecycle.State.STARTED; - import android.app.Activity; import android.app.Application.ActivityLifecycleCallbacks; import android.os.Bundle; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.lifecycle.Lifecycle; +import androidx.lifecycle.Lifecycle.Event; import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.LifecycleRegistry; import io.flutter.embedding.engine.plugins.FlutterPlugin; @@ -29,6 +25,7 @@ * overlay. */ public class GoogleMapsPlugin implements FlutterPlugin, ActivityAware, LifecycleProvider { + @Nullable private Lifecycle lifecycle; private static final String VIEW_TYPE = "plugins.flutter.io/google_maps"; @@ -122,7 +119,7 @@ public void onActivityCreated(Activity activity, Bundle savedInstanceState) { if (activity.hashCode() != registrarActivityHashCode) { return; } - lifecycle.setCurrentState(CREATED); + lifecycle.handleLifecycleEvent(Event.ON_CREATE); } @Override @@ -130,7 +127,7 @@ public void onActivityStarted(Activity activity) { if (activity.hashCode() != registrarActivityHashCode) { return; } - lifecycle.setCurrentState(STARTED); + lifecycle.handleLifecycleEvent(Event.ON_START); } @Override @@ -138,7 +135,7 @@ public void onActivityResumed(Activity activity) { if (activity.hashCode() != registrarActivityHashCode) { return; } - lifecycle.setCurrentState(RESUMED); + lifecycle.handleLifecycleEvent(Event.ON_RESUME); } @Override @@ -146,7 +143,7 @@ public void onActivityPaused(Activity activity) { if (activity.hashCode() != registrarActivityHashCode) { return; } - lifecycle.setCurrentState(STARTED); + lifecycle.handleLifecycleEvent(Event.ON_PAUSE); } @Override @@ -154,7 +151,7 @@ public void onActivityStopped(Activity activity) { if (activity.hashCode() != registrarActivityHashCode) { return; } - lifecycle.setCurrentState(CREATED); + lifecycle.handleLifecycleEvent(Event.ON_STOP); } @Override @@ -166,7 +163,7 @@ public void onActivityDestroyed(Activity activity) { return; } activity.getApplication().unregisterActivityLifecycleCallbacks(this); - lifecycle.setCurrentState(DESTROYED); + lifecycle.handleLifecycleEvent(Event.ON_DESTROY); } @NonNull From 5f479f037136f88bb7f7dae688e16032615b295f Mon Sep 17 00:00:00 2001 From: math1man Date: Wed, 28 Oct 2020 14:08:39 -0700 Subject: [PATCH 7/9] Update version from 1.1.0 to 1.0.5 --- packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md | 2 +- packages/google_maps_flutter/google_maps_flutter/pubspec.yaml | 2 +- 2 files changed, 2 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 4c0461b7f94f..a2e0c1bc3df6 100644 --- a/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md +++ b/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md @@ -1,4 +1,4 @@ -## 1.1.0 +## 1.0.5 Overhaul lifecycle management in GoogleMapsPlugin. diff --git a/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml index 1ed28725f10d..dadedb8cce3b 100644 --- a/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml +++ b/packages/google_maps_flutter/google_maps_flutter/pubspec.yaml @@ -1,7 +1,7 @@ name: google_maps_flutter description: A Flutter plugin for integrating Google Maps in iOS and Android applications. homepage: https://github.com/flutter/plugins/tree/master/packages/google_maps_flutter/google_maps_flutter -version: 1.1.0 +version: 1.0.5 dependencies: flutter: From 039e3ce7f564d1f7f03b07ee87ff89d406ccf814 Mon Sep 17 00:00:00 2001 From: math1man Date: Wed, 28 Oct 2020 14:14:11 -0700 Subject: [PATCH 8/9] Add javadoc for ProxyLifecycleProvider and move initial ActivityLifecycleCallbacks registration inside its constructor. --- .../flutter/plugins/googlemaps/GoogleMapsPlugin.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) 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/src/main/java/io/flutter/plugins/googlemaps/GoogleMapsPlugin.java index 8a37c9f00b6c..01bcd36e92f7 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/src/main/java/io/flutter/plugins/googlemaps/GoogleMapsPlugin.java @@ -53,12 +53,11 @@ public Lifecycle getLifecycle() { } })); } else { - ProxyLifecycleProvider proxyLifecycleProvider = new ProxyLifecycleProvider(activity); - activity.getApplication().registerActivityLifecycleCallbacks(proxyLifecycleProvider); registrar .platformViewRegistry() .registerViewFactory( - VIEW_TYPE, new GoogleMapFactory(registrar.messenger(), proxyLifecycleProvider)); + VIEW_TYPE, + new GoogleMapFactory(registrar.messenger(), new ProxyLifecycleProvider(activity))); } } @@ -104,6 +103,12 @@ public Lifecycle getLifecycle() { return lifecycle; } + /** + * This class provides a {@link LifecycleOwner} for the activity driven by {@link + * ActivityLifecycleCallbacks}. + * + *

This is used in the case where a direct Lifecycle/Owner is not available. + */ private static final class ProxyLifecycleProvider implements ActivityLifecycleCallbacks, LifecycleOwner, LifecycleProvider { @@ -112,6 +117,7 @@ private static final class ProxyLifecycleProvider private ProxyLifecycleProvider(Activity activity) { this.registrarActivityHashCode = activity.hashCode(); + activity.getApplication().registerActivityLifecycleCallbacks(this); } @Override From df2345da8d2d55e74a87524c7bb36142b43f5ca6 Mon Sep 17 00:00:00 2001 From: math1man Date: Wed, 28 Oct 2020 14:16:31 -0700 Subject: [PATCH 9/9] Remove LifecycleProvider as an interface of GoogleMapsPlugin and instead create an anonymous inner class. --- .../plugins/googlemaps/GoogleMapsPlugin.java | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) 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/src/main/java/io/flutter/plugins/googlemaps/GoogleMapsPlugin.java index 01bcd36e92f7..69ba8cb222c0 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/src/main/java/io/flutter/plugins/googlemaps/GoogleMapsPlugin.java @@ -24,7 +24,7 @@ * the map. A Texture drawn using GoogleMap bitmap snapshots can then be shown instead of the * overlay. */ -public class GoogleMapsPlugin implements FlutterPlugin, ActivityAware, LifecycleProvider { +public class GoogleMapsPlugin implements FlutterPlugin, ActivityAware { @Nullable private Lifecycle lifecycle; @@ -69,7 +69,17 @@ public GoogleMapsPlugin() {} public void onAttachedToEngine(FlutterPluginBinding binding) { binding .getPlatformViewRegistry() - .registerViewFactory(VIEW_TYPE, new GoogleMapFactory(binding.getBinaryMessenger(), this)); + .registerViewFactory( + VIEW_TYPE, + new GoogleMapFactory( + binding.getBinaryMessenger(), + new LifecycleProvider() { + @Nullable + @Override + public Lifecycle getLifecycle() { + return lifecycle; + } + })); } @Override @@ -97,12 +107,6 @@ public void onDetachedFromActivityForConfigChanges() { onDetachedFromActivity(); } - @Nullable - @Override - public Lifecycle getLifecycle() { - return lifecycle; - } - /** * This class provides a {@link LifecycleOwner} for the activity driven by {@link * ActivityLifecycleCallbacks}.