From 55e19bb96d9fb696c82a27c586cc254088d18cd2 Mon Sep 17 00:00:00 2001 From: Ross Wang Date: Tue, 11 Apr 2023 16:27:16 -0700 Subject: [PATCH] Hacks to get currents to work There are serious performance issues on Android that go as deep as GMS, so we'll want to migrate to flutter_map instead. For now, just do what we have to. --- .../lib/src/controller.dart | 6 +- .../lib/src/google_map.dart | 13 +++- .../flutter/plugins/googlemaps/Convert.java | 75 +++++++++++++++---- .../googlemaps/GoogleMapController.java | 5 ++ .../lib/src/google_maps_flutter_android.dart | 17 ++++- .../lib/src/google_maps_flutter_ios.dart | 3 +- .../lib/latlng.dart | 1 + .../method_channel_google_maps_flutter.dart | 3 +- .../google_maps_flutter_platform.dart | 3 +- .../lib/src/types/cap.dart | 15 ++++ .../lib/src/types/location.dart | 11 +-- .../lib/src/types/maps_object_updates.dart | 10 +-- .../lib/src/types/polyline.dart | 66 +++++++++++++--- .../lib/src/convert.dart | 11 ++- .../lib/src/google_maps_flutter_web.dart | 3 +- 15 files changed, 186 insertions(+), 56 deletions(-) create mode 100644 packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/latlng.dart diff --git a/packages/google_maps_flutter/google_maps_flutter/lib/src/controller.dart b/packages/google_maps_flutter/google_maps_flutter/lib/src/controller.dart index cd3d0781e47..68a335dfc09 100644 --- a/packages/google_maps_flutter/google_maps_flutter/lib/src/controller.dart +++ b/packages/google_maps_flutter/google_maps_flutter/lib/src/controller.dart @@ -123,10 +123,10 @@ class GoogleMapController { /// platform side. /// /// The returned [Future] completes after listeners have been notified. - Future _updatePolylines(PolylineUpdates polylineUpdates) { - assert(polylineUpdates != null); + Future _updatePolylines( + PolylineUpdates updates, Map oldPolylines) { return GoogleMapsFlutterPlatform.instance - .updatePolylines(polylineUpdates, mapId: mapId); + .updatePolylines(updates, oldPolylines, mapId: mapId); } /// Updates circle configuration. diff --git a/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart b/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart index 15ea6182d7b..697ee7f1abb 100644 --- a/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart +++ b/packages/google_maps_flutter/google_maps_flutter/lib/src/google_map.dart @@ -348,7 +348,9 @@ class _GoogleMapState extends State { _updateOptions(); _updateMarkers(); _updatePolygons(); - _updatePolylines(); + if (widget.polylines != oldWidget.polylines) { + _updatePolylines(widget.polylines, oldWidget.polylines); + } _updateCircles(); _updateTileOverlays(); } @@ -381,12 +383,15 @@ class _GoogleMapState extends State { _polygons = keyByPolygonId(widget.polygons); } - Future _updatePolylines() async { + Future _updatePolylines( + Set polylines, Set oldPolylines) async { final GoogleMapController controller = await _controller.future; // ignore: unawaited_futures controller._updatePolylines( - PolylineUpdates.from(_polylines.values.toSet(), widget.polylines)); - _polylines = keyByPolylineId(widget.polylines); + PolylineUpdates.from(oldPolylines, polylines), + _polylines, + ); + _polylines = keyByPolylineId(polylines); } Future _updateCircles() async { diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java index 6d143c637dd..cd7624bded6 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java @@ -36,9 +36,34 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; /** Conversions between JSON-like values and GoogleMaps data types. */ class Convert { + private static final class AssetImageKey { + public final String asset; + public final double scale; + + public AssetImageKey(String asset, double scale) { + this.asset = asset; + this.scale = scale; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AssetImageKey that = (AssetImageKey) o; + return Double.compare(that.scale, scale) == 0 && asset.equals(that.asset); + } + + @Override + public int hashCode() { + return Objects.hash(asset, scale); + } + } + + private static Map assetCache = new HashMap<>(); // TODO(hamdikahloun): FlutterMain has been deprecated and should be replaced with FlutterLoader // when it's available in Stable channel: https://github.com/flutter/flutter/issues/70923. @@ -68,18 +93,27 @@ private static BitmapDescriptor toBitmapDescriptor(Object o, Context context) { return BitmapDescriptorFactory.fromAsset( io.flutter.view.FlutterMain.getLookupKeyForAsset(toString(data.get(1)))); } else { - // Another option could be to modify the Android API side to support scaling in BitmapDescriptor.loadBitmap. - final String fileName = io.flutter.view.FlutterMain.getLookupKeyForAsset(toString(data.get(1))); - try (final InputStream asset = context.getAssets().open(fileName)) { - final BitmapFactory.Options options = new BitmapFactory.Options(); - options.inScaled = true; - options.inDensity = DisplayMetrics.DENSITY_DEFAULT; - options.inTargetDensity = (int) (scale * DisplayMetrics.DENSITY_DEFAULT); - return BitmapDescriptorFactory.fromBitmap(BitmapFactory.decodeStream(asset, null, options)); - } catch (IOException e) { - // This may just be punting the error down the line though. It may be better to return - // null (which is what loadBitmap does) to fail fast. - return BitmapDescriptorFactory.fromAsset(fileName); + final AssetImageKey key = new AssetImageKey(toString(data.get(1)), scale); + BitmapDescriptor result = assetCache.get(key); + if (result != null) { + return result; + } else { + // Another option could be to modify the Android API side to support scaling in BitmapDescriptor.loadBitmap. + final String fileName = io.flutter.view.FlutterMain.getLookupKeyForAsset(key.asset); + try (final InputStream asset = context.getAssets().open(fileName)) { + final BitmapFactory.Options options = new BitmapFactory.Options(); + options.inScaled = true; + options.inDensity = DisplayMetrics.DENSITY_DEFAULT; + options.inTargetDensity = (int) (scale * DisplayMetrics.DENSITY_DEFAULT); + result = BitmapDescriptorFactory.fromBitmap(BitmapFactory.decodeStream(asset, null, options)); + } catch (IOException e) { + // This may just be punting the error down the line though. It may be better to return + // null (which is what loadBitmap does) to fail fast. + result = BitmapDescriptorFactory.fromAsset(fileName); + } + assetCache.put(key, result); + System.out.println("Asset cache size: " + assetCache.size()); + return result; } } } else { @@ -667,6 +701,8 @@ private static List toPattern(Object o) { return pattern; } + private static Map, Cap> capCache = new HashMap<>(); + private static Cap toCap(Object o, Context context) { final List data = toList(o); switch (toString(data.get(0))) { @@ -677,11 +713,18 @@ private static Cap toCap(Object o, Context context) { case "squareCap": return new SquareCap(); case "customCap": - if (data.size() == 2) { - return new CustomCap(toBitmapDescriptor(data.get(1), context)); - } else { - return new CustomCap(toBitmapDescriptor(data.get(1), context), toFloat(data.get(2))); + final List key = data.subList(1, data.size()); + Cap cap = capCache.get(key); + if (cap == null) { + if (data.size() == 2) { + cap = new CustomCap(toBitmapDescriptor(data.get(1), context)); + } else { + cap = new CustomCap(toBitmapDescriptor(data.get(1), context), toFloat(data.get(2))); + } + capCache.put(key, cap); + System.out.println("Cap cache size: " + capCache.size()); } + return cap; default: throw new IllegalArgumentException("Cannot interpret " + o + " as Cap"); } diff --git a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java index 7486bc9c7ec..510c75460c7 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java +++ b/packages/google_maps_flutter/google_maps_flutter_android/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java @@ -354,13 +354,18 @@ public void onSnapshotReady(Bitmap bitmap) { } case "polylines#update": { + long t = System.currentTimeMillis(); invalidateMapIfNeeded(); + System.out.println("invalidate: " + (System.currentTimeMillis() - t) + " ms"); + + t = System.currentTimeMillis(); List polylinesToAdd = call.argument("polylinesToAdd"); polylinesController.addPolylines(polylinesToAdd); List polylinesToChange = call.argument("polylinesToChange"); polylinesController.changePolylines(polylinesToChange); List polylineIdsToRemove = call.argument("polylineIdsToRemove"); polylinesController.removePolylines(polylineIdsToRemove); + System.out.println("CRUD: " + (System.currentTimeMillis() - t) + " ms"); result.success(null); break; } diff --git a/packages/google_maps_flutter/google_maps_flutter_android/lib/src/google_maps_flutter_android.dart b/packages/google_maps_flutter/google_maps_flutter_android/lib/src/google_maps_flutter_android.dart index 725fcdbbbab..2da07b2a1f2 100644 --- a/packages/google_maps_flutter/google_maps_flutter_android/lib/src/google_maps_flutter_android.dart +++ b/packages/google_maps_flutter/google_maps_flutter_android/lib/src/google_maps_flutter_android.dart @@ -342,13 +342,24 @@ class GoogleMapsFlutterAndroid extends GoogleMapsFlutterPlatform { @override Future updatePolylines( - PolylineUpdates polylineUpdates, { + PolylineUpdates updates, + Map oldPolylines, { required int mapId, }) { - assert(polylineUpdates != null); return _channel(mapId).invokeMethod( 'polylines#update', - polylineUpdates.toJson(), + { + 'polylinesToAdd': [ + for (final toAdd in updates.objectsToAdd) toAdd.toJson(), + ], + 'polylinesToChange': [ + for (final toChange in updates.objectsToChange) + toChange.toDiffJson(oldPolylines[toChange.polylineId]!), + ], + 'polylineIdsToRemove': [ + for (final idToRemove in updates.objectIdsToRemove) idToRemove.value, + ] + }, ); } diff --git a/packages/google_maps_flutter/google_maps_flutter_ios/lib/src/google_maps_flutter_ios.dart b/packages/google_maps_flutter/google_maps_flutter_ios/lib/src/google_maps_flutter_ios.dart index 492aedab1fd..3e89a96927b 100644 --- a/packages/google_maps_flutter/google_maps_flutter_ios/lib/src/google_maps_flutter_ios.dart +++ b/packages/google_maps_flutter/google_maps_flutter_ios/lib/src/google_maps_flutter_ios.dart @@ -324,7 +324,8 @@ class GoogleMapsFlutterIOS extends GoogleMapsFlutterPlatform { @override Future updatePolylines( - PolylineUpdates polylineUpdates, { + PolylineUpdates polylineUpdates, + Map oldPolylines, { required int mapId, }) { assert(polylineUpdates != null); diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/latlng.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/latlng.dart new file mode 100644 index 00000000000..0fbcc872d54 --- /dev/null +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/latlng.dart @@ -0,0 +1 @@ +export 'src/types/location.dart'; diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/method_channel/method_channel_google_maps_flutter.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/method_channel/method_channel_google_maps_flutter.dart index 36a96ac2c6f..e2026f3b8cd 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/method_channel/method_channel_google_maps_flutter.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/method_channel/method_channel_google_maps_flutter.dart @@ -327,7 +327,8 @@ class MethodChannelGoogleMapsFlutter extends GoogleMapsFlutterPlatform { @override Future updatePolylines( - PolylineUpdates polylineUpdates, { + PolylineUpdates polylineUpdates, + Map oldPolylines, { required int mapId, }) { assert(polylineUpdates != null); diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/platform_interface/google_maps_flutter_platform.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/platform_interface/google_maps_flutter_platform.dart index 147d64f715b..8d764d12fe6 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/platform_interface/google_maps_flutter_platform.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/platform_interface/google_maps_flutter_platform.dart @@ -113,7 +113,8 @@ abstract class GoogleMapsFlutterPlatform extends PlatformInterface { /// /// The returned [Future] completes after listeners have been notified. Future updatePolylines( - PolylineUpdates polylineUpdates, { + PolylineUpdates polylineUpdates, + Map oldPolylines, { required int mapId, }) { throw UnimplementedError('updatePolylines() has not been implemented.'); diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/cap.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/cap.dart index 5bef7baf0bf..f956243903e 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/cap.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/cap.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart' show immutable; import 'types.dart'; @@ -52,4 +53,18 @@ class Cap { /// Converts this object to something serializable in JSON. Object toJson() => _json; + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (other.runtimeType != runtimeType) { + return false; + } + return other is Cap && + const DeepCollectionEquality().equals(_json, other._json); + } + + @override + int get hashCode => const DeepCollectionEquality().hash(_json); } diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/location.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/location.dart index 81fe08bb132..ff718c0e6aa 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/location.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/location.dart @@ -2,11 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:flutter/foundation.dart' - show immutable, objectRuntimeType, visibleForTesting; - /// A pair of latitude and longitude coordinates, stored as degrees. -@immutable class LatLng { /// Creates a geographical location specified in degrees [latitude] and /// [longitude]. @@ -47,8 +43,7 @@ class LatLng { } @override - String toString() => - '${objectRuntimeType(this, 'LatLng')}($latitude, $longitude)'; + String toString() => 'LatLng($latitude, $longitude)'; @override bool operator ==(Object other) { @@ -69,7 +64,6 @@ class LatLng { /// if `southwest.longitude` ≤ `northeast.longitude`, /// * lng ∈ [-180, `northeast.longitude`] ∪ [`southwest.longitude`, 180], /// if `northeast.longitude` < `southwest.longitude` -@immutable class LatLngBounds { /// Creates geographical bounding box with the specified corners. /// @@ -110,7 +104,6 @@ class LatLngBounds { } /// Converts a list to [LatLngBounds]. - @visibleForTesting static LatLngBounds? fromList(Object? json) { if (json == null) { return null; @@ -125,7 +118,7 @@ class LatLngBounds { @override String toString() { - return '${objectRuntimeType(this, 'LatLngBounds')}($southwest, $northeast)'; + return 'LatLngBounds($southwest, $northeast)'; } @override diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/maps_object_updates.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/maps_object_updates.dart index 71015ce9c23..a8e1b241b74 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/maps_object_updates.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/maps_object_updates.dart @@ -51,7 +51,7 @@ class MapsObjectUpdates> { MapsObjectUpdates.from( Set previous, Set current, { - required this.objectName, + this.objectName, }) { final Map, T> previousObjects = keyByMapsObjectId(previous); final Map, T> currentObjects = keyByMapsObjectId(current); @@ -86,7 +86,7 @@ class MapsObjectUpdates> { } /// The name of the objects being updated, for use in serialization. - final String objectName; + final String? objectName; /// Set of objects to be added in this update. Set get objectsToAdd { @@ -119,11 +119,11 @@ class MapsObjectUpdates> { } } - addIfNonNull('${objectName}sToAdd', serializeMapsObjectSet(_objectsToAdd)); + addIfNonNull('${objectName!}sToAdd', serializeMapsObjectSet(_objectsToAdd)); addIfNonNull( - '${objectName}sToChange', serializeMapsObjectSet(_objectsToChange)); + '${objectName!}sToChange', serializeMapsObjectSet(_objectsToChange)); addIfNonNull( - '${objectName}IdsToRemove', + '${objectName!}IdsToRemove', _objectIdsToRemove .map((MapsObjectId m) => m.value) .toList()); diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/polyline.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/polyline.dart index cd0a64ee7b8..fd8d236965e 100644 --- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/polyline.dart +++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/polyline.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart' show immutable, listEquals, VoidCallback; import 'package:flutter/material.dart' show Color, Colors; @@ -163,22 +164,22 @@ class Polyline implements MapsObject { Object toJson() { final Map json = {}; - void addIfPresent(String fieldName, Object? value) { + void addIfChanged(String fieldName, Object? value) { if (value != null) { json[fieldName] = value; } } - addIfPresent('polylineId', polylineId.value); - addIfPresent('consumeTapEvents', consumeTapEvents); - addIfPresent('color', color.value); - addIfPresent('endCap', endCap.toJson()); - addIfPresent('geodesic', geodesic); - addIfPresent('jointType', jointType.value); - addIfPresent('startCap', startCap.toJson()); - addIfPresent('visible', visible); - addIfPresent('width', width); - addIfPresent('zIndex', zIndex); + addIfChanged('polylineId', polylineId.value); + addIfChanged('consumeTapEvents', consumeTapEvents); + addIfChanged('color', color.value); + addIfChanged('endCap', endCap.toJson()); + addIfChanged('geodesic', geodesic); + addIfChanged('jointType', jointType.value); + addIfChanged('startCap', startCap.toJson()); + addIfChanged('visible', visible); + addIfChanged('width', width); + addIfChanged('zIndex', zIndex); if (points != null) { json['points'] = _pointsToJson(); @@ -191,6 +192,49 @@ class Polyline implements MapsObject { return json; } + Object toDiffJson(Polyline old) { + final Map json = { + 'polylineId': polylineId.value, + }; + + void addIfChanged( + String fieldName, T Function(Polyline) getter, + [Object Function(T)? toJson]) { + final T value = getter(this); + if (value != getter(old)) { + json[fieldName] = toJson == null ? value : toJson(value); + } + } + + addIfChanged('consumeTapEvents', (p) => consumeTapEvents); + addIfChanged('color', (p) => p.color, (color) => color.value); + addIfChanged('endCap', (p) => p.endCap, (endCap) => endCap.toJson()); + addIfChanged('geodesic', (p) => p.geodesic); + addIfChanged( + 'jointType', + (p) => p.jointType, + (jointType) => jointType.value, + ); + addIfChanged( + 'startCap', + (p) => p.startCap, + (startCap) => startCap.toJson(), + ); + addIfChanged('visible', (p) => p.visible); + addIfChanged('width', (p) => p.width); + addIfChanged('zIndex', (p) => p.zIndex); + + if (!const ListEquality().equals(points, old.points)) { + json['points'] = _pointsToJson(); + } + + if (!const ListEquality().equals(patterns, old.patterns)) { + json['pattern'] = _patternToJson(); + } + + return json; + } + @override bool operator ==(Object other) { if (identical(this, other)) { diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/convert.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/convert.dart index 3925dbbc22b..c664129b1ee 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/convert.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/convert.dart @@ -407,7 +407,7 @@ gmaps.PolylineOptions _polylineOptionsFromPolyline( final List paths = polyline.points.map(_latLngToGmLatLng).toList(); - return gmaps.PolylineOptions() + final options = gmaps.PolylineOptions() ..path = paths ..strokeWeight = polyline.width ..strokeColor = _getCssColor(polyline.color) @@ -420,6 +420,15 @@ gmaps.PolylineOptions _polylineOptionsFromPolyline( // this.patterns = const [], // this.startCap = Cap.buttCap, // this.width = 10, + final endCap = polyline.endCap.toJson() as List; + if (endCap[0] == 'customCap') { + options.icons = [ + gmaps.IconSequence() + ..icon = + (gmaps.GSymbol()..path = gmaps.SymbolPath.FORWARD_CLOSED_ARROW), + ]; + } + return options; } // Translates a [CameraUpdate] into operations on a [gmaps.GMap]. diff --git a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_flutter_web.dart b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_flutter_web.dart index 707a4a5c2f6..9079270a9f8 100644 --- a/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_flutter_web.dart +++ b/packages/google_maps_flutter/google_maps_flutter_web/lib/src/google_maps_flutter_web.dart @@ -75,7 +75,8 @@ class GoogleMapsPlugin extends GoogleMapsFlutterPlatform { /// Applies the passed in `polylineUpdates` to the `mapId`. @override Future updatePolylines( - PolylineUpdates polylineUpdates, { + PolylineUpdates polylineUpdates, + Map oldPolylines, { required int mapId, }) async { _map(mapId).updatePolylines(polylineUpdates);