Port google_maps_flutter to use AndroidView for embedding the map. (#743)
This change adds a GoogleMap widget which shows a map as part of the widget tree.
Each map uses a dedicated method channel named 'plugins.flutter.io/google_maps_$id',
The Java GoogleMapsController class now extends PlatformView, and is the
Java endpoint of the per map method channel.
This change disables the plugin on iOS until we have an inlining solution
for iOS.
diff --git a/packages/google_maps_flutter/README.md b/packages/google_maps_flutter/README.md
index 19eb664..6457d4f 100644
--- a/packages/google_maps_flutter/README.md
+++ b/packages/google_maps_flutter/README.md
@@ -2,8 +2,7 @@
[](https://pub.dartlang.org/packages/google_maps_flutter)
-A Flutter plugin to use [Google Maps](https://developers.google.com/maps/) in
-iOS and Android apps.
+A Flutter plugin to use [Google Maps](https://developers.google.com/maps/).
## Caveat
@@ -12,24 +11,9 @@
code are still being consolidated and expanded. The intention is to grow
current coverage into a complete offering. Issues and pull requests aimed to
help us prioritize and speed up this effort are very welcome.
-* The technique currently used for compositing GoogleMap views with Flutter
- widgets is *inherently limited* and will be replaced by a fully compositional
- [Texture](https://docs.flutter.io/flutter/widgets/Texture-class.html)-based
- approach before we publish this plugin.
-
- In detail: the plugin currently relies on placing platform overlays on top of
- a bitmap snapshotting widget for creating the illusion of in-line compositing
- of GoogleMap views with Flutter widgets. This works only in very limited
- situations where
- * the widget is stationary
- * the widget is drawn on top of all other widgets within bounds
- * touch events within widget bounds can be safely ignored by Flutter
-
- The root problem with platform overlays is that they cannot be freely composed
- with other widgets. Many workarounds can be devised to address this shortcoming
- in particular situations, but the Flutter team does not intend to support such
- work, as it would not move us forward towards our goal of a fully compositional
- GoogleMaps widget.
+* Currently the plugin only supports Android as it embeds a platform view in the
+ Flutter hierarchy which is currently only supported for Android ([tracking
+ issue](https://github.com/flutter/flutter/issues/19030)).
## Usage
@@ -79,38 +63,32 @@
### Both
-You can now instantiate a `GoogleMapOverlayController` and use it to configure
-a `GoogleMapOverlay` widget. Client code will have to change once the plugin
-stops using platform overlays.
+You can now add a `GoogleMap` widget to your widget tree.
-Once added as an overlay, the map view can be controlled via the
-`GoogleMapController` that you obtain as the `mapController` property of
-the overlay controller. Client code written against the `GoogleMapController`
-interface will be unaffected by the change away from platform overlays.
+The map view can be controlled with the `GoogleMapController` that is passed to
+the `GoogleMap`'s `onMapCreated` callback.
```dart
import 'package:flutter/material.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
void main() {
- GoogleMapController.init();
- final GoogleMapOverlayController controller =
- GoogleMapOverlayController.fromSize(width: 300.0, height: 200.0);
- final Widget mapWidget = GoogleMapOverlay(controller: controller);
runApp(MaterialApp(
home: new Scaffold(
appBar: AppBar(title: const Text('Google Maps demo')),
- body: MapsDemo(mapWidget, controller.mapController),
+ body: MapsDemo(),
),
- navigatorObservers: <NavigatorObserver>[controller.overlayController],
));
}
-class MapsDemo extends StatelessWidget {
- MapsDemo(this.mapWidget, this.controller);
+class MapsDemo extends StatefulWidget {
+ @override
+ State createState() => MapsDemoState();
+}
- final Widget mapWidget;
- final GoogleMapController controller;
+class MapsDemoState extends State<MapsDemo> {
+
+ GoogleMapController mapController;
@override
Widget build(BuildContext context) {
@@ -119,11 +97,19 @@
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
- Center(child: mapWidget),
+ Center(
+ child: SizedBox(
+ width: 300.0,
+ height: 200.0,
+ child: GoogleMap(
+ onMapCreated: _onMapCreated,
+ ),
+ ),
+ ),
RaisedButton(
child: const Text('Go to London'),
- onPressed: () {
- controller.animateCamera(CameraUpdate.newCameraPosition(
+ onPressed: mapController == null ? null : () {
+ mapController.animateCamera(CameraUpdate.newCameraPosition(
const CameraPosition(
bearing: 270.0,
target: LatLng(51.5160895, -0.1294527),
@@ -137,6 +123,10 @@
),
);
}
+
+ void _onMapCreated(GoogleMapController controller) {
+ setState(() { mapController = controller; });
+ }
}
```
diff --git a/packages/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapBuilder.java b/packages/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapBuilder.java
index f6fe4ef..d2b1c6d 100644
--- a/packages/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapBuilder.java
+++ b/packages/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapBuilder.java
@@ -4,10 +4,10 @@
package io.flutter.plugins.googlemaps;
+import android.content.Context;
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.MethodChannel;
import io.flutter.plugin.common.PluginRegistry;
import java.util.concurrent.atomic.AtomicInteger;
@@ -16,13 +16,9 @@
private boolean trackCameraPosition = false;
GoogleMapController build(
- AtomicInteger state,
- PluginRegistry.Registrar registrar,
- int width,
- int height,
- MethodChannel.Result result) {
+ int id, Context context, AtomicInteger state, PluginRegistry.Registrar registrar) {
final GoogleMapController controller =
- new GoogleMapController(state, registrar, width, height, options, result);
+ new GoogleMapController(id, context, state, registrar, options);
controller.init();
controller.setTrackCameraPosition(trackCameraPosition);
return controller;
diff --git a/packages/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java b/packages/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java
index 05739b3..4f0b1b4 100644
--- a/packages/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java
+++ b/packages/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapController.java
@@ -12,12 +12,9 @@
import android.app.Activity;
import android.app.Application;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Paint;
+import android.content.Context;
import android.os.Bundle;
-import android.view.Surface;
-import android.widget.FrameLayout;
+import android.view.View;
import com.google.android.gms.maps.CameraUpdate;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
@@ -28,79 +25,60 @@
import com.google.android.gms.maps.model.LatLngBounds;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;
+import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.PluginRegistry;
-import io.flutter.view.TextureRegistry;
-import java.util.ArrayList;
+import io.flutter.plugin.platform.PlatformView;
+import java.util.Collections;
import java.util.HashMap;
-import java.util.List;
import java.util.Map;
-import java.util.Timer;
-import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicInteger;
/** Controller of a single GoogleMaps MapView instance. */
final class GoogleMapController
implements Application.ActivityLifecycleCallbacks,
- GoogleMapOptionsSink,
- OnMapReadyCallback,
- GoogleMap.SnapshotReadyCallback,
+ GoogleMap.OnCameraIdleListener,
+ GoogleMap.OnCameraMoveListener,
+ GoogleMap.OnCameraMoveStartedListener,
GoogleMap.OnInfoWindowClickListener,
GoogleMap.OnMarkerClickListener,
- GoogleMap.OnCameraMoveStartedListener,
- GoogleMap.OnCameraMoveListener,
- GoogleMap.OnCameraIdleListener {
+ GoogleMapOptionsSink,
+ MethodChannel.MethodCallHandler,
+ OnMapReadyCallback,
+ OnMarkerTappedListener,
+ PlatformView {
+ private final int id;
private final AtomicInteger activityState;
- private final FrameLayout parent;
+ private final MethodChannel methodChannel;
private final PluginRegistry.Registrar registrar;
- private final TextureRegistry.SurfaceTextureEntry textureEntry;
private final MapView mapView;
- private final Bitmap bitmap;
- private final int width;
- private final int height;
- private final MethodChannel.Result result;
- private final Timer timer;
private final Map<String, MarkerController> markers;
- private OnMarkerTappedListener onMarkerTappedListener;
- private OnCameraMoveListener onCameraMoveListener;
- private OnInfoWindowTappedListener onInfoWindowTappedListener;
private GoogleMap googleMap;
- private Surface surface;
private boolean trackCameraPosition = false;
private boolean disposed = false;
+ private final float density;
+ private MethodChannel.Result mapReadyResult;
GoogleMapController(
+ int id,
+ Context context,
AtomicInteger activityState,
PluginRegistry.Registrar registrar,
- int width,
- int height,
- GoogleMapOptions options,
- MethodChannel.Result result) {
+ GoogleMapOptions options) {
+ this.id = id;
this.activityState = activityState;
this.registrar = registrar;
- this.width = width;
- this.height = height;
- this.result = result;
- this.bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
- this.parent = (FrameLayout) registrar.view().getParent();
- this.textureEntry = registrar.textures().createSurfaceTexture();
- this.surface = new Surface(textureEntry.surfaceTexture());
- textureEntry.surfaceTexture().setDefaultBufferSize(width, height);
- this.mapView = new MapView(registrar.activity(), options);
- this.timer = new Timer();
+ this.mapView = new MapView(context, options);
this.markers = new HashMap<>();
+ this.density = context.getResources().getDisplayMetrics().density;
+ methodChannel =
+ new MethodChannel(registrar.messenger(), "plugins.flutter.io/google_maps_" + id);
+ methodChannel.setMethodCallHandler(this);
}
- void setOnCameraMoveListener(OnCameraMoveListener listener) {
- this.onCameraMoveListener = listener;
- }
-
- void setOnMarkerTappedListener(OnMarkerTappedListener listener) {
- this.onMarkerTappedListener = listener;
- }
-
- void setOnInfoWindowTappedListener(OnInfoWindowTappedListener listener) {
- this.onInfoWindowTappedListener = listener;
+ @Override
+ public View getView() {
+ return mapView;
}
void init() {
@@ -132,66 +110,39 @@
break;
}
registrar.activity().getApplication().registerActivityLifecycleCallbacks(this);
- final FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(width, height);
- parent.addView(mapView, 0, layoutParams);
mapView.getMapAsync(this);
}
- long id() {
- return textureEntry.id();
- }
-
- void showOverlay(int x, int y) {
- if (disposed) {
- return;
- }
- parent.removeView(mapView);
- final FrameLayout.LayoutParams layout = new FrameLayout.LayoutParams(width, height);
- layout.leftMargin = x;
- layout.topMargin = y;
- parent.addView(mapView, layout);
- }
-
- void hideOverlay() {
- if (disposed) {
- return;
- }
- googleMap.stopAnimation();
- parent.removeView(mapView);
- parent.addView(mapView, 0);
- }
-
- void moveCamera(CameraUpdate cameraUpdate) {
+ private void moveCamera(CameraUpdate cameraUpdate) {
googleMap.moveCamera(cameraUpdate);
}
- void animateCamera(CameraUpdate cameraUpdate) {
+ private void animateCamera(CameraUpdate cameraUpdate) {
googleMap.animateCamera(cameraUpdate);
}
- CameraPosition getCameraPosition() {
+ private CameraPosition getCameraPosition() {
return trackCameraPosition ? googleMap.getCameraPosition() : null;
}
- MarkerBuilder newMarkerBuilder() {
+ private MarkerBuilder newMarkerBuilder() {
return new MarkerBuilder(this);
}
Marker addMarker(MarkerOptions markerOptions, boolean consumesTapEvents) {
final Marker marker = googleMap.addMarker(markerOptions);
- markers.put(
- marker.getId(), new MarkerController(marker, consumesTapEvents, onMarkerTappedListener));
+ markers.put(marker.getId(), new MarkerController(marker, consumesTapEvents, this));
return marker;
}
- void removeMarker(String markerId) {
+ private void removeMarker(String markerId) {
final MarkerController markerController = markers.remove(markerId);
if (markerController != null) {
markerController.remove();
}
}
- MarkerController marker(String markerId) {
+ private MarkerController marker(String markerId) {
final MarkerController marker = markers.get(markerId);
if (marker == null) {
throw new IllegalArgumentException("Unknown marker: " + markerId);
@@ -199,58 +150,114 @@
return marker;
}
- private void updateTexture() {
- if (disposed) {
- return;
- }
- final Canvas canvas = surface.lockCanvas(null);
- canvas.drawBitmap(bitmap, 0, 0, new Paint());
- surface.unlockCanvasAndPost(canvas);
- }
-
@Override
public void onMapReady(GoogleMap googleMap) {
this.googleMap = googleMap;
- result.success(id());
googleMap.setOnInfoWindowClickListener(this);
+ if (mapReadyResult != null) {
+ mapReadyResult.success(null);
+ mapReadyResult = null;
+ }
googleMap.setOnCameraMoveStartedListener(this);
googleMap.setOnCameraMoveListener(this);
googleMap.setOnCameraIdleListener(this);
googleMap.setOnMarkerClickListener(this);
- // Take snapshots until the dust settles.
- timer.schedule(newSnapshotTask(), 0);
- timer.schedule(newSnapshotTask(), 500);
- timer.schedule(newSnapshotTask(), 1000);
- timer.schedule(newSnapshotTask(), 2000);
- timer.schedule(newSnapshotTask(), 4000);
}
- @Override
- public void onCameraMoveStarted(int reason) {
- onCameraMoveListener.onCameraMoveStarted(
- reason == GoogleMap.OnCameraMoveStartedListener.REASON_GESTURE);
- cancelSnapshotTimerTasks();
- }
-
- @Override
- public void onInfoWindowClick(Marker marker) {
- onInfoWindowTappedListener.onInfoWindowTapped(marker);
- }
-
- @Override
- public void onCameraMove() {
- if (trackCameraPosition && onCameraMoveListener != null) {
- onCameraMoveListener.onCameraMove(googleMap.getCameraPosition());
+ public void onMethodCall(MethodCall call, MethodChannel.Result result) {
+ switch (call.method) {
+ case "map#waitForMap":
+ if (googleMap != null) {
+ result.success(null);
+ return;
+ }
+ mapReadyResult = result;
+ break;
+ case "map#update":
+ {
+ Convert.interpretGoogleMapOptions(call.argument("options"), this);
+ result.success(Convert.toJson(getCameraPosition()));
+ break;
+ }
+ case "camera#move":
+ {
+ 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;
+ }
+ case "marker#add":
+ {
+ final MarkerBuilder markerBuilder = newMarkerBuilder();
+ Convert.interpretMarkerOptions(call.argument("options"), markerBuilder);
+ final String markerId = markerBuilder.build();
+ result.success(markerId);
+ break;
+ }
+ case "marker#remove":
+ {
+ final String markerId = call.argument("marker");
+ removeMarker(markerId);
+ result.success(null);
+ break;
+ }
+ case "marker#update":
+ {
+ final String markerId = call.argument("marker");
+ final MarkerController marker = marker(markerId);
+ Convert.interpretMarkerOptions(call.argument("options"), marker);
+ result.success(null);
+ break;
+ }
+ default:
+ result.notImplemented();
}
}
@Override
+ public void onCameraMoveStarted(int reason) {
+ final Map<String, Object> arguments = new HashMap<>(2);
+ boolean isGesture = reason == GoogleMap.OnCameraMoveStartedListener.REASON_GESTURE;
+ arguments.put("isGesture", isGesture);
+ methodChannel.invokeMethod("camera#onMoveStarted", arguments);
+ }
+
+ @Override
+ public void onInfoWindowClick(Marker marker) {
+ final Map<String, Object> arguments = new HashMap<>(2);
+ arguments.put("marker", marker.getId());
+ methodChannel.invokeMethod("infoWindow#onTap", arguments);
+ }
+
+ @Override
+ public void onCameraMove() {
+ if (!trackCameraPosition) {
+ return;
+ }
+ final Map<String, Object> arguments = new HashMap<>(2);
+ arguments.put("position", Convert.toJson(googleMap.getCameraPosition()));
+ methodChannel.invokeMethod("camera#onMove", arguments);
+ }
+
+ @Override
public void onCameraIdle() {
- onCameraMoveListener.onCameraIdle();
- // Take snapshots until the dust settles.
- timer.schedule(newSnapshotTask(), 500);
- timer.schedule(newSnapshotTask(), 1500);
- timer.schedule(newSnapshotTask(), 4000);
+ methodChannel.invokeMethod("camera#onIdle", Collections.singletonMap("map", id));
+ }
+
+ @Override
+ public void onMarkerTapped(Marker marker) {
+ final Map<String, Object> arguments = new HashMap<>(2);
+ arguments.put("marker", marker.getId());
+ methodChannel.invokeMethod("marker#onTap", arguments);
}
@Override
@@ -260,18 +267,11 @@
}
@Override
- public void onSnapshotReady(Bitmap bitmap) {
- updateTexture();
- }
-
- void dispose() {
+ public void dispose() {
if (disposed) {
return;
}
disposed = true;
- timer.cancel();
- parent.removeView(mapView);
- textureEntry.release();
mapView.onDestroy();
registrar.activity().getApplication().unregisterActivityLifecycleCallbacks(this);
}
@@ -332,31 +332,6 @@
mapView.onDestroy();
}
- private List<SnapshotTimerTask> snapshotTasks = new ArrayList<>();
-
- private SnapshotTimerTask newSnapshotTask() {
- final SnapshotTimerTask task = new SnapshotTimerTask();
- snapshotTasks.add(task);
- return task;
- }
-
- private void cancelSnapshotTimerTasks() {
- for (SnapshotTimerTask task : snapshotTasks) {
- task.cancel();
- }
- snapshotTasks.clear();
- }
-
- class SnapshotTimerTask extends TimerTask {
- @Override
- public void run() {
- if (disposed || activityState.get() != RESUMED) {
- return;
- }
- googleMap.snapshot(GoogleMapController.this, bitmap);
- }
- }
-
// GoogleMapOptionsSink methods
@Override
diff --git a/packages/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapFactory.java b/packages/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapFactory.java
new file mode 100644
index 0000000..8e2d18e
--- /dev/null
+++ b/packages/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapFactory.java
@@ -0,0 +1,28 @@
+package io.flutter.plugins.googlemaps;
+
+import static io.flutter.plugin.common.PluginRegistry.Registrar;
+
+import android.content.Context;
+import io.flutter.plugin.common.StandardMessageCodec;
+import io.flutter.plugin.platform.PlatformView;
+import io.flutter.plugin.platform.PlatformViewFactory;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class GoogleMapFactory extends PlatformViewFactory {
+
+ private final AtomicInteger mActivityState;
+ private final Registrar mPluginRegistrar;
+
+ public GoogleMapFactory(AtomicInteger state, Registrar registrar) {
+ super(StandardMessageCodec.INSTANCE);
+ mActivityState = state;
+ mPluginRegistrar = registrar;
+ }
+
+ @Override
+ public PlatformView create(Context context, int id, Object params) {
+ final GoogleMapBuilder builder = new GoogleMapBuilder();
+ Convert.interpretGoogleMapOptions(params, builder);
+ return builder.build(id, context, mActivityState, mPluginRegistrar);
+ }
+}
diff --git a/packages/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapsPlugin.java b/packages/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapsPlugin.java
index 103a466..f0873c9 100644
--- a/packages/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapsPlugin.java
+++ b/packages/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/GoogleMapsPlugin.java
@@ -7,17 +7,7 @@
import android.app.Activity;
import android.app.Application;
import android.os.Bundle;
-import com.google.android.gms.maps.CameraUpdate;
-import com.google.android.gms.maps.model.CameraPosition;
-import com.google.android.gms.maps.model.Marker;
-import io.flutter.plugin.common.MethodCall;
-import io.flutter.plugin.common.MethodChannel;
-import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
-import io.flutter.plugin.common.MethodChannel.Result;
import io.flutter.plugin.common.PluginRegistry.Registrar;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
/**
@@ -26,189 +16,22 @@
* the map. A Texture drawn using GoogleMap bitmap snapshots can then be shown instead of the
* overlay.
*/
-public class GoogleMapsPlugin implements MethodCallHandler, Application.ActivityLifecycleCallbacks {
+public class GoogleMapsPlugin implements Application.ActivityLifecycleCallbacks {
static final int CREATED = 1;
static final int STARTED = 2;
static final int RESUMED = 3;
static final int PAUSED = 4;
static final int STOPPED = 5;
static final int DESTROYED = 6;
- private final Map<Long, GoogleMapController> googleMaps = new HashMap<>();
- private final Registrar registrar;
- private final MethodChannel channel;
- private final float density;
private final AtomicInteger state = new AtomicInteger(0);
public static void registerWith(Registrar registrar) {
- final MethodChannel channel =
- new MethodChannel(registrar.messenger(), "plugins.flutter.io/google_maps");
- final GoogleMapsPlugin plugin = new GoogleMapsPlugin(registrar, channel);
- channel.setMethodCallHandler(plugin);
+ final GoogleMapsPlugin plugin = new GoogleMapsPlugin();
registrar.activity().getApplication().registerActivityLifecycleCallbacks(plugin);
- }
-
- private GoogleMapsPlugin(Registrar registrar, MethodChannel channel) {
- this.registrar = registrar;
- this.channel = channel;
- this.density = registrar.context().getResources().getDisplayMetrics().density;
- }
-
- @Override
- public void onMethodCall(MethodCall call, Result result) {
- switch (call.method) {
- case "init":
- {
- for (GoogleMapController controller : googleMaps.values()) {
- controller.dispose();
- }
- googleMaps.clear();
- result.success(null);
- break;
- }
- case "map#create":
- {
- final int width = Convert.toPixels(call.argument("width"), density);
- final int height = Convert.toPixels(call.argument("height"), density);
- final Map<?, ?> options = Convert.toMap(call.argument("options"));
- final GoogleMapBuilder builder = new GoogleMapBuilder();
- Convert.interpretGoogleMapOptions(options, builder);
- final GoogleMapController controller =
- builder.build(state, registrar, width, height, result);
- googleMaps.put(controller.id(), controller);
- controller.setOnCameraMoveListener(
- new OnCameraMoveListener() {
- @Override
- public void onCameraMoveStarted(boolean isGesture) {
- final Map<String, Object> arguments = new HashMap<>(2);
- arguments.put("map", controller.id());
- arguments.put("isGesture", isGesture);
- channel.invokeMethod("camera#onMoveStarted", arguments);
- }
-
- @Override
- public void onCameraMove(CameraPosition position) {
- final Map<String, Object> arguments = new HashMap<>(2);
- arguments.put("map", controller.id());
- arguments.put("position", Convert.toJson(position));
- channel.invokeMethod("camera#onMove", arguments);
- }
-
- @Override
- public void onCameraIdle() {
- channel.invokeMethod(
- "camera#onIdle", Collections.singletonMap("map", controller.id()));
- }
- });
- controller.setOnMarkerTappedListener(
- new OnMarkerTappedListener() {
- @Override
- public void onMarkerTapped(Marker marker) {
- final Map<String, Object> arguments = new HashMap<>(2);
- arguments.put("map", controller.id());
- arguments.put("marker", marker.getId());
- channel.invokeMethod("marker#onTap", arguments);
- }
- });
- controller.setOnInfoWindowTappedListener(
- new OnInfoWindowTappedListener() {
- @Override
- public void onInfoWindowTapped(Marker marker) {
- final Map<String, Object> arguments = new HashMap<>(2);
- arguments.put("map", controller.id());
- arguments.put("marker", marker.getId());
- channel.invokeMethod("infoWindow#onTap", arguments);
- }
- });
- // result.success is called from controller when the GoogleMaps instance
- // is ready
- break;
- }
- case "map#update":
- {
- final GoogleMapController controller = mapsController(call);
- Convert.interpretGoogleMapOptions(call.argument("options"), controller);
- result.success(Convert.toJson(controller.getCameraPosition()));
- break;
- }
- case "camera#move":
- {
- final GoogleMapController controller = mapsController(call);
- final CameraUpdate cameraUpdate =
- Convert.toCameraUpdate(call.argument("cameraUpdate"), density);
- controller.moveCamera(cameraUpdate);
- result.success(null);
- break;
- }
- case "camera#animate":
- {
- final GoogleMapController controller = mapsController(call);
- final CameraUpdate cameraUpdate =
- Convert.toCameraUpdate(call.argument("cameraUpdate"), density);
- controller.animateCamera(cameraUpdate);
- result.success(null);
- break;
- }
- case "marker#add":
- {
- final GoogleMapController controller = mapsController(call);
- final MarkerBuilder markerBuilder = controller.newMarkerBuilder();
- Convert.interpretMarkerOptions(call.argument("options"), markerBuilder);
- final String markerId = markerBuilder.build();
- result.success(markerId);
- break;
- }
- case "marker#remove":
- {
- final GoogleMapController controller = mapsController(call);
- final String markerId = call.argument("marker");
- controller.removeMarker(markerId);
- result.success(null);
- break;
- }
- case "marker#update":
- {
- final GoogleMapController controller = mapsController(call);
- final String markerId = call.argument("marker");
- final MarkerController marker = controller.marker(markerId);
- Convert.interpretMarkerOptions(call.argument("options"), marker);
- result.success(null);
- break;
- }
- case "map#show":
- {
- final GoogleMapController controller = mapsController(call);
- final int x = Convert.toPixels(call.argument("x"), density);
- final int y = Convert.toPixels(call.argument("y"), density);
- controller.showOverlay(x, y);
- result.success(null);
- break;
- }
- case "map#hide":
- {
- final GoogleMapController controller = mapsController(call);
- controller.hideOverlay();
- result.success(null);
- break;
- }
- case "map#dispose":
- {
- final GoogleMapController controller = mapsController(call);
- controller.dispose();
- result.success(null);
- break;
- }
- default:
- result.notImplemented();
- }
- }
-
- private GoogleMapController mapsController(MethodCall call) {
- final long id = Convert.toLong(call.argument("map"));
- final GoogleMapController controller = googleMaps.get(id);
- if (controller == null) {
- throw new IllegalArgumentException("Unknown map: " + id);
- }
- return controller;
+ registrar
+ .platformViewRegistry()
+ .registerViewFactory(
+ "plugins.flutter.io/google_maps", new GoogleMapFactory(plugin.state, registrar));
}
@Override
diff --git a/packages/google_maps_flutter/example/lib/animate_camera.dart b/packages/google_maps_flutter/example/lib/animate_camera.dart
index 0a7bfe1..2337c08 100644
--- a/packages/google_maps_flutter/example/lib/animate_camera.dart
+++ b/packages/google_maps_flutter/example/lib/animate_camera.dart
@@ -12,8 +12,23 @@
: super(const Icon(Icons.map), 'Camera control, animated');
@override
- final GoogleMapOverlayController controller =
- GoogleMapOverlayController.fromSize(width: 300.0, height: 200.0);
+ Widget build(BuildContext context) {
+ return const AnimateCamera();
+ }
+}
+
+class AnimateCamera extends StatefulWidget {
+ const AnimateCamera();
+ @override
+ State createState() => AnimateCameraState();
+}
+
+class AnimateCameraState extends State<AnimateCamera> {
+ GoogleMapController mapController;
+
+ void _onMapCreated(GoogleMapController controller) {
+ mapController = controller;
+ }
@override
Widget build(BuildContext context) {
@@ -21,7 +36,14 @@
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
- Center(child: GoogleMapOverlay(controller: controller)),
+ Center(
+ child: new SizedBox(
+ width: 300.0,
+ height: 200.0,
+ child: GoogleMap(
+ onMapCreated: _onMapCreated,
+ options: GoogleMapOptions.defaultOptions)),
+ ),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
@@ -29,7 +51,7 @@
children: <Widget>[
FlatButton(
onPressed: () {
- controller.mapController.animateCamera(
+ mapController.animateCamera(
CameraUpdate.newCameraPosition(
const CameraPosition(
bearing: 270.0,
@@ -44,7 +66,7 @@
),
FlatButton(
onPressed: () {
- controller.mapController.animateCamera(
+ mapController.animateCamera(
CameraUpdate.newLatLng(
const LatLng(56.1725505, 10.1850512),
),
@@ -54,7 +76,7 @@
),
FlatButton(
onPressed: () {
- controller.mapController.animateCamera(
+ mapController.animateCamera(
CameraUpdate.newLatLngBounds(
LatLngBounds(
southwest: const LatLng(-38.483935, 113.248673),
@@ -68,7 +90,7 @@
),
FlatButton(
onPressed: () {
- controller.mapController.animateCamera(
+ mapController.animateCamera(
CameraUpdate.newLatLngZoom(
const LatLng(37.4231613, -122.087159),
11.0,
@@ -79,7 +101,7 @@
),
FlatButton(
onPressed: () {
- controller.mapController.animateCamera(
+ mapController.animateCamera(
CameraUpdate.scrollBy(150.0, -225.0),
);
},
@@ -91,7 +113,7 @@
children: <Widget>[
FlatButton(
onPressed: () {
- controller.mapController.animateCamera(
+ mapController.animateCamera(
CameraUpdate.zoomBy(
-0.5,
const Offset(30.0, 20.0),
@@ -102,7 +124,7 @@
),
FlatButton(
onPressed: () {
- controller.mapController.animateCamera(
+ mapController.animateCamera(
CameraUpdate.zoomBy(-0.5),
);
},
@@ -110,7 +132,7 @@
),
FlatButton(
onPressed: () {
- controller.mapController.animateCamera(
+ mapController.animateCamera(
CameraUpdate.zoomIn(),
);
},
@@ -118,7 +140,7 @@
),
FlatButton(
onPressed: () {
- controller.mapController.animateCamera(
+ mapController.animateCamera(
CameraUpdate.zoomOut(),
);
},
@@ -126,7 +148,7 @@
),
FlatButton(
onPressed: () {
- controller.mapController.animateCamera(
+ mapController.animateCamera(
CameraUpdate.zoomTo(16.0),
);
},
diff --git a/packages/google_maps_flutter/example/lib/main.dart b/packages/google_maps_flutter/example/lib/main.dart
index e8161b6..eab390c 100644
--- a/packages/google_maps_flutter/example/lib/main.dart
+++ b/packages/google_maps_flutter/example/lib/main.dart
@@ -3,7 +3,6 @@
// found in the LICENSE file.
import 'package:flutter/material.dart';
-import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'animate_camera.dart';
import 'map_ui.dart';
import 'move_camera.dart';
@@ -43,10 +42,6 @@
}
void main() {
- GoogleMapController.init();
final List<NavigatorObserver> observers = <NavigatorObserver>[];
- for (Page p in _allPages) {
- observers.add(p.controller.overlayController);
- }
runApp(MaterialApp(home: MapsDemo(), navigatorObservers: observers));
}
diff --git a/packages/google_maps_flutter/example/lib/map_ui.dart b/packages/google_maps_flutter/example/lib/map_ui.dart
index be3d7f6..0e0c739 100644
--- a/packages/google_maps_flutter/example/lib/map_ui.dart
+++ b/packages/google_maps_flutter/example/lib/map_ui.dart
@@ -16,48 +16,36 @@
MapUiPage() : super(const Icon(Icons.map), 'User interface');
@override
- final GoogleMapOverlayController controller =
- GoogleMapOverlayController.fromSize(
- width: 300.0,
- height: 200.0,
- options: GoogleMapOptions(
- cameraPosition: const CameraPosition(
- target: LatLng(-33.852, 151.211),
- zoom: 11.0,
- ),
- trackCameraPosition: true,
- ),
- );
-
- @override
Widget build(BuildContext context) {
- return MapUiBody(controller);
+ return const MapUiBody();
}
}
class MapUiBody extends StatefulWidget {
- final GoogleMapOverlayController controller;
-
- const MapUiBody(this.controller);
+ const MapUiBody();
@override
- State<StatefulWidget> createState() =>
- MapUiBodyState(controller.mapController);
+ State<StatefulWidget> createState() => MapUiBodyState();
}
class MapUiBodyState extends State<MapUiBody> {
- MapUiBodyState(this.mapController);
+ MapUiBodyState();
- final GoogleMapController mapController;
+ GoogleMapController mapController;
CameraPosition _position;
- GoogleMapOptions _options;
- bool _isMoving;
+ GoogleMapOptions _options = GoogleMapOptions(
+ cameraPosition: const CameraPosition(
+ target: LatLng(-33.852, 151.211),
+ zoom: 11.0,
+ ),
+ trackCameraPosition: true,
+ compassEnabled: true,
+ );
+ bool _isMoving = false;
@override
void initState() {
super.initState();
- mapController.addListener(_onMapChanged);
- _extractMapInfo();
}
void _onMapChanged() {
@@ -196,36 +184,64 @@
@override
Widget build(BuildContext context) {
+ final List<Widget> columnChildren = <Widget>[
+ Padding(
+ padding: const EdgeInsets.all(10.0),
+ child: Center(
+ child: new SizedBox(
+ width: 300.0,
+ height: 200.0,
+ child: GoogleMap(
+ onMapCreated: onMapCreated,
+ options: GoogleMapOptions(
+ cameraPosition: const CameraPosition(
+ target: LatLng(-33.852, 151.211),
+ zoom: 11.0,
+ ),
+ trackCameraPosition: true,
+ ),
+ ),
+ ),
+ ),
+ ),
+ ];
+
+ if (mapController != null) {
+ columnChildren.add(
+ new Expanded(
+ child: ListView(
+ children: <Widget>[
+ Text('camera bearing: ${_position.bearing}'),
+ Text(
+ 'camera target: ${_position.target.latitude.toStringAsFixed(4)},'
+ '${_position.target.longitude.toStringAsFixed(4)}'),
+ Text('camera zoom: ${_position.zoom}'),
+ Text('camera tilt: ${_position.tilt}'),
+ Text(_isMoving ? '(Camera moving)' : '(Camera idle)'),
+ _compassToggler(),
+ _latLngBoundsToggler(),
+ _mapTypeCycler(),
+ _zoomBoundsToggler(),
+ _rotateToggler(),
+ _scrollToggler(),
+ _tiltToggler(),
+ _zoomToggler(),
+ ],
+ ),
+ ),
+ );
+ }
return Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.stretch,
- children: <Widget>[
- Padding(
- padding: const EdgeInsets.all(10.0),
- child: Center(
- child: GoogleMapOverlay(controller: widget.controller),
- ),
- ),
- Column(
- children: <Widget>[
- Text('camera bearing: ${_position.bearing}'),
- Text(
- 'camera target: ${_position.target.latitude.toStringAsFixed(4)},'
- '${_position.target.longitude.toStringAsFixed(4)}'),
- Text('camera zoom: ${_position.zoom}'),
- Text('camera tilt: ${_position.tilt}'),
- Text(_isMoving ? '(Camera moving)' : '(Camera idle)'),
- _compassToggler(),
- _latLngBoundsToggler(),
- _mapTypeCycler(),
- _zoomBoundsToggler(),
- _rotateToggler(),
- _scrollToggler(),
- _tiltToggler(),
- _zoomToggler(),
- ],
- ),
- ],
+ children: columnChildren,
);
}
+
+ void onMapCreated(GoogleMapController controller) {
+ mapController = controller;
+ mapController.addListener(_onMapChanged);
+ _extractMapInfo();
+ setState(() {});
+ }
}
diff --git a/packages/google_maps_flutter/example/lib/move_camera.dart b/packages/google_maps_flutter/example/lib/move_camera.dart
index 8160495..491ce3b 100644
--- a/packages/google_maps_flutter/example/lib/move_camera.dart
+++ b/packages/google_maps_flutter/example/lib/move_camera.dart
@@ -11,8 +11,23 @@
MoveCameraPage() : super(const Icon(Icons.map), 'Camera control');
@override
- final GoogleMapOverlayController controller =
- GoogleMapOverlayController.fromSize(width: 300.0, height: 200.0);
+ Widget build(BuildContext context) {
+ return const MoveCamera();
+ }
+}
+
+class MoveCamera extends StatefulWidget {
+ const MoveCamera();
+ @override
+ State createState() => new MoveCameraState();
+}
+
+class MoveCameraState extends State<MoveCamera> {
+ GoogleMapController mapController;
+
+ void _onMapCreated(GoogleMapController controller) {
+ mapController = controller;
+ }
@override
Widget build(BuildContext context) {
@@ -20,7 +35,14 @@
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
- Center(child: GoogleMapOverlay(controller: controller)),
+ Center(
+ child: new SizedBox(
+ width: 300.0,
+ height: 200.0,
+ child: new GoogleMap(
+ onMapCreated: _onMapCreated,
+ options: GoogleMapOptions.defaultOptions)),
+ ),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
@@ -28,7 +50,7 @@
children: <Widget>[
FlatButton(
onPressed: () {
- controller.mapController.moveCamera(
+ mapController.moveCamera(
CameraUpdate.newCameraPosition(
const CameraPosition(
bearing: 270.0,
@@ -43,7 +65,7 @@
),
FlatButton(
onPressed: () {
- controller.mapController.moveCamera(
+ mapController.moveCamera(
CameraUpdate.newLatLng(
const LatLng(56.1725505, 10.1850512),
),
@@ -53,7 +75,7 @@
),
FlatButton(
onPressed: () {
- controller.mapController.moveCamera(
+ mapController.moveCamera(
CameraUpdate.newLatLngBounds(
LatLngBounds(
southwest: const LatLng(-38.483935, 113.248673),
@@ -67,7 +89,7 @@
),
FlatButton(
onPressed: () {
- controller.mapController.moveCamera(
+ mapController.moveCamera(
CameraUpdate.newLatLngZoom(
const LatLng(37.4231613, -122.087159),
11.0,
@@ -78,7 +100,7 @@
),
FlatButton(
onPressed: () {
- controller.mapController.moveCamera(
+ mapController.moveCamera(
CameraUpdate.scrollBy(150.0, -225.0),
);
},
@@ -90,7 +112,7 @@
children: <Widget>[
FlatButton(
onPressed: () {
- controller.mapController.moveCamera(
+ mapController.moveCamera(
CameraUpdate.zoomBy(
-0.5,
const Offset(30.0, 20.0),
@@ -101,7 +123,7 @@
),
FlatButton(
onPressed: () {
- controller.mapController.moveCamera(
+ mapController.moveCamera(
CameraUpdate.zoomBy(-0.5),
);
},
@@ -109,7 +131,7 @@
),
FlatButton(
onPressed: () {
- controller.mapController.moveCamera(
+ mapController.moveCamera(
CameraUpdate.zoomIn(),
);
},
@@ -117,7 +139,7 @@
),
FlatButton(
onPressed: () {
- controller.mapController.moveCamera(
+ mapController.moveCamera(
CameraUpdate.zoomOut(),
);
},
@@ -125,7 +147,7 @@
),
FlatButton(
onPressed: () {
- controller.mapController.moveCamera(
+ mapController.moveCamera(
CameraUpdate.zoomTo(16.0),
);
},
diff --git a/packages/google_maps_flutter/example/lib/page.dart b/packages/google_maps_flutter/example/lib/page.dart
index 477e17a..c9f834b 100644
--- a/packages/google_maps_flutter/example/lib/page.dart
+++ b/packages/google_maps_flutter/example/lib/page.dart
@@ -3,13 +3,10 @@
// found in the LICENSE file.
import 'package:flutter/material.dart';
-import 'package:google_maps_flutter/google_maps_flutter.dart';
abstract class Page extends StatelessWidget {
const Page(this.leading, this.title);
final Widget leading;
final String title;
-
- GoogleMapOverlayController get controller;
}
diff --git a/packages/google_maps_flutter/example/lib/place_marker.dart b/packages/google_maps_flutter/example/lib/place_marker.dart
index 27c2752..05b2cac 100644
--- a/packages/google_maps_flutter/example/lib/place_marker.dart
+++ b/packages/google_maps_flutter/example/lib/place_marker.dart
@@ -13,53 +13,35 @@
PlaceMarkerPage() : super(const Icon(Icons.place), 'Place marker');
@override
- final GoogleMapOverlayController controller =
- GoogleMapOverlayController.fromSize(
- width: 300.0,
- height: 200.0,
- options: GoogleMapOptions(
- cameraPosition: const CameraPosition(
- target: LatLng(-33.852, 151.211),
- zoom: 11.0,
- ),
- ),
- );
-
- @override
Widget build(BuildContext context) {
- return PlaceMarkerBody(controller);
+ return const PlaceMarkerBody();
}
}
class PlaceMarkerBody extends StatefulWidget {
- final GoogleMapOverlayController controller;
-
- const PlaceMarkerBody(this.controller);
+ const PlaceMarkerBody();
@override
- State<StatefulWidget> createState() {
- return PlaceMarkerBodyState(controller.mapController);
- }
+ State<StatefulWidget> createState() => PlaceMarkerBodyState();
}
class PlaceMarkerBodyState extends State<PlaceMarkerBody> {
static final LatLng center = const LatLng(-33.86711, 151.1947171);
- PlaceMarkerBodyState(this.controller);
+ PlaceMarkerBodyState();
- final GoogleMapController controller;
+ GoogleMapController controller;
int _markerCount = 0;
Marker _selectedMarker;
- @override
- void initState() {
- super.initState();
+ void _onMapCreated(GoogleMapController controller) {
+ this.controller = controller;
controller.onMarkerTapped.add(_onMarkerTapped);
}
@override
void dispose() {
- controller.onMarkerTapped.remove(_onMarkerTapped);
+ controller?.onMarkerTapped?.remove(_onMarkerTapped);
super.dispose();
}
@@ -187,80 +169,104 @@
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
- Center(child: GoogleMapOverlay(controller: widget.controller)),
- Row(
- mainAxisAlignment: MainAxisAlignment.spaceEvenly,
- children: <Widget>[
- Row(
+ Center(
+ child: new SizedBox(
+ width: 300.0,
+ height: 200.0,
+ child: GoogleMap(
+ onMapCreated: _onMapCreated,
+ options: new GoogleMapOptions(
+ cameraPosition: const CameraPosition(
+ target: LatLng(-33.852, 151.211),
+ zoom: 11.0,
+ ),
+ ),
+ ),
+ ),
+ ),
+ Expanded(
+ child: SingleChildScrollView(
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
- Column(
+ Row(
children: <Widget>[
- FlatButton(
- child: const Text('add'),
- onPressed: (_markerCount == 12) ? null : _add,
+ Column(
+ children: <Widget>[
+ FlatButton(
+ child: const Text('add'),
+ onPressed: (_markerCount == 12) ? null : _add,
+ ),
+ FlatButton(
+ child: const Text('remove'),
+ onPressed: (_selectedMarker == null) ? null : _remove,
+ ),
+ FlatButton(
+ child: const Text('change info'),
+ onPressed:
+ (_selectedMarker == null) ? null : _changeInfo,
+ ),
+ FlatButton(
+ child: const Text('change info anchor'),
+ onPressed: (_selectedMarker == null)
+ ? null
+ : _changeInfoAnchor,
+ ),
+ ],
),
- FlatButton(
- child: const Text('remove'),
- onPressed: (_selectedMarker == null) ? null : _remove,
- ),
- FlatButton(
- child: const Text('change info'),
- onPressed: (_selectedMarker == null) ? null : _changeInfo,
- ),
- FlatButton(
- child: const Text('change info anchor'),
- onPressed:
- (_selectedMarker == null) ? null : _changeInfoAnchor,
+ Column(
+ children: <Widget>[
+ FlatButton(
+ child: const Text('change alpha'),
+ onPressed:
+ (_selectedMarker == null) ? null : _changeAlpha,
+ ),
+ FlatButton(
+ child: const Text('change anchor'),
+ onPressed:
+ (_selectedMarker == null) ? null : _changeAnchor,
+ ),
+ FlatButton(
+ child: const Text('toggle draggable'),
+ onPressed: (_selectedMarker == null)
+ ? null
+ : _toggleDraggable,
+ ),
+ FlatButton(
+ child: const Text('toggle flat'),
+ onPressed:
+ (_selectedMarker == null) ? null : _toggleFlat,
+ ),
+ FlatButton(
+ child: const Text('change position'),
+ onPressed: (_selectedMarker == null)
+ ? null
+ : _changePosition,
+ ),
+ FlatButton(
+ child: const Text('change rotation'),
+ onPressed: (_selectedMarker == null)
+ ? null
+ : _changeRotation,
+ ),
+ FlatButton(
+ child: const Text('toggle visible'),
+ onPressed:
+ (_selectedMarker == null) ? null : _toggleVisible,
+ ),
+ FlatButton(
+ child: const Text('change zIndex'),
+ onPressed:
+ (_selectedMarker == null) ? null : _changeZIndex,
+ ),
+ ],
),
],
- ),
- Column(
- children: <Widget>[
- FlatButton(
- child: const Text('change alpha'),
- onPressed:
- (_selectedMarker == null) ? null : _changeAlpha,
- ),
- FlatButton(
- child: const Text('change anchor'),
- onPressed:
- (_selectedMarker == null) ? null : _changeAnchor,
- ),
- FlatButton(
- child: const Text('toggle draggable'),
- onPressed:
- (_selectedMarker == null) ? null : _toggleDraggable,
- ),
- FlatButton(
- child: const Text('toggle flat'),
- onPressed: (_selectedMarker == null) ? null : _toggleFlat,
- ),
- FlatButton(
- child: const Text('change position'),
- onPressed:
- (_selectedMarker == null) ? null : _changePosition,
- ),
- FlatButton(
- child: const Text('change rotation'),
- onPressed:
- (_selectedMarker == null) ? null : _changeRotation,
- ),
- FlatButton(
- child: const Text('toggle visible'),
- onPressed:
- (_selectedMarker == null) ? null : _toggleVisible,
- ),
- FlatButton(
- child: const Text('change zIndex'),
- onPressed:
- (_selectedMarker == null) ? null : _changeZIndex,
- ),
- ],
- ),
+ )
],
- )
- ],
- )
+ ),
+ ),
+ ),
],
);
}
diff --git a/packages/google_maps_flutter/lib/google_maps_flutter.dart b/packages/google_maps_flutter/lib/google_maps_flutter.dart
index 8f98368..7815417 100644
--- a/packages/google_maps_flutter/lib/google_maps_flutter.dart
+++ b/packages/google_maps_flutter/lib/google_maps_flutter.dart
@@ -9,7 +9,6 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
-import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart';
export 'dart:async';
@@ -18,7 +17,7 @@
part 'src/callbacks.dart';
part 'src/camera.dart';
part 'src/controller.dart';
+part 'src/google_map.dart';
part 'src/marker.dart';
part 'src/location.dart';
-part 'src/platform_overlay.dart';
part 'src/ui.dart';
diff --git a/packages/google_maps_flutter/lib/src/controller.dart b/packages/google_maps_flutter/lib/src/controller.dart
index 80b8574..20549f1 100644
--- a/packages/google_maps_flutter/lib/src/controller.dart
+++ b/packages/google_maps_flutter/lib/src/controller.dart
@@ -4,9 +4,6 @@
part of google_maps_flutter;
-final MethodChannel _channel =
- const MethodChannel('plugins.flutter.io/google_maps');
-
/// Controller for a single GoogleMap instance running on the host platform.
///
/// Change listeners are notified upon changes to any of
@@ -20,20 +17,33 @@
///
/// Marker tap events can be received by adding callbacks to [onMarkerTapped].
class GoogleMapController extends ChangeNotifier {
- @visibleForTesting
- GoogleMapController(this._id, GoogleMapOptions options)
+ GoogleMapController._(
+ this._id, GoogleMapOptions options, MethodChannel channel)
: assert(_id != null),
assert(options != null),
assert(options.cameraPosition != null),
- _options = options {
- _id.then((int id) {
- _controllers[id] = this;
- });
+ assert(channel != null),
+ _channel = channel {
if (options.trackCameraPosition) {
_cameraPosition = options.cameraPosition;
}
+ _channel.setMethodCallHandler(_handleMethodCall);
+ _options = GoogleMapOptions.defaultOptions.copyWith(options);
}
+ static Future<GoogleMapController> init(
+ int id, GoogleMapOptions options) async {
+ assert(id != null);
+ assert(options != null);
+ assert(options.cameraPosition != null);
+ final MethodChannel channel =
+ new MethodChannel('plugins.flutter.io/google_maps_$id');
+ await channel.invokeMethod('map#waitForMap');
+ return GoogleMapController._(id, options, channel);
+ }
+
+ final MethodChannel _channel;
+
/// Callbacks to receive tap events for markers placed on this map.
final ArgumentCallbacks<Marker> onMarkerTapped =
new ArgumentCallbacks<Marker>();
@@ -63,28 +73,9 @@
CameraPosition get cameraPosition => _cameraPosition;
CameraPosition _cameraPosition;
- final Future<int> _id;
+ final int _id;
- static Map<int, GoogleMapController> _controllers =
- <int, GoogleMapController>{};
-
- /// Initializes the GoogleMaps plugin. Should be called from the Flutter
- /// application's main entry point.
- // Clears any existing platform-side map instances after hot restart.
- // Sets up method call handlers for receiving map events.
- static Future<void> init() async {
- await _channel.invokeMethod('init');
- _controllers.clear();
- _channel.setMethodCallHandler((MethodCall call) async {
- final int mapId = call.arguments['map'];
- final GoogleMapController controller = _controllers[mapId];
- if (controller != null) {
- controller._handleMethodCall(call);
- }
- });
- }
-
- void _handleMethodCall(MethodCall call) {
+ Future<void> _handleMethodCall(MethodCall call) async {
switch (call.method) {
case 'infoWindow#onTap':
final String markerId = call.arguments['marker'];
@@ -126,11 +117,9 @@
/// The returned [Future] completes after listeners have been notified.
Future<void> updateMapOptions(GoogleMapOptions changes) async {
assert(changes != null);
- final int id = await _id;
final dynamic json = await _channel.invokeMethod(
'map#update',
<String, dynamic>{
- 'map': id,
'options': changes._toJson(),
},
);
@@ -144,9 +133,7 @@
/// The returned [Future] completes after the change has been started on the
/// platform side.
Future<void> animateCamera(CameraUpdate cameraUpdate) async {
- final int id = await _id;
await _channel.invokeMethod('camera#animate', <String, dynamic>{
- 'map': id,
'cameraUpdate': cameraUpdate._toJson(),
});
}
@@ -156,9 +143,7 @@
/// The returned [Future] completes after the change has been made on the
/// platform side.
Future<void> moveCamera(CameraUpdate cameraUpdate) async {
- final int id = await _id;
await _channel.invokeMethod('camera#move', <String, dynamic>{
- 'map': id,
'cameraUpdate': cameraUpdate._toJson(),
});
}
@@ -171,13 +156,11 @@
/// The returned [Future] completes with the added marker once listeners have
/// been notified.
Future<Marker> addMarker(MarkerOptions options) async {
- final int id = await _id;
final MarkerOptions effectiveOptions =
MarkerOptions.defaultOptions.copyWith(options);
final String markerId = await _channel.invokeMethod(
'marker#add',
<String, dynamic>{
- 'map': id,
'options': effectiveOptions._toJson(),
},
);
@@ -198,9 +181,7 @@
assert(marker != null);
assert(_markers[marker._id] == marker);
assert(changes != null);
- final int id = await _id;
await _channel.invokeMethod('marker#update', <String, dynamic>{
- 'map': id,
'marker': marker._id,
'options': changes._toJson(),
});
@@ -218,157 +199,10 @@
Future<void> removeMarker(Marker marker) async {
assert(marker != null);
assert(_markers[marker._id] == marker);
- final int id = await _id;
await _channel.invokeMethod('marker#remove', <String, dynamic>{
- 'map': id,
'marker': marker._id,
});
_markers.remove(marker._id);
notifyListeners();
}
}
-
-/// Controller pair for a GoogleMap instance that is integrated as a
-/// platform overlay.
-///
-/// The [mapController] programmatically controls the platform GoogleMap view
-/// and supports event handling.
-///
-/// The [overlayController] is used to hide and show the platform overlay at
-/// appropriate times to avoid rendering artifacts when the necessary conditions
-/// for correctly displaying a platform overlay are not met: the underlying
-/// widget must be stationary and rendered on top of all other widgets within
-/// bounds.
-///
-/// *Warning*: Platform overlays cannot be freely composed with
-/// other widgets. See [PlatformOverlayController] for caveats and
-/// limitations.
-class GoogleMapOverlayController {
- GoogleMapOverlayController._(this.mapController, this.overlayController);
-
- /// Creates a controller for a GoogleMaps of the specified size and with the
- /// specified custom [options], if any.
- factory GoogleMapOverlayController.fromSize({
- @required double width,
- @required double height,
- GoogleMapOptions options,
- }) {
- assert(width != null);
- assert(height != null);
- final GoogleMapOptions effectiveOptions =
- GoogleMapOptions.defaultOptions.copyWith(options);
- final _GoogleMapsPlatformOverlay overlay =
- new _GoogleMapsPlatformOverlay(effectiveOptions);
- return new GoogleMapOverlayController._(
- new GoogleMapController(overlay._textureId.future, effectiveOptions),
- new PlatformOverlayController(width, height, overlay),
- );
- }
-
- /// The controller of the GoogleMaps instance.
- final GoogleMapController mapController;
-
- /// The controller of the platform overlay.
- final PlatformOverlayController overlayController;
-
- void dispose() {
- overlayController.dispose();
- }
-}
-
-class _GoogleMapsPlatformOverlay extends PlatformOverlay {
- _GoogleMapsPlatformOverlay(this.options);
-
- final GoogleMapOptions options;
- Completer<int> _textureId = new Completer<int>();
-
- @override
- Future<int> create(Size size) {
- _textureId.complete(_channel.invokeMethod('map#create', <String, dynamic>{
- 'width': size.width,
- 'height': size.height,
- 'options': options._toJson(),
- }).then<int>((dynamic value) => value));
- return _textureId.future;
- }
-
- @override
- Future<void> show(Offset offset) async {
- final int id = await _textureId.future;
- _channel.invokeMethod('map#show', <String, dynamic>{
- 'map': id,
- 'x': offset.dx,
- 'y': offset.dy,
- });
- }
-
- @override
- Future<void> hide() async {
- final int id = await _textureId.future;
- _channel.invokeMethod('map#hide', <String, dynamic>{
- 'map': id,
- });
- }
-
- @override
- Future<void> dispose() async {
- final int id = await _textureId.future;
- _channel.invokeMethod('map#dispose', <String, dynamic>{
- 'map': id,
- });
- }
-}
-
-/// A widget covered by a GoogleMap platform overlay.
-///
-/// The overlay is intended to be shown only while the map is interactive,
-/// stationary, and the widget is rendered on top of all other widgets. In all
-/// other situations, the overlay should be hidden to avoid rendering artifacts.
-/// While the overlay is hidden, the widget shows a Texture with the most recent
-/// bitmap snapshot extracted from the GoogleMap view. That bitmap will be
-/// slightly delayed compared to the actual platform view which will be visible,
-/// if a map animation is started and the overlay then hidden.
-///
-/// *Warning*: Platform overlays cannot be freely composed with
-/// other widgets. See [PlatformOverlayController] for caveats and
-/// limitations.
-class GoogleMapOverlay extends StatefulWidget {
- final GoogleMapOverlayController controller;
-
- GoogleMapOverlay({Key key, @required this.controller}) : super(key: key);
-
- @override
- State<StatefulWidget> createState() => new _GoogleMapOverlayState();
-}
-
-class _GoogleMapOverlayState extends State<GoogleMapOverlay> {
- @override
- void initState() {
- super.initState();
- widget.controller.overlayController.attachTo(context);
- }
-
- @override
- void dispose() {
- widget.controller.overlayController.detach();
- super.dispose();
- }
-
- @override
- Widget build(BuildContext context) {
- return new SizedBox(
- child: new FutureBuilder<int>(
- future: widget.controller.mapController._id,
- builder: (_, AsyncSnapshot<int> snapshot) {
- if (snapshot.hasData) {
- return new Texture(textureId: snapshot.data);
- } else {
- return new Container();
- }
- },
- ),
- width: widget.controller.overlayController.width,
- height: widget.controller.overlayController.height,
- );
- }
-}
diff --git a/packages/google_maps_flutter/lib/src/google_map.dart b/packages/google_maps_flutter/lib/src/google_map.dart
new file mode 100644
index 0000000..22d0bdc
--- /dev/null
+++ b/packages/google_maps_flutter/lib/src/google_map.dart
@@ -0,0 +1,37 @@
+part of google_maps_flutter;
+
+typedef void MapCreatedCallback(GoogleMapController controller);
+
+class GoogleMap extends StatefulWidget {
+ GoogleMap({@required this.onMapCreated, GoogleMapOptions options})
+ : this.options = GoogleMapOptions.defaultOptions.copyWith(options);
+
+ final MapCreatedCallback onMapCreated;
+ final GoogleMapOptions options;
+
+ @override
+ State createState() => new _GoogleMapState();
+}
+
+class _GoogleMapState extends State<GoogleMap> {
+ @override
+ Widget build(BuildContext context) {
+ if (defaultTargetPlatform == TargetPlatform.android) {
+ return AndroidView(
+ viewType: 'plugins.flutter.io/google_maps',
+ onPlatformViewCreated: onPlatformViewCreated,
+ creationParams: widget.options._toJson(),
+ creationParamsCodec: const StandardMessageCodec(),
+ );
+ }
+
+ return new Text(
+ '$defaultTargetPlatform is not yet supported by the maps plugin');
+ }
+
+ Future<void> onPlatformViewCreated(int id) async {
+ final GoogleMapController controller =
+ await GoogleMapController.init(id, widget.options);
+ widget.onMapCreated(controller);
+ }
+}
diff --git a/packages/google_maps_flutter/lib/src/platform_overlay.dart b/packages/google_maps_flutter/lib/src/platform_overlay.dart
deleted file mode 100644
index 5f61e30..0000000
--- a/packages/google_maps_flutter/lib/src/platform_overlay.dart
+++ /dev/null
@@ -1,243 +0,0 @@
-// Copyright 2018 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.
-
-part of google_maps_flutter;
-
-/// Controller of platform overlays used for creating the illusion, in *very
-/// limited situations*, of in-line compositing of platform views with Flutter
-/// widgets.
-///
-/// Platform overlays are normal platform views that are displayed on top of the
-/// Flutter view when so directed by the Flutter app's Dart code. The platform
-/// overlay is placed on top of a [Texture] widget acting as a non-interactive
-/// stand in while the conditions for correctly displaying the overlay are not
-/// met. Those conditions are:
-///
-/// * the widget must be stationary
-/// * the widget must be rendered on top of all other widgets within bounds
-/// * touch events originating within the widget's bounds can be safely ignored
-/// by Flutter code (they will be intercepted by the platform overlay)
-///
-/// These conditions severely restrict the contexts in which platform overlays
-/// can be used. Worse, there is no easy way of learning if a given widget
-/// currently satisfies those conditions, so they must be explicitly enforced
-/// by the app author. Examples include avoiding placing the widget on a
-/// scrollable view; hiding the overlay during animated transitions or while a
-/// drawer is being shown on top; avoiding placing the widget at the edge of
-/// the screen where the platform view would interfere with edge swipes; etc.
-/// The app author should expect little help from existing widgets in this
-/// endeavor; some widgets (Material Scaffold being a prime example) do not
-/// offer to notify clients before and after they display Flutter overlays or
-/// animate to new configurations. Using platform overlays may require custom
-/// implementations of such widgets.
-///
-/// *Warning*: Platform overlays cannot be freely composed with other widgets.
-///
-/// For the above reasons, *the use of platform overlays is generally
-/// discouraged*. Still, overlays provide an interim solution in situations
-/// where one wants to create the illusion of in-line compositing of native
-/// platform views (such as GoogleMaps) for which no API exists for connecting a
-/// Texture widget directly to the native OpenGL rendering pipeline.
-///
-/// Overlays may be attached to the [BuildContext] in which the Texture widget
-/// is built and are then automatically hidden when the ambient ModalRoute (if
-/// any) is not on top of the navigator stack. This is currently the *only*
-/// built-in mechanism for helping the app author ensure that the overlay
-/// conditions mentioned above are met. Making use of this mechanism requires
-/// the overlay controller to be added as an observer of the main Navigator.
-class PlatformOverlayController extends NavigatorObserver
- with WidgetsBindingObserver {
- final double width;
- final double height;
- final PlatformOverlay overlay;
- final Completer<int> _overlayIdCompleter = new Completer<int>();
- BuildContext _context;
-
- // Current route as observed via NavigatorObserver calls.
- Route<dynamic> _currentRoute;
-
- // Previous route as observed via NavigatorObserver calls.
- Route<dynamic> _previousRoute;
-
- // Current route at the most recent time [attachTo] was called.
- Route<dynamic> _routeWithOverlay;
-
- // Number of calls to [activateOverlay] minus number of calls to
- // [deactivateOverlay].
- int _activationCount = 0;
-
- // True if [deactivateOverlay] has been called due to another route
- // having been pushed atop [_routeWithOverlay].
- bool _deactivatedByPush = false;
-
- // True if [dispose] has been called.
- bool _disposed = false;
-
- PlatformOverlayController(this.width, this.height, this.overlay);
-
- void attachTo(BuildContext context) {
- _context = context;
- _routeWithOverlay = _currentRoute;
- _activateOverlayAfterPushAnimations(_routeWithOverlay, _previousRoute);
- WidgetsBinding.instance.addObserver(this);
- SchedulerBinding.instance.addPostFrameCallback((_) {
- if (_disposed) {
- return;
- }
- if (!_overlayIdCompleter.isCompleted) {
- _overlayIdCompleter.complete(overlay.create(new Size(width, height)));
- }
- });
- }
-
- void detach() {
- WidgetsBinding.instance.removeObserver(this);
- _context = null;
- _routeWithOverlay = null;
- }
-
- /// Allow activating the overlay, unless there are other pending calls to
- /// [deactivateOverlay].
- void activateOverlay() {
- assert(_activationCount <= 0);
- _activationCount += 1;
- if (_activationCount == 1) {
- SchedulerBinding.instance.addPostFrameCallback((_) {
- if (_disposed) {
- return;
- }
- final RenderObject object = _context?.findRenderObject();
- Offset offset;
- if (object is RenderBox) {
- offset = object.localToGlobal(Offset.zero);
- } else {
- offset = Offset.zero;
- }
- overlay.show(offset);
- });
- }
- }
-
- /// Prevent activating the overlay until a matching call to [activateOverlay].
- void deactivateOverlay() {
- _activationCount -= 1;
- if (_activationCount == 0) {
- overlay.hide();
- }
- }
-
- @override
- void didPush(Route<dynamic> route, Route<dynamic> previousRoute) {
- _currentRoute = route;
- _previousRoute = previousRoute;
- if (previousRoute != null && identical(previousRoute, _routeWithOverlay)) {
- deactivateOverlay();
- _deactivatedByPush = true;
- }
- }
-
- @override
- void didPop(Route<dynamic> route, Route<dynamic> previousRoute) {
- _currentRoute = previousRoute;
- _previousRoute = route;
- if (identical(route, _routeWithOverlay)) {
- deactivateOverlay();
- } else if (identical(previousRoute, _routeWithOverlay) &&
- _deactivatedByPush) {
- _activateOverlayAfterPopAnimations(route, previousRoute);
- }
- }
-
- void _activateOverlayAfterPopAnimations(
- Route<dynamic> route, Route<dynamic> previousRoute) {
- if (route is ModalRoute && previousRoute is ModalRoute) {
- _doOnceAfter(route.animation, () {
- _doOnceAfter(previousRoute.secondaryAnimation, () {
- activateOverlay();
- _deactivatedByPush = false;
- });
- });
- } else if (route is ModalRoute) {
- _doOnceAfter(route.animation, () {
- activateOverlay();
- _deactivatedByPush = false;
- });
- } else if (previousRoute is ModalRoute) {
- _doOnceAfter(previousRoute.secondaryAnimation, () {
- activateOverlay();
- _deactivatedByPush = false;
- });
- }
- }
-
- void _activateOverlayAfterPushAnimations(
- Route<dynamic> route, Route<dynamic> previousRoute) {
- if (route is ModalRoute && previousRoute is ModalRoute) {
- _doOnceAfter(route.animation, () {
- _doOnceAfter(previousRoute.secondaryAnimation, () {
- activateOverlay();
- });
- });
- } else if (route is ModalRoute) {
- _doOnceAfter(route.animation, () {
- activateOverlay();
- });
- } else if (previousRoute is ModalRoute) {
- _doOnceAfter(previousRoute.secondaryAnimation, () {
- activateOverlay();
- });
- }
- }
-
- void _doOnceAfter(Animation<dynamic> animation, void onDone()) {
- void listener() {
- if (animation.status == AnimationStatus.completed ||
- animation.status == AnimationStatus.dismissed) {
- animation.removeListener(listener);
- onDone();
- }
- }
-
- if (animation.status == AnimationStatus.forward ||
- animation.status == AnimationStatus.reverse) {
- animation.addListener(listener);
- } else {
- onDone();
- }
- }
-
- void dispose() {
- if (!_disposed) {
- overlay.dispose();
- _disposed = true;
- }
- }
-
- @override
- void didChangeMetrics() {
- super.didChangeMetrics();
- deactivateOverlay();
- activateOverlay();
- }
-}
-
-/// Interface of platform overlay implementations. Typical implementation use
-/// a [MethodChannel] to communicate with platform-specific code and have it
-/// manage a collection of related platform overlays.
-abstract class PlatformOverlay {
- /// Creates a platform view of the specified [size].
- ///
- /// The platform view should remain hidden until explicitly shown by calling
- /// [show].
- Future<int> create(Size size);
-
- /// Shows the platform view at the specified [offset].
- Future<void> show(Offset offset);
-
- /// Hides the platform view.
- Future<void> hide();
-
- /// Disposes of the platform view.
- Future<void> dispose();
-}