API documentation and cleanup (#545)

diff --git a/packages/google_maps_flutter/README.md b/packages/google_maps_flutter/README.md
index 0302726..7b9b5f7 100644
--- a/packages/google_maps_flutter/README.md
+++ b/packages/google_maps_flutter/README.md
@@ -2,13 +2,45 @@
 
 [![pub package](https://img.shields.io/pub/v/google_maps_flutter.svg)](https://pub.dartlang.org/packages/google_maps_flutter)
 
-A Flutter plugin to use [Google Maps](https://developers.google.com/maps/) for iOS and Android.
+A Flutter plugin to use [Google Maps](https://developers.google.com/maps/) in
+iOS and Android apps.
 
-*Note*: This plugin is currently a stub and under active development.
+## Caveat
+
+This plugin provides an *unpublished preview* of the Flutter API for GoogleMaps:
+* Dart APIs for controlling and interacting with a GoogleMap view from Flutter
+  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.
 
 ## Usage
 
-To use this plugin, add `google_maps_flutter` as a [dependency in your pubspec.yaml file](https://flutter.io/platform-plugins/).
+To use this plugin, add
+```yaml
+ google_maps_flutter:
+   git:
+     url: git://github.com/flutter/plugins
+     path: packages/google_maps_flutter
+```
+as a [dependency in your pubspec.yaml file](https://flutter.io/platform-plugins/).
 
 ## Getting Started
 
diff --git a/packages/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java b/packages/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java
index 0ee9d40..de2023e 100644
--- a/packages/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java
+++ b/packages/google_maps_flutter/android/src/main/java/io/flutter/plugins/googlemaps/Convert.java
@@ -104,6 +104,9 @@
   }
 
   static Object toJson(CameraPosition position) {
+    if (position == null) {
+      return null;
+    }
     final Map<String, Object> data = new HashMap<>();
     data.put("bearing", position.bearing);
     data.put("target", toJson(position.target));
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 13a1862..ef905c3 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
@@ -163,6 +163,10 @@
     googleMap.animateCamera(cameraUpdate);
   }
 
+  CameraPosition getCameraPosition() {
+    return trackCameraPosition ? googleMap.getCameraPosition() : null;
+  }
+
   MarkerBuilder newMarkerBuilder() {
     return new MarkerBuilder(this);
   }
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 692a820..5805aab 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
@@ -65,7 +65,7 @@
           result.success(null);
           break;
         }
-      case "createMap":
+      case "map#create":
         {
           final int width = Convert.toPixels(call.argument("width"), density);
           final int height = Convert.toPixels(call.argument("height"), density);
@@ -82,7 +82,7 @@
                   final Map<String, Object> arguments = new HashMap<>(2);
                   arguments.put("map", controller.id());
                   arguments.put("isGesture", isGesture);
-                  channel.invokeMethod("map#onCameraMoveStarted", arguments);
+                  channel.invokeMethod("camera#onMoveStarted", arguments);
                 }
 
                 @Override
@@ -90,13 +90,13 @@
                   final Map<String, Object> arguments = new HashMap<>(2);
                   arguments.put("map", controller.id());
                   arguments.put("position", Convert.toJson(position));
-                  channel.invokeMethod("map#onCameraMove", arguments);
+                  channel.invokeMethod("camera#onMove", arguments);
                 }
 
                 @Override
                 public void onCameraIdle() {
                   channel.invokeMethod(
-                      "map#onCameraIdle", Collections.singletonMap("map", controller.id()));
+                      "camera#onIdle", Collections.singletonMap("map", controller.id()));
                 }
               });
           controller.setOnMarkerTappedListener(
@@ -113,14 +113,14 @@
           // is ready
           break;
         }
-      case "updateMapOptions":
+      case "map#update":
         {
           final GoogleMapController controller = mapsController(call);
           Convert.interpretGoogleMapOptions(call.argument("options"), controller);
-          result.success(null);
+          result.success(Convert.toJson(controller.getCameraPosition()));
           break;
         }
-      case "moveCamera":
+      case "camera#move":
         {
           final GoogleMapController controller = mapsController(call);
           final CameraUpdate cameraUpdate =
@@ -129,7 +129,7 @@
           result.success(null);
           break;
         }
-      case "animateCamera":
+      case "camera#animate":
         {
           final GoogleMapController controller = mapsController(call);
           final CameraUpdate cameraUpdate =
@@ -138,7 +138,7 @@
           result.success(null);
           break;
         }
-      case "addMarker":
+      case "marker#add":
         {
           final GoogleMapController controller = mapsController(call);
           final MarkerBuilder markerBuilder = controller.newMarkerBuilder();
@@ -164,7 +164,7 @@
           result.success(null);
           break;
         }
-      case "showMapOverlay":
+      case "map#show":
         {
           final GoogleMapController controller = mapsController(call);
           final int x = Convert.toPixels(call.argument("x"), density);
@@ -173,14 +173,14 @@
           result.success(null);
           break;
         }
-      case "hideMapOverlay":
+      case "map#hide":
         {
           final GoogleMapController controller = mapsController(call);
           controller.hideOverlay();
           result.success(null);
           break;
         }
-      case "disposeMap":
+      case "map#dispose":
         {
           final GoogleMapController controller = mapsController(call);
           controller.dispose();
diff --git a/packages/google_maps_flutter/example/lib/animate_camera.dart b/packages/google_maps_flutter/example/lib/animate_camera.dart
index 25a7769..0a7bfe1 100644
--- a/packages/google_maps_flutter/example/lib/animate_camera.dart
+++ b/packages/google_maps_flutter/example/lib/animate_camera.dart
@@ -9,31 +9,31 @@
 
 class AnimateCameraPage extends Page {
   AnimateCameraPage()
-      : super(const Icon(Icons.map), "Camera control, animated");
+      : super(const Icon(Icons.map), 'Camera control, animated');
 
   @override
   final GoogleMapOverlayController controller =
-      new GoogleMapOverlayController.fromSize(width: 300.0, height: 200.0);
+      GoogleMapOverlayController.fromSize(width: 300.0, height: 200.0);
 
   @override
   Widget build(BuildContext context) {
-    return new Column(
+    return Column(
       mainAxisAlignment: MainAxisAlignment.spaceEvenly,
       crossAxisAlignment: CrossAxisAlignment.stretch,
       children: <Widget>[
-        new Center(child: new GoogleMapOverlay(controller: controller)),
-        new Row(
+        Center(child: GoogleMapOverlay(controller: controller)),
+        Row(
           mainAxisAlignment: MainAxisAlignment.spaceEvenly,
           children: <Widget>[
-            new Column(
+            Column(
               children: <Widget>[
-                new FlatButton(
+                FlatButton(
                   onPressed: () {
                     controller.mapController.animateCamera(
                       CameraUpdate.newCameraPosition(
                         const CameraPosition(
                           bearing: 270.0,
-                          target: const LatLng(51.5160895, -0.1294527),
+                          target: LatLng(51.5160895, -0.1294527),
                           tilt: 30.0,
                           zoom: 17.0,
                         ),
@@ -42,7 +42,7 @@
                   },
                   child: const Text('newCameraPosition'),
                 ),
-                new FlatButton(
+                FlatButton(
                   onPressed: () {
                     controller.mapController.animateCamera(
                       CameraUpdate.newLatLng(
@@ -52,11 +52,11 @@
                   },
                   child: const Text('newLatLng'),
                 ),
-                new FlatButton(
+                FlatButton(
                   onPressed: () {
                     controller.mapController.animateCamera(
                       CameraUpdate.newLatLngBounds(
-                        const LatLngBounds(
+                        LatLngBounds(
                           southwest: const LatLng(-38.483935, 113.248673),
                           northeast: const LatLng(-8.982446, 153.823821),
                         ),
@@ -66,7 +66,7 @@
                   },
                   child: const Text('newLatLngBounds'),
                 ),
-                new FlatButton(
+                FlatButton(
                   onPressed: () {
                     controller.mapController.animateCamera(
                       CameraUpdate.newLatLngZoom(
@@ -77,7 +77,7 @@
                   },
                   child: const Text('newLatLngZoom'),
                 ),
-                new FlatButton(
+                FlatButton(
                   onPressed: () {
                     controller.mapController.animateCamera(
                       CameraUpdate.scrollBy(150.0, -225.0),
@@ -87,9 +87,9 @@
                 ),
               ],
             ),
-            new Column(
+            Column(
               children: <Widget>[
-                new FlatButton(
+                FlatButton(
                   onPressed: () {
                     controller.mapController.animateCamera(
                       CameraUpdate.zoomBy(
@@ -100,7 +100,7 @@
                   },
                   child: const Text('zoomBy with focus'),
                 ),
-                new FlatButton(
+                FlatButton(
                   onPressed: () {
                     controller.mapController.animateCamera(
                       CameraUpdate.zoomBy(-0.5),
@@ -108,7 +108,7 @@
                   },
                   child: const Text('zoomBy'),
                 ),
-                new FlatButton(
+                FlatButton(
                   onPressed: () {
                     controller.mapController.animateCamera(
                       CameraUpdate.zoomIn(),
@@ -116,7 +116,7 @@
                   },
                   child: const Text('zoomIn'),
                 ),
-                new FlatButton(
+                FlatButton(
                   onPressed: () {
                     controller.mapController.animateCamera(
                       CameraUpdate.zoomOut(),
@@ -124,7 +124,7 @@
                   },
                   child: const Text('zoomOut'),
                 ),
-                new FlatButton(
+                FlatButton(
                   onPressed: () {
                     controller.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 de1affa..e8161b6 100644
--- a/packages/google_maps_flutter/example/lib/main.dart
+++ b/packages/google_maps_flutter/example/lib/main.dart
@@ -11,30 +11,30 @@
 import 'place_marker.dart';
 
 final List<Page> _allPages = <Page>[
-  new MapUiPage(),
-  new AnimateCameraPage(),
-  new MoveCameraPage(),
-  new PlaceMarkerPage(),
+  MapUiPage(),
+  AnimateCameraPage(),
+  MoveCameraPage(),
+  PlaceMarkerPage(),
 ];
 
 class MapsDemo extends StatelessWidget {
   void _pushPage(BuildContext context, Page page) {
-    Navigator.of(context).push(new MaterialPageRoute<void>(
-        builder: (_) => new Scaffold(
-              appBar: new AppBar(title: new Text(page.title)),
+    Navigator.of(context).push(MaterialPageRoute<void>(
+        builder: (_) => Scaffold(
+              appBar: AppBar(title: Text(page.title)),
               body: page,
             )));
   }
 
   @override
   Widget build(BuildContext context) {
-    return new Scaffold(
-      appBar: new AppBar(title: const Text('GoogleMaps examples')),
-      body: new ListView.builder(
+    return Scaffold(
+      appBar: AppBar(title: const Text('GoogleMaps examples')),
+      body: ListView.builder(
         itemCount: _allPages.length,
-        itemBuilder: (_, int index) => new ListTile(
+        itemBuilder: (_, int index) => ListTile(
               leading: _allPages[index].leading,
-              title: new Text(_allPages[index].title),
+              title: Text(_allPages[index].title),
               onTap: () => _pushPage(context, _allPages[index]),
             ),
       ),
@@ -48,5 +48,5 @@
   for (Page p in _allPages) {
     observers.add(p.controller.overlayController);
   }
-  runApp(new MaterialApp(home: new MapsDemo(), navigatorObservers: observers));
+  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 b362e32..3c11273 100644
--- a/packages/google_maps_flutter/example/lib/map_ui.dart
+++ b/packages/google_maps_flutter/example/lib/map_ui.dart
@@ -7,22 +7,22 @@
 
 import 'page.dart';
 
-const LatLngBounds sydneyBounds = const LatLngBounds(
+final LatLngBounds sydneyBounds = LatLngBounds(
   southwest: const LatLng(-34.022631, 150.620685),
   northeast: const LatLng(-33.571835, 151.325952),
 );
 
 class MapUiPage extends Page {
-  MapUiPage() : super(const Icon(Icons.map), "User interface");
+  MapUiPage() : super(const Icon(Icons.map), 'User interface');
 
   @override
   final GoogleMapOverlayController controller =
-      new GoogleMapOverlayController.fromSize(
+      GoogleMapOverlayController.fromSize(
     width: 300.0,
     height: 200.0,
-    options: const GoogleMapOptions(
+    options: GoogleMapOptions(
       cameraPosition: const CameraPosition(
-        target: const LatLng(-33.852, 151.211),
+        target: LatLng(-33.852, 151.211),
         zoom: 11.0,
       ),
       trackCameraPosition: true,
@@ -31,7 +31,7 @@
 
   @override
   Widget build(BuildContext context) {
-    return new MapUiBody(controller);
+    return MapUiBody(controller);
   }
 }
 
@@ -41,7 +41,7 @@
   const MapUiBody(this.controller);
 
   @override
-  State<StatefulWidget> createState() => new MapUiBodyState();
+  State<StatefulWidget> createState() => MapUiBodyState();
 }
 
 class MapUiBodyState extends State<MapUiBody> {
@@ -66,29 +66,28 @@
   }
 
   Widget _compassToggler() {
-    return new FlatButton(
-      child:
-          new Text('${_options.compassEnabled ? 'disable' : 'enable'} compass'),
+    return FlatButton(
+      child: Text('${_options.compassEnabled ? 'disable' : 'enable'} compass'),
       onPressed: () {
         widget.controller.mapController.updateMapOptions(
-          new GoogleMapOptions(compassEnabled: !_options.compassEnabled),
+          GoogleMapOptions(compassEnabled: !_options.compassEnabled),
         );
       },
     );
   }
 
   Widget _latLngBoundsToggler() {
-    return new FlatButton(
-      child: new Text(
+    return FlatButton(
+      child: Text(
         _options.cameraTargetBounds.bounds == null
             ? 'bound camera target'
             : 'release camera target',
       ),
       onPressed: () {
         widget.controller.mapController.updateMapOptions(
-          new GoogleMapOptions(
+          GoogleMapOptions(
             cameraTargetBounds: _options.cameraTargetBounds.bounds == null
-                ? const CameraTargetBounds(sydneyBounds)
+                ? CameraTargetBounds(sydneyBounds)
                 : CameraTargetBounds.unbounded,
           ),
         );
@@ -97,13 +96,13 @@
   }
 
   Widget _zoomBoundsToggler() {
-    return new FlatButton(
-      child: new Text(_options.minMaxZoomPreference.minZoom == null
+    return FlatButton(
+      child: Text(_options.minMaxZoomPreference.minZoom == null
           ? 'bound zoom'
           : 'release zoom'),
       onPressed: () {
         widget.controller.mapController.updateMapOptions(
-          new GoogleMapOptions(
+          GoogleMapOptions(
             minMaxZoomPreference: _options.minMaxZoomPreference.minZoom == null
                 ? const MinMaxZoomPreference(12.0, 16.0)
                 : MinMaxZoomPreference.unbounded,
@@ -116,23 +115,23 @@
   Widget _mapTypeCycler() {
     final MapType nextType =
         MapType.values[(_options.mapType.index + 1) % MapType.values.length];
-    return new FlatButton(
-      child: new Text('change map type to $nextType'),
+    return FlatButton(
+      child: Text('change map type to $nextType'),
       onPressed: () {
         widget.controller.mapController.updateMapOptions(
-          new GoogleMapOptions(mapType: nextType),
+          GoogleMapOptions(mapType: nextType),
         );
       },
     );
   }
 
   Widget _rotateToggler() {
-    return new FlatButton(
-      child: new Text(
+    return FlatButton(
+      child: Text(
           '${_options.rotateGesturesEnabled ? 'disable' : 'enable'} rotate'),
       onPressed: () {
         widget.controller.mapController.updateMapOptions(
-          new GoogleMapOptions(
+          GoogleMapOptions(
             rotateGesturesEnabled: !_options.rotateGesturesEnabled,
           ),
         );
@@ -141,12 +140,12 @@
   }
 
   Widget _scrollToggler() {
-    return new FlatButton(
-      child: new Text(
+    return FlatButton(
+      child: Text(
           '${_options.scrollGesturesEnabled ? 'disable' : 'enable'} scroll'),
       onPressed: () {
         widget.controller.mapController.updateMapOptions(
-          new GoogleMapOptions(
+          GoogleMapOptions(
             scrollGesturesEnabled: !_options.scrollGesturesEnabled,
           ),
         );
@@ -155,12 +154,12 @@
   }
 
   Widget _tiltToggler() {
-    return new FlatButton(
-      child: new Text(
-          '${_options.tiltGesturesEnabled ? 'disable' : 'enable'} tilt'),
+    return FlatButton(
+      child:
+          Text('${_options.tiltGesturesEnabled ? 'disable' : 'enable'} tilt'),
       onPressed: () {
         widget.controller.mapController.updateMapOptions(
-          new GoogleMapOptions(
+          GoogleMapOptions(
             tiltGesturesEnabled: !_options.tiltGesturesEnabled,
           ),
         );
@@ -169,12 +168,12 @@
   }
 
   Widget _zoomToggler() {
-    return new FlatButton(
-      child: new Text(
-          '${_options.zoomGesturesEnabled ? 'disable' : 'enable'} zoom'),
+    return FlatButton(
+      child:
+          Text('${_options.zoomGesturesEnabled ? 'disable' : 'enable'} zoom'),
       onPressed: () {
         widget.controller.mapController.updateMapOptions(
-          new GoogleMapOptions(
+          GoogleMapOptions(
             zoomGesturesEnabled: !_options.zoomGesturesEnabled,
           ),
         );
@@ -184,25 +183,25 @@
 
   @override
   Widget build(BuildContext context) {
-    return new Column(
+    return Column(
       mainAxisAlignment: MainAxisAlignment.start,
       crossAxisAlignment: CrossAxisAlignment.stretch,
       children: <Widget>[
-        new Padding(
+        Padding(
           padding: const EdgeInsets.all(10.0),
-          child: new Center(
-            child: new GoogleMapOverlay(controller: widget.controller),
+          child: Center(
+            child: GoogleMapOverlay(controller: widget.controller),
           ),
         ),
-        new Column(
+        Column(
           children: <Widget>[
-            new Text('camera bearing: ${_position.bearing}'),
-            new Text(
+            Text('camera bearing: ${_position.bearing}'),
+            Text(
                 'camera target: ${_position.target.latitude.toStringAsFixed(4)},'
                 '${_position.target.longitude.toStringAsFixed(4)}'),
-            new Text('camera zoom: ${_position.zoom}'),
-            new Text('camera tilt: ${_position.tilt}'),
-            new Text(_isMoving ? '(Camera moving)' : '(Camera idle)'),
+            Text('camera zoom: ${_position.zoom}'),
+            Text('camera tilt: ${_position.tilt}'),
+            Text(_isMoving ? '(Camera moving)' : '(Camera idle)'),
             _compassToggler(),
             _latLngBoundsToggler(),
             _mapTypeCycler(),
diff --git a/packages/google_maps_flutter/example/lib/move_camera.dart b/packages/google_maps_flutter/example/lib/move_camera.dart
index 2675d6b..8160495 100644
--- a/packages/google_maps_flutter/example/lib/move_camera.dart
+++ b/packages/google_maps_flutter/example/lib/move_camera.dart
@@ -8,31 +8,31 @@
 import 'page.dart';
 
 class MoveCameraPage extends Page {
-  MoveCameraPage() : super(const Icon(Icons.map), "Camera control");
+  MoveCameraPage() : super(const Icon(Icons.map), 'Camera control');
 
   @override
   final GoogleMapOverlayController controller =
-      new GoogleMapOverlayController.fromSize(width: 300.0, height: 200.0);
+      GoogleMapOverlayController.fromSize(width: 300.0, height: 200.0);
 
   @override
   Widget build(BuildContext context) {
-    return new Column(
+    return Column(
       mainAxisAlignment: MainAxisAlignment.spaceEvenly,
       crossAxisAlignment: CrossAxisAlignment.stretch,
       children: <Widget>[
-        new Center(child: new GoogleMapOverlay(controller: controller)),
-        new Row(
+        Center(child: GoogleMapOverlay(controller: controller)),
+        Row(
           mainAxisAlignment: MainAxisAlignment.spaceEvenly,
           children: <Widget>[
-            new Column(
+            Column(
               children: <Widget>[
-                new FlatButton(
+                FlatButton(
                   onPressed: () {
                     controller.mapController.moveCamera(
                       CameraUpdate.newCameraPosition(
                         const CameraPosition(
                           bearing: 270.0,
-                          target: const LatLng(51.5160895, -0.1294527),
+                          target: LatLng(51.5160895, -0.1294527),
                           tilt: 30.0,
                           zoom: 17.0,
                         ),
@@ -41,7 +41,7 @@
                   },
                   child: const Text('newCameraPosition'),
                 ),
-                new FlatButton(
+                FlatButton(
                   onPressed: () {
                     controller.mapController.moveCamera(
                       CameraUpdate.newLatLng(
@@ -51,11 +51,11 @@
                   },
                   child: const Text('newLatLng'),
                 ),
-                new FlatButton(
+                FlatButton(
                   onPressed: () {
                     controller.mapController.moveCamera(
                       CameraUpdate.newLatLngBounds(
-                        const LatLngBounds(
+                        LatLngBounds(
                           southwest: const LatLng(-38.483935, 113.248673),
                           northeast: const LatLng(-8.982446, 153.823821),
                         ),
@@ -65,7 +65,7 @@
                   },
                   child: const Text('newLatLngBounds'),
                 ),
-                new FlatButton(
+                FlatButton(
                   onPressed: () {
                     controller.mapController.moveCamera(
                       CameraUpdate.newLatLngZoom(
@@ -76,7 +76,7 @@
                   },
                   child: const Text('newLatLngZoom'),
                 ),
-                new FlatButton(
+                FlatButton(
                   onPressed: () {
                     controller.mapController.moveCamera(
                       CameraUpdate.scrollBy(150.0, -225.0),
@@ -86,9 +86,9 @@
                 ),
               ],
             ),
-            new Column(
+            Column(
               children: <Widget>[
-                new FlatButton(
+                FlatButton(
                   onPressed: () {
                     controller.mapController.moveCamera(
                       CameraUpdate.zoomBy(
@@ -99,7 +99,7 @@
                   },
                   child: const Text('zoomBy with focus'),
                 ),
-                new FlatButton(
+                FlatButton(
                   onPressed: () {
                     controller.mapController.moveCamera(
                       CameraUpdate.zoomBy(-0.5),
@@ -107,7 +107,7 @@
                   },
                   child: const Text('zoomBy'),
                 ),
-                new FlatButton(
+                FlatButton(
                   onPressed: () {
                     controller.mapController.moveCamera(
                       CameraUpdate.zoomIn(),
@@ -115,7 +115,7 @@
                   },
                   child: const Text('zoomIn'),
                 ),
-                new FlatButton(
+                FlatButton(
                   onPressed: () {
                     controller.mapController.moveCamera(
                       CameraUpdate.zoomOut(),
@@ -123,7 +123,7 @@
                   },
                   child: const Text('zoomOut'),
                 ),
-                new FlatButton(
+                FlatButton(
                   onPressed: () {
                     controller.mapController.moveCamera(
                       CameraUpdate.zoomTo(16.0),
diff --git a/packages/google_maps_flutter/example/lib/place_marker.dart b/packages/google_maps_flutter/example/lib/place_marker.dart
index b2fe8c6..5b0a3eb 100644
--- a/packages/google_maps_flutter/example/lib/place_marker.dart
+++ b/packages/google_maps_flutter/example/lib/place_marker.dart
@@ -10,16 +10,16 @@
 import 'page.dart';
 
 class PlaceMarkerPage extends Page {
-  PlaceMarkerPage() : super(const Icon(Icons.place), "Place marker");
+  PlaceMarkerPage() : super(const Icon(Icons.place), 'Place marker');
 
   @override
   final GoogleMapOverlayController controller =
-      new GoogleMapOverlayController.fromSize(
+      GoogleMapOverlayController.fromSize(
     width: 300.0,
     height: 200.0,
-    options: const GoogleMapOptions(
+    options: GoogleMapOptions(
       cameraPosition: const CameraPosition(
-        target: const LatLng(-33.852, 151.211),
+        target: LatLng(-33.852, 151.211),
         zoom: 11.0,
       ),
     ),
@@ -27,7 +27,7 @@
 
   @override
   Widget build(BuildContext context) {
-    return new PlaceMarkerBody(controller);
+    return PlaceMarkerBody(controller);
   }
 }
 
@@ -37,39 +37,53 @@
   const PlaceMarkerBody(this.controller);
 
   @override
-  State<StatefulWidget> createState() => new PlaceMarkerBodyState();
+  State<StatefulWidget> createState() {
+    return PlaceMarkerBodyState(controller.mapController);
+  }
 }
 
 class PlaceMarkerBodyState extends State<PlaceMarkerBody> {
-  static const LatLng center = const LatLng(-33.86711, 151.1947171);
+  static final LatLng center = const LatLng(-33.86711, 151.1947171);
 
+  PlaceMarkerBodyState(this.controller);
+
+  final GoogleMapController controller;
   int _markerCount = 0;
   Marker _selectedMarker;
 
   @override
   void initState() {
     super.initState();
-    widget.controller.mapController.onMarkerTapped.add((Marker marker) {
+    controller.onMarkerTapped.add((Marker marker) {
       if (_selectedMarker != null) {
-        _selectedMarker
-            .update(const MarkerOptions(icon: BitmapDescriptor.defaultMarker));
+        _updateSelectedMarker(
+          const MarkerOptions(icon: BitmapDescriptor.defaultMarker),
+        );
       }
       setState(() {
         _selectedMarker = marker;
       });
-      _selectedMarker.update(new MarkerOptions(
-          icon: BitmapDescriptor
-              .defaultMarkerWithHue(BitmapDescriptor.hueGreen)));
+      _updateSelectedMarker(
+        MarkerOptions(
+          icon: BitmapDescriptor.defaultMarkerWithHue(
+            BitmapDescriptor.hueGreen,
+          ),
+        ),
+      );
     });
   }
 
+  void _updateSelectedMarker(MarkerOptions changes) {
+    controller.updateMarker(_selectedMarker, changes);
+  }
+
   void _add() {
-    widget.controller.mapController.addMarker(new MarkerOptions(
-      position: new LatLng(
+    controller.addMarker(MarkerOptions(
+      position: LatLng(
         center.latitude + sin(_markerCount * pi / 6.0) / 20.0,
         center.longitude + cos(_markerCount * pi / 6.0) / 20.0,
       ),
-      infoWindowText: new InfoWindowText('Marker #${_markerCount + 1}', '*'),
+      infoWindowText: InfoWindowText('Marker #${_markerCount + 1}', '*'),
     ));
     setState(() {
       _markerCount += 1;
@@ -77,7 +91,7 @@
   }
 
   void _remove() {
-    _selectedMarker.remove();
+    controller.removeMarker(_selectedMarker);
     setState(() {
       _selectedMarker = null;
       _markerCount -= 1;
@@ -86,13 +100,13 @@
 
   void _changePosition() {
     final LatLng current = _selectedMarker.options.position;
-    final Offset offset = new Offset(
+    final Offset offset = Offset(
       center.latitude - current.latitude,
       center.longitude - current.longitude,
     );
-    _selectedMarker.update(
-      new MarkerOptions(
-        position: new LatLng(
+    _updateSelectedMarker(
+      MarkerOptions(
+        position: LatLng(
           center.latitude + offset.dy,
           center.longitude + offset.dx,
         ),
@@ -102,32 +116,30 @@
 
   void _changeAnchor() {
     final Offset currentAnchor = _selectedMarker.options.anchor;
-    final Offset newAnchor =
-        new Offset(1.0 - currentAnchor.dy, currentAnchor.dx);
-    _selectedMarker.update(new MarkerOptions(anchor: newAnchor));
+    final Offset newAnchor = Offset(1.0 - currentAnchor.dy, currentAnchor.dx);
+    _updateSelectedMarker(MarkerOptions(anchor: newAnchor));
   }
 
   Future<void> _changeInfoAnchor() async {
     final Offset currentAnchor = _selectedMarker.options.infoWindowAnchor;
-    final Offset newAnchor =
-        new Offset(1.0 - currentAnchor.dy, currentAnchor.dx);
-    _selectedMarker.update(new MarkerOptions(infoWindowAnchor: newAnchor));
+    final Offset newAnchor = Offset(1.0 - currentAnchor.dy, currentAnchor.dx);
+    _updateSelectedMarker(MarkerOptions(infoWindowAnchor: newAnchor));
   }
 
   Future<void> _toggleDraggable() async {
-    _selectedMarker.update(
-        new MarkerOptions(draggable: !_selectedMarker.options.draggable));
+    _updateSelectedMarker(
+      MarkerOptions(draggable: !_selectedMarker.options.draggable),
+    );
   }
 
   Future<void> _toggleFlat() async {
-    _selectedMarker
-        .update(new MarkerOptions(flat: !_selectedMarker.options.flat));
+    _updateSelectedMarker(MarkerOptions(flat: !_selectedMarker.options.flat));
   }
 
   Future<void> _changeInfo() async {
     final InfoWindowText currentInfo = _selectedMarker.options.infoWindowText;
-    _selectedMarker.update(new MarkerOptions(
-      infoWindowText: new InfoWindowText(
+    _updateSelectedMarker(MarkerOptions(
+      infoWindowText: InfoWindowText(
         currentInfo.title,
         currentInfo.snippet + '*',
       ),
@@ -136,100 +148,101 @@
 
   Future<void> _changeAlpha() async {
     final double current = _selectedMarker.options.alpha;
-    _selectedMarker.update(
-      new MarkerOptions(alpha: current < 0.1 ? 1.0 : current * 0.75),
+    _updateSelectedMarker(
+      MarkerOptions(alpha: current < 0.1 ? 1.0 : current * 0.75),
     );
   }
 
   Future<void> _changeRotation() async {
     final double current = _selectedMarker.options.rotation;
-    _selectedMarker.update(
-      new MarkerOptions(rotation: current == 330.0 ? 0.0 : current + 30.0),
+    _updateSelectedMarker(
+      MarkerOptions(rotation: current == 330.0 ? 0.0 : current + 30.0),
     );
   }
 
   Future<void> _toggleVisible() async {
-    _selectedMarker
-        .update(new MarkerOptions(visible: !_selectedMarker.options.visible));
+    _updateSelectedMarker(
+      MarkerOptions(visible: !_selectedMarker.options.visible),
+    );
   }
 
   Future<void> _changeZIndex() async {
     final double current = _selectedMarker.options.zIndex;
-    _selectedMarker.update(
-      new MarkerOptions(zIndex: current == 12.0 ? 0.0 : current + 1.0),
+    _updateSelectedMarker(
+      MarkerOptions(zIndex: current == 12.0 ? 0.0 : current + 1.0),
     );
   }
 
   @override
   Widget build(BuildContext context) {
-    return new Column(
+    return Column(
       mainAxisAlignment: MainAxisAlignment.spaceEvenly,
       crossAxisAlignment: CrossAxisAlignment.stretch,
       children: <Widget>[
-        new Center(child: new GoogleMapOverlay(controller: widget.controller)),
-        new Row(
+        Center(child: GoogleMapOverlay(controller: widget.controller)),
+        Row(
           mainAxisAlignment: MainAxisAlignment.spaceEvenly,
           children: <Widget>[
-            new Row(
+            Row(
               children: <Widget>[
-                new Column(
+                Column(
                   children: <Widget>[
-                    new FlatButton(
+                    FlatButton(
                       child: const Text('add'),
                       onPressed: (_markerCount == 12) ? null : _add,
                     ),
-                    new FlatButton(
+                    FlatButton(
                       child: const Text('remove'),
                       onPressed: (_selectedMarker == null) ? null : _remove,
                     ),
-                    new FlatButton(
+                    FlatButton(
                       child: const Text('change info'),
                       onPressed: (_selectedMarker == null) ? null : _changeInfo,
                     ),
-                    new FlatButton(
+                    FlatButton(
                       child: const Text('change info anchor'),
                       onPressed:
                           (_selectedMarker == null) ? null : _changeInfoAnchor,
                     ),
                   ],
                 ),
-                new Column(
+                Column(
                   children: <Widget>[
-                    new FlatButton(
+                    FlatButton(
                       child: const Text('change alpha'),
                       onPressed:
                           (_selectedMarker == null) ? null : _changeAlpha,
                     ),
-                    new FlatButton(
+                    FlatButton(
                       child: const Text('change anchor'),
                       onPressed:
                           (_selectedMarker == null) ? null : _changeAnchor,
                     ),
-                    new FlatButton(
+                    FlatButton(
                       child: const Text('toggle draggable'),
                       onPressed:
                           (_selectedMarker == null) ? null : _toggleDraggable,
                     ),
-                    new FlatButton(
+                    FlatButton(
                       child: const Text('toggle flat'),
                       onPressed: (_selectedMarker == null) ? null : _toggleFlat,
                     ),
-                    new FlatButton(
+                    FlatButton(
                       child: const Text('change position'),
                       onPressed:
                           (_selectedMarker == null) ? null : _changePosition,
                     ),
-                    new FlatButton(
+                    FlatButton(
                       child: const Text('change rotation'),
                       onPressed:
                           (_selectedMarker == null) ? null : _changeRotation,
                     ),
-                    new FlatButton(
+                    FlatButton(
                       child: const Text('toggle visible'),
                       onPressed:
                           (_selectedMarker == null) ? null : _toggleVisible,
                     ),
-                    new FlatButton(
+                    FlatButton(
                       child: const Text('change zIndex'),
                       onPressed:
                           (_selectedMarker == null) ? null : _changeZIndex,
diff --git a/packages/google_maps_flutter/ios/Classes/GoogleMapController.h b/packages/google_maps_flutter/ios/Classes/GoogleMapController.h
index 16ea81d..a111525 100644
--- a/packages/google_maps_flutter/ios/Classes/GoogleMapController.h
+++ b/packages/google_maps_flutter/ios/Classes/GoogleMapController.h
@@ -41,6 +41,7 @@
 - (void)hide;
 - (void)animateWithCameraUpdate:(GMSCameraUpdate*)cameraUpdate;
 - (void)moveWithCameraUpdate:(GMSCameraUpdate*)cameraUpdate;
+- (GMSCameraPosition*)cameraPosition;
 - (NSString*)addMarkerWithPosition:(CLLocationCoordinate2D)position;
 - (FLTGoogleMapMarkerController*)markerWithId:(NSString*)markerId;
 - (void)removeMarkerWithId:(NSString*)markerId;
diff --git a/packages/google_maps_flutter/ios/Classes/GoogleMapController.m b/packages/google_maps_flutter/ios/Classes/GoogleMapController.m
index 4391106..0cfae0b 100644
--- a/packages/google_maps_flutter/ios/Classes/GoogleMapController.m
+++ b/packages/google_maps_flutter/ios/Classes/GoogleMapController.m
@@ -59,6 +59,14 @@
   [_mapView moveCamera:cameraUpdate];
 }
 
+- (GMSCameraPosition*)cameraPosition {
+  if (_trackCameraPosition) {
+    return _mapView.camera;
+  } else {
+    return nil;
+  }
+}
+
 - (NSString*)addMarkerWithPosition:(CLLocationCoordinate2D)position {
   FLTGoogleMapMarkerController* markerController =
       [[FLTGoogleMapMarkerController alloc] initWithPosition:position mapView:_mapView];
diff --git a/packages/google_maps_flutter/ios/Classes/GoogleMapMarkerController.h b/packages/google_maps_flutter/ios/Classes/GoogleMapMarkerController.h
index ee724b5..2e5b80a 100644
--- a/packages/google_maps_flutter/ios/Classes/GoogleMapMarkerController.h
+++ b/packages/google_maps_flutter/ios/Classes/GoogleMapMarkerController.h
@@ -15,6 +15,7 @@
 - (void)setIcon:(UIImage*)icon;
 - (void)setInfoWindowAnchor:(CGPoint)anchor;
 - (void)setInfoWindowTitle:(NSString*)title snippet:(NSString*)snippet;
+- (void)setPosition:(CLLocationCoordinate2D)position;
 - (void)setRotation:(CLLocationDegrees)rotation;
 - (void)setVisible:(BOOL)visible;
 - (void)setZIndex:(int)zIndex;
diff --git a/packages/google_maps_flutter/ios/Classes/GoogleMapMarkerController.m b/packages/google_maps_flutter/ios/Classes/GoogleMapMarkerController.m
index d552db9..600c755 100644
--- a/packages/google_maps_flutter/ios/Classes/GoogleMapMarkerController.m
+++ b/packages/google_maps_flutter/ios/Classes/GoogleMapMarkerController.m
@@ -48,6 +48,9 @@
   _marker.title = title;
   _marker.snippet = snippet;
 }
+- (void)setPosition:(CLLocationCoordinate2D)position {
+  _marker.position = position;
+}
 - (void)setRotation:(CLLocationDegrees)rotation {
   _marker.rotation = rotation;
 }
diff --git a/packages/google_maps_flutter/ios/Classes/GoogleMapsPlugin.m b/packages/google_maps_flutter/ios/Classes/GoogleMapsPlugin.m
index 29063e9..a251ceb 100644
--- a/packages/google_maps_flutter/ios/Classes/GoogleMapsPlugin.m
+++ b/packages/google_maps_flutter/ios/Classes/GoogleMapsPlugin.m
@@ -53,7 +53,7 @@
     }
     [_mapControllers removeAllObjects];
     result(nil);
-  } else if ([call.method isEqualToString:@"createMap"]) {
+  } else if ([call.method isEqualToString:@"map#create"]) {
     NSDictionary* options = call.arguments[@"options"];
     GMSCameraPosition* camera = toOptionalCameraPosition(options[@"cameraPosition"]);
     FLTGoogleMapController* controller =
@@ -73,22 +73,22 @@
       result(error);
       return;
     }
-    if ([call.method isEqualToString:@"showMapOverlay"]) {
+    if ([call.method isEqualToString:@"map#show"]) {
       [controller showAtX:toDouble(call.arguments[@"x"]) Y:toDouble(call.arguments[@"y"])];
       result(nil);
-    } else if ([call.method isEqualToString:@"hideMapOverlay"]) {
+    } else if ([call.method isEqualToString:@"map#hide"]) {
       [controller hide];
       result(nil);
-    } else if ([call.method isEqualToString:@"animateCamera"]) {
+    } else if ([call.method isEqualToString:@"camera#animate"]) {
       [controller animateWithCameraUpdate:toCameraUpdate(call.arguments[@"cameraUpdate"])];
       result(nil);
-    } else if ([call.method isEqualToString:@"moveCamera"]) {
+    } else if ([call.method isEqualToString:@"camera#move"]) {
       [controller moveWithCameraUpdate:toCameraUpdate(call.arguments[@"cameraUpdate"])];
       result(nil);
-    } else if ([call.method isEqualToString:@"updateMapOptions"]) {
+    } else if ([call.method isEqualToString:@"map#update"]) {
       interpretMapOptions(call.arguments[@"options"], controller);
-      result(nil);
-    } else if ([call.method isEqualToString:@"addMarker"]) {
+      result(positionToJson([controller cameraPosition]));
+    } else if ([call.method isEqualToString:@"marker#add"]) {
       NSDictionary* options = call.arguments[@"options"];
       NSString* markerId = [controller addMarkerWithPosition:toLocation(options[@"position"])];
       interpretMarkerOptions(options, [controller markerWithId:markerId], _registrar);
@@ -118,7 +118,7 @@
 #pragma mark - FLTGoogleMapsDelegate methods, used to send platform messages to Flutter
 
 - (void)onCameraMoveStartedOnMap:(id)mapId gesture:(BOOL)gesture {
-  [_channel invokeMethod:@"map#onCameraMoveStarted"
+  [_channel invokeMethod:@"camera#onMoveStarted"
                arguments:@{
                  @"map" : mapId,
                  @"isGesture" : @(gesture)
@@ -126,12 +126,12 @@
 }
 
 - (void)onCameraMoveOnMap:(id)mapId cameraPosition:(GMSCameraPosition*)cameraPosition {
-  [_channel invokeMethod:@"map#onCameraMove"
+  [_channel invokeMethod:@"camera#onMove"
                arguments:@{@"map" : mapId, @"position" : positionToJson(cameraPosition)}];
 }
 
 - (void)onCameraIdleOnMap:(id)mapId {
-  [_channel invokeMethod:@"map#onCameraIdle" arguments:@{@"map" : mapId}];
+  [_channel invokeMethod:@"camera#onIdle" arguments:@{@"map" : mapId}];
 }
 
 - (void)onMarkerTappedOnMap:(id)mapId marker:(NSString*)markerId {
@@ -146,6 +146,9 @@
 }
 
 static id positionToJson(GMSCameraPosition* position) {
+  if (!position) {
+    return nil;
+  }
   return @{
     @"target" : locationToJson([position target]),
     @"zoom" : @([position zoom]),
@@ -338,6 +341,10 @@
     NSString* snippet = (infoWindowTextData[1] == [NSNull null]) ? nil : infoWindowTextData[1];
     [sink setInfoWindowTitle:title snippet:snippet];
   }
+  id position = data[@"position"];
+  if (position) {
+    [sink setPosition:toLocation(position)];
+  }
   id rotation = data[@"rotation"];
   if (rotation) {
     [sink setRotation:toDouble(rotation)];
diff --git a/packages/google_maps_flutter/lib/google_maps_flutter.dart b/packages/google_maps_flutter/lib/google_maps_flutter.dart
index f07c568..8f98368 100644
--- a/packages/google_maps_flutter/lib/google_maps_flutter.dart
+++ b/packages/google_maps_flutter/lib/google_maps_flutter.dart
@@ -18,7 +18,7 @@
 part 'src/callbacks.dart';
 part 'src/camera.dart';
 part 'src/controller.dart';
-part 'src/ui.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/callbacks.dart b/packages/google_maps_flutter/lib/src/callbacks.dart
index eb2ab86..d8e906f 100644
--- a/packages/google_maps_flutter/lib/src/callbacks.dart
+++ b/packages/google_maps_flutter/lib/src/callbacks.dart
@@ -11,10 +11,17 @@
 ///
 /// Additions and removals happening during a single [call] invocation do not
 /// change who gets a callback until the next such invocation.
+///
+/// Optimized for the singleton case.
 class ArgumentCallbacks<T> {
   final List<ArgumentCallback<T>> _callbacks = <ArgumentCallback<T>>[];
-  VoidCallback _onEmptyChanged;
 
+  /// Callback method. Invokes the corresponding method on each callback
+  /// in this collection.
+  ///
+  /// The list of callbacks being invoked is computed at the start of the
+  /// method and is unaffected by any changes subsequently made to this
+  /// collection.
   void call(T argument) {
     final int length = _callbacks.length;
     if (length == 1) {
@@ -27,18 +34,22 @@
     }
   }
 
+  /// Adds a callback to this collection.
   void add(ArgumentCallback<T> callback) {
+    assert(callback != null);
     _callbacks.add(callback);
-    if (_onEmptyChanged != null && _callbacks.length == 1) _onEmptyChanged();
   }
 
+  /// Removes a callback from this collection.
+  ///
+  /// Does nothing, if the callback was not present.
   void remove(ArgumentCallback<T> callback) {
-    final bool removed = _callbacks.remove(callback);
-    if (_onEmptyChanged != null && removed && _callbacks.isEmpty)
-      _onEmptyChanged();
+    _callbacks.remove(callback);
   }
 
+  /// Whether this collection is empty.
   bool get isEmpty => _callbacks.isEmpty;
 
+  /// Whether this collection is non-empty.
   bool get isNotEmpty => _callbacks.isNotEmpty;
 }
diff --git a/packages/google_maps_flutter/lib/src/camera.dart b/packages/google_maps_flutter/lib/src/camera.dart
index e105980..3362a27 100644
--- a/packages/google_maps_flutter/lib/src/camera.dart
+++ b/packages/google_maps_flutter/lib/src/camera.dart
@@ -4,6 +4,9 @@
 
 part of google_maps_flutter;
 
+/// The position of the map "camera", the view point from which the world is
+/// shown in the map view. Aggregates the camera's [target] geographical
+/// location, its [zoom] level, [tilt] angle, and [bearing].
 class CameraPosition {
   const CameraPosition({
     this.bearing = 0.0,
@@ -15,9 +18,37 @@
         assert(tilt != null),
         assert(zoom != null);
 
+  /// The camera's bearing in degrees, measured clockwise from north.
+  ///
+  /// A bearing of 0.0, the default, means the camera points north.
+  /// A bearing of 90.0 means the camera points east.
   final double bearing;
+
+  /// The geographical location that the camera is pointing at.
   final LatLng target;
+
+  /// The angle, in degrees, of the camera angle from the nadir.
+  ///
+  /// A tilt of 0.0, the default and minimum supported value, means the camera
+  /// is directly facing the Earth.
+  ///
+  /// The maximum tilt value depends on the current zoom level. Values beyond
+  /// the supported range are allowed, but on applying them to a map they will
+  /// be silently clamped to the supported range.
   final double tilt;
+
+  /// The zoom level of the camera.
+  ///
+  /// A zoom of 0.0, the default, means the screen width of the world is 256.
+  /// Adding 1.0 to the zoom level doubles the screen width of the map. So at
+  /// zoom level 3.0, the screen width of the world is 2³x256=2048.
+  ///
+  /// Larger zoom levels thus means the camera is placed closer to the surface
+  /// of the Earth, revealing more detail in a narrower geographical region.
+  ///
+  /// The supported zoom level range depends on the map data and device. Values
+  /// beyond the supported range are allowed, but on applying them to a map they
+  /// will be silently clamped to the supported range.
   final double zoom;
 
   dynamic _toJson() => <String, dynamic>{
@@ -40,21 +71,28 @@
   }
 }
 
+/// Defines a camera move, supporting absolute moves as well as moves relative
+/// the current position.
 class CameraUpdate {
   CameraUpdate._(this._json);
 
-  final dynamic _json;
-
+  /// Returns a camera update that moves the camera to the specified position.
   static CameraUpdate newCameraPosition(CameraPosition cameraPosition) {
     return new CameraUpdate._(
       <dynamic>['newCameraPosition', cameraPosition._toJson()],
     );
   }
 
+  /// Returns a camera update that moves the camera target to the specified
+  /// geographical location.
   static CameraUpdate newLatLng(LatLng latLng) {
     return new CameraUpdate._(<dynamic>['newLatLng', latLng._toJson()]);
   }
 
+  /// Returns a camera update that transforms the camera so that the specified
+  /// geographical bounding box is centered in the map view at the greatest
+  /// possible zoom level. A non-zero [padding] insets the bounding box from the
+  /// map view's edges. The camera's new tilt and bearing will both be 0.0.
   static CameraUpdate newLatLngBounds(LatLngBounds bounds, double padding) {
     return new CameraUpdate._(<dynamic>[
       'newLatLngBounds',
@@ -63,18 +101,29 @@
     ]);
   }
 
+  /// Returns a camera update that moves the camera target to the specified
+  /// geographical location and zoom level.
   static CameraUpdate newLatLngZoom(LatLng latLng, double zoom) {
     return new CameraUpdate._(
       <dynamic>['newLatLngZoom', latLng._toJson(), zoom],
     );
   }
 
+  /// Returns a camera update that moves the camera target the specified screen
+  /// distance.
+  ///
+  /// For a camera with bearing 0.0 (pointing north), scrolling by 50,75 moves
+  /// the camera's target to a geographical location that is 50 to the east and
+  /// 75 to the south of the current location, measured in screen coordinates.
   static CameraUpdate scrollBy(double dx, double dy) {
     return new CameraUpdate._(
       <dynamic>['scrollBy', dx, dy],
     );
   }
 
+  /// Returns a camera update that modifies the camera zoom level by the
+  /// specified amount. The optional [focus] is a screen point whose underlying
+  /// geographical location should be invariant, if possible, by the movement.
   static CameraUpdate zoomBy(double amount, [Offset focus]) {
     if (focus == null) {
       return new CameraUpdate._(<dynamic>['zoomBy', amount]);
@@ -87,17 +136,28 @@
     }
   }
 
+  /// Returns a camera update that zooms the camera in, bringing the camera
+  /// closer to the surface of the Earth.
+  ///
+  /// Equivalent to the result of calling `zoomBy(1.0)`.
   static CameraUpdate zoomIn() {
     return new CameraUpdate._(<dynamic>['zoomIn']);
   }
 
+  /// Returns a camera update that zooms the camera out, bringing the camera
+  /// further away from the surface of the Earth.
+  ///
+  /// Equivalent to the result of calling `zoomBy(-1.0)`.
   static CameraUpdate zoomOut() {
     return new CameraUpdate._(<dynamic>['zoomOut']);
   }
 
+  /// Returns a camera update that sets the camera zoom level.
   static CameraUpdate zoomTo(double zoom) {
     return new CameraUpdate._(<dynamic>['zoomTo', zoom]);
   }
 
+  final dynamic _json;
+
   dynamic _toJson() => _json;
 }
diff --git a/packages/google_maps_flutter/lib/src/controller.dart b/packages/google_maps_flutter/lib/src/controller.dart
index dd5d8fd..032766c 100644
--- a/packages/google_maps_flutter/lib/src/controller.dart
+++ b/packages/google_maps_flutter/lib/src/controller.dart
@@ -7,25 +7,26 @@
 final MethodChannel _channel =
     const MethodChannel('plugins.flutter.io/google_maps');
 
-/// Controller for a single GoogleMap instance.
-///
-/// Used for programmatically controlling a platform-specific GoogleMap view.
+/// Controller for a single GoogleMap instance running on the host platform.
 ///
 /// Change listeners are notified upon changes to any of
 ///
-/// * the [options] property,
+/// * the [options] property
 /// * the collection of [Marker]s added to this map
-/// * the [cameraPosition] property,
+/// * the [isCameraMoving] property
+/// * the [cameraPosition] property
 ///
-/// Listeners are notified when changes have been applied on the platform side.
+/// Listeners are notified after changes have been applied on the platform side.
 ///
 /// Marker tap events can be received by adding callbacks to [onMarkerTapped].
 class GoogleMapController extends ChangeNotifier {
-  GoogleMapController._({
-    this.id,
-    GoogleMapOptions options,
-  }) : _options = options {
-    id.then((int id) {
+  @visibleForTesting
+  GoogleMapController(this._id, GoogleMapOptions options)
+      : assert(_id != null),
+        assert(options != null),
+        assert(options.cameraPosition != null),
+        _options = options {
+    _id.then((int id) {
       _controllers[id] = this;
     });
     if (options.trackCameraPosition) {
@@ -33,9 +34,7 @@
     }
   }
 
-  /// An ID identifying the GoogleMaps instance, once created.
-  final Future<int> id;
-
+  /// Callbacks to receive tap events for markers placed on this map.
   final ArgumentCallbacks<Marker> onMarkerTapped =
       new ArgumentCallbacks<Marker>();
 
@@ -44,9 +43,13 @@
   GoogleMapOptions get options => _options;
   GoogleMapOptions _options;
 
+  /// The current set of markers on this map.
+  ///
+  /// The returned set will be a detached snapshot of the markers collection.
   Set<Marker> get markers => new Set<Marker>.from(_markers.values);
   final Map<String, Marker> _markers = <String, Marker>{};
 
+  /// True if the map camera is currently moving.
   bool get isCameraMoving => _isCameraMoving;
   bool _isCameraMoving = false;
 
@@ -56,11 +59,18 @@
   CameraPosition get cameraPosition => _cameraPosition;
   CameraPosition _cameraPosition;
 
+  final Future<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) {
       final int mapId = call.arguments['map'];
       final GoogleMapController controller = _controllers[mapId];
@@ -79,15 +89,15 @@
           onMarkerTapped(marker);
         }
         break;
-      case 'map#onCameraMoveStarted':
+      case 'camera#onMoveStarted':
         _isCameraMoving = true;
         notifyListeners();
         break;
-      case 'map#onCameraMove':
+      case 'camera#onMove':
         _cameraPosition = CameraPosition._fromJson(call.arguments['position']);
         notifyListeners();
         break;
-      case 'map#onCameraIdle':
+      case 'camera#onIdle':
         _isCameraMoving = false;
         notifyListeners();
         break;
@@ -96,108 +106,149 @@
     }
   }
 
-  Future<void> updateMapOptions(GoogleMapOptions options) async {
-    assert(options != null);
-    final int id = await this.id;
-    await _channel.invokeMethod('updateMapOptions', <String, dynamic>{
-      'map': id,
-      'options': options._toJson(),
-    });
-    _options = _options._updateWith(options);
-    if (!_options.trackCameraPosition) {
-      _cameraPosition = null;
-    }
+  /// Updates configuration options of the map user interface.
+  ///
+  /// Change listeners are notified once the update has been made on the
+  /// platform side.
+  ///
+  /// 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(),
+      },
+    );
+    _options = _options.copyWith(changes);
+    _cameraPosition = CameraPosition._fromJson(json);
     notifyListeners();
   }
 
+  /// Starts an animated change of the map camera position.
+  ///
+  /// The returned [Future] completes after the change has been started on the
+  /// platform side.
   Future<void> animateCamera(CameraUpdate cameraUpdate) async {
-    final int id = await this.id;
-    await _channel.invokeMethod('animateCamera', <String, dynamic>{
+    final int id = await _id;
+    await _channel.invokeMethod('camera#animate', <String, dynamic>{
       'map': id,
       'cameraUpdate': cameraUpdate._toJson(),
     });
   }
 
+  /// Changes the map camera position.
+  ///
+  /// The returned [Future] completes after the change has been made on the
+  /// platform side.
   Future<void> moveCamera(CameraUpdate cameraUpdate) async {
-    final int id = await this.id;
-    await _channel.invokeMethod('moveCamera', <String, dynamic>{
+    final int id = await _id;
+    await _channel.invokeMethod('camera#move', <String, dynamic>{
       'map': id,
       'cameraUpdate': cameraUpdate._toJson(),
     });
   }
 
+  /// Adds a marker to the map, configured using the specified custom [options].
+  ///
+  /// Change listeners are notified once the marker has been added on the
+  /// platform side.
+  ///
+  /// The returned [Future] completes with the added marker once listeners have
+  /// been notified.
   Future<Marker> addMarker(MarkerOptions options) async {
-    assert(options != null);
-    assert(options.position != null);
-    final int id = await this.id;
+    final int id = await _id;
     final MarkerOptions effectiveOptions =
-        MarkerOptions.defaultOptions._updateWith(options);
+        MarkerOptions.defaultOptions.copyWith(options);
     final String markerId = await _channel.invokeMethod(
-      'addMarker',
+      'marker#add',
       <String, dynamic>{
         'map': id,
         'options': effectiveOptions._toJson(),
       },
     );
-    final Marker marker = new Marker._(this, markerId, effectiveOptions);
+    final Marker marker = new Marker(markerId, effectiveOptions);
     _markers[markerId] = marker;
     notifyListeners();
     return marker;
   }
 
-  Future<void> _updateMarker(Marker marker, MarkerOptions changes) async {
-    assert(_markers[marker.id] == marker);
+  /// Updates the specified [marker] with the given [changes]. The marker must
+  /// be a current member of the [markers] set.
+  ///
+  /// Change listeners are notified once the marker has been updated on the
+  /// platform side.
+  ///
+  /// The returned [Future] completes once listeners have been notified.
+  Future<void> updateMarker(Marker marker, MarkerOptions changes) async {
+    assert(marker != null);
+    assert(_markers[marker._id] == marker);
     assert(changes != null);
-    final int id = await this.id;
+    final int id = await _id;
     await _channel.invokeMethod('marker#update', <String, dynamic>{
       'map': id,
-      'marker': marker.id,
+      'marker': marker._id,
       'options': changes._toJson(),
     });
-    marker._options = marker._options._updateWith(changes);
+    marker._options = marker._options.copyWith(changes);
     notifyListeners();
   }
 
-  Future<void> _removeMarker(Marker marker) async {
-    assert(_markers[marker.id] == marker);
-    final int id = await this.id;
+  /// Removes the specified [marker] from the map. The marker must be a current
+  /// member of the [markers] set.
+  ///
+  /// Change listeners are notified once the marker has been removed on the
+  /// platform side.
+  ///
+  /// The returned [Future] completes once listeners have been notified.
+  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,
+      'marker': marker._id,
     });
-    _markers.remove(marker.id);
+    _markers.remove(marker._id);
     notifyListeners();
   }
 }
 
-/// Controller for a GoogleMap instance that is integrated as a
+/// 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 in
-  /// logical pixels.
+  /// 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 = const GoogleMapOptions(),
+    GoogleMapOptions options,
   }) {
     assert(width != null);
     assert(height != null);
-    assert(options != null);
     final GoogleMapOptions effectiveOptions =
-        GoogleMapOptions.defaultOptions._updateWith(options);
+        GoogleMapOptions.defaultOptions.copyWith(options);
     final _GoogleMapsPlatformOverlay overlay =
         new _GoogleMapsPlatformOverlay(effectiveOptions);
     return new GoogleMapOverlayController._(
-      new GoogleMapController._(
-        id: overlay._textureId.future,
-        options: effectiveOptions,
-      ),
+      new GoogleMapController(overlay._textureId.future, effectiveOptions),
       new PlatformOverlayController(width, height, overlay),
     );
   }
@@ -221,7 +272,7 @@
 
   @override
   Future<int> create(Size size) {
-    _textureId.complete(_channel.invokeMethod('createMap', <String, dynamic>{
+    _textureId.complete(_channel.invokeMethod('map#create', <String, dynamic>{
       'width': size.width,
       'height': size.height,
       'options': options._toJson(),
@@ -232,7 +283,7 @@
   @override
   Future<void> show(Offset offset) async {
     final int id = await _textureId.future;
-    _channel.invokeMethod('showMapOverlay', <String, dynamic>{
+    _channel.invokeMethod('map#show', <String, dynamic>{
       'map': id,
       'x': offset.dx,
       'y': offset.dy,
@@ -242,7 +293,7 @@
   @override
   Future<void> hide() async {
     final int id = await _textureId.future;
-    _channel.invokeMethod('hideMapOverlay', <String, dynamic>{
+    _channel.invokeMethod('map#hide', <String, dynamic>{
       'map': id,
     });
   }
@@ -250,13 +301,25 @@
   @override
   Future<void> dispose() async {
     final int id = await _textureId.future;
-    _channel.invokeMethod('disposeMap', <String, dynamic>{
+    _channel.invokeMethod('map#dispose', <String, dynamic>{
       'map': id,
     });
   }
 }
 
-/// A Widget covered by a GoogleMaps platform overlay.
+/// 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;
 
@@ -283,7 +346,7 @@
   Widget build(BuildContext context) {
     return new SizedBox(
       child: new FutureBuilder<int>(
-        future: widget.controller.mapController.id,
+        future: widget.controller.mapController._id,
         builder: (_, AsyncSnapshot<int> snapshot) {
           if (snapshot.hasData) {
             return new Texture(textureId: snapshot.data);
diff --git a/packages/google_maps_flutter/lib/src/location.dart b/packages/google_maps_flutter/lib/src/location.dart
index f459d70..9a2b4a1 100644
--- a/packages/google_maps_flutter/lib/src/location.dart
+++ b/packages/google_maps_flutter/lib/src/location.dart
@@ -6,12 +6,25 @@
 
 /// A pair of latitude and longitude coordinates, stored as degrees.
 class LatLng {
+  /// The latitude in degrees between -90.0 and 90.0, both inclusive.
   final double latitude;
+
+  /// The longitude in degrees between -180.0 (inclusive) and 180.0 (exclusive).
   final double longitude;
 
-  const LatLng(this.latitude, this.longitude)
+  /// Creates a geographical location specified in degrees [latitude] and
+  /// [longitude].
+  ///
+  /// The latitude is clamped to the inclusive interval from -90.0 to +90.0.
+  ///
+  /// The longitude is normalized to the half-open interval from -180.0
+  /// (inclusive) to +180.0 (exclusive)
+  const LatLng(double latitude, double longitude)
       : assert(latitude != null),
-        assert(longitude != null);
+        assert(longitude != null),
+        latitude =
+            (latitude < -90.0 ? -90.0 : (90.0 < latitude ? 90.0 : latitude)),
+        longitude = (longitude + 180.0) % 360.0 - 180.0;
 
   dynamic _toJson() {
     return <double>[latitude, longitude];
@@ -39,13 +52,28 @@
 }
 
 /// A latitude/longitude aligned rectangle.
+///
+/// The rectangle conceptually includes all points (lat, lng) where
+/// * lat ∈ [`southwest.latitude`, `northeast.latitude`]
+/// * lng ∈ [`southwest.longitude`, `northeast.longitude`],
+///   if `southwest.longitude` ≤ `northeast.longitude`,
+/// * lng ∈ [-180, `northeast.longitude`] ∪ [`southwest.longitude`, 180[,
+///   if `northeast.longitude` < `southwest.longitude`
 class LatLngBounds {
+  /// The southwest corner of the rectangle.
   final LatLng southwest;
+
+  /// The northeast corner of the rectangle.
   final LatLng northeast;
 
-  const LatLngBounds({@required this.southwest, @required this.northeast})
+  /// Creates geographical bounding box with the specified corners.
+  ///
+  /// The latitude of the southwest corner cannot be larger than the
+  /// latitude of the northeast corner.
+  LatLngBounds({@required this.southwest, @required this.northeast})
       : assert(southwest != null),
-        assert(northeast != null);
+        assert(northeast != null),
+        assert(southwest.latitude <= northeast.latitude);
 
   dynamic _toJson() {
     return <dynamic>[southwest._toJson(), northeast._toJson()];
diff --git a/packages/google_maps_flutter/lib/src/marker.dart b/packages/google_maps_flutter/lib/src/marker.dart
index 4552cf8..c830adc 100644
--- a/packages/google_maps_flutter/lib/src/marker.dart
+++ b/packages/google_maps_flutter/lib/src/marker.dart
@@ -4,32 +4,25 @@
 
 part of google_maps_flutter;
 
-/// An icon placed at a particular point on the map's surface. A marker icon is
-/// drawn oriented against the device's screen rather than the map's surface;
-/// that is, it will not necessarily change orientation due to map rotations,
-/// tilting, or zooming.
+/// An icon placed at a particular geographical location on the map's surface.
+/// A marker icon is drawn oriented against the device's screen rather than the
+/// map's surface; that is, it will not necessarily change orientation due to
+/// map rotations, tilting, or zooming.
 ///
-/// Markers are owned by a single [GoogleMapController] which fires change
-/// events when markers are added, updated, or removed.
+/// Markers are owned by a single [GoogleMapController] which fires events
+/// as markers are added, updated, tapped, and removed.
 class Marker {
-  Marker._(this._mapController, this.id, this._options);
+  @visibleForTesting
+  Marker(this._id, this._options);
 
-  final GoogleMapController _mapController;
-  final String id;
+  final String _id;
   MarkerOptions _options;
 
-  Future<void> remove() {
-    return _mapController._removeMarker(this);
-  }
-
-  Future<void> update(MarkerOptions changes) {
-    return _mapController._updateMarker(this, changes);
-  }
-
-  /// The configuration options most recently applied programmatically.
+  /// The marker configuration options most recently applied programmatically
+  /// via the map controller.
   ///
   /// The returned value does not reflect any changes made to the marker through
-  /// touch events. Add listeners to track those.
+  /// touch events. Add listeners to the owning map controller to track those.
   MarkerOptions get options => _options;
 }
 
@@ -44,9 +37,17 @@
 class InfoWindowText {
   const InfoWindowText(this.title, this.snippet);
 
+  /// Text labels specifying that no text is to be displayed.
   static const InfoWindowText noText = const InfoWindowText(null, null);
 
+  /// Text displayed in an info window when the user taps the marker.
+  ///
+  /// A null value means no title.
   final String title;
+
+  /// Additional text displayed below the [title].
+  ///
+  /// A null value means no additional text.
   final String snippet;
 
   dynamic _toJson() => <dynamic>[title, snippet];
@@ -55,21 +56,66 @@
 /// Configuration options for [Marker] instances.
 ///
 /// When used to change configuration, null values will be interpreted as
-/// "do not change this configuration item".
+/// "do not change this configuration option".
 class MarkerOptions {
+  /// The opacity of the marker, between 0.0 and 1.0 inclusive.
+  ///
+  /// 0.0 means fully transparent, 1.0 means fully opaque.
   final double alpha;
+
+  /// The icon image point that will be placed at the [position] of the marker.
+  ///
+  /// The image point is specified in normalized coordinates: An anchor of
+  /// (0.0, 0.0) means the top left corner of the image. An anchor
+  /// of (1.0, 1.0) means the bottom right corner of the image.
   final Offset anchor;
+
+  /// True if the marker icon consumes tap events. If not, the map will perform
+  /// default tap handling by centering the map on the marker and displaying its
+  /// info window.
   final bool consumeTapEvents;
+
+  /// True if the marker is draggable by user touch events.
   final bool draggable;
+
+  /// True if the marker is rendered flatly against the surface of the Earth, so
+  /// that it will rotate and tilt along with map camera movements.
   final bool flat;
+
+  /// A description of the bitmap used to draw the marker icon.
   final BitmapDescriptor icon;
+
+  /// The icon image point that will be the anchor of the info window when
+  /// displayed.
+  ///
+  /// The image point is specified in normalized coordinates: An anchor of
+  /// (0.0, 0.0) means the top left corner of the image. An anchor
+  /// of (1.0, 1.0) means the bottom right corner of the image.
   final Offset infoWindowAnchor;
+
+  /// Text content for the info window.
   final InfoWindowText infoWindowText;
+
+  /// Geographical location of the marker.
   final LatLng position;
+
+  /// Rotation of the marker image in degrees clockwise from the [anchor] point.
   final double rotation;
+
+  /// True if the marker is visible.
   final bool visible;
+
+  /// The z-index of the marker, used to determine relative drawing order of
+  /// map overlays.
+  ///
+  /// Overlays are drawn in order of z-index, so that lower values means drawn
+  /// earlier, and thus appearing to be closer to the surface of the Earth.
   final double zIndex;
 
+  /// Creates a set of marker configuration options.
+  ///
+  /// By default, every non-specified field is null, meaning no desire to change
+  /// marker defaults or current configuration.
   const MarkerOptions({
     this.alpha,
     this.anchor,
@@ -83,9 +129,24 @@
     this.rotation,
     this.visible,
     this.zIndex,
-  });
+  }) : assert(alpha == null || (0.0 <= alpha && alpha <= 1.0));
 
-  static const MarkerOptions defaultOptions = const MarkerOptions(
+  /// Default marker options.
+  ///
+  /// Specifies a marker that
+  /// * is fully opaque; [alpha] is 1.0
+  /// * uses icon bottom center to indicate map position; [anchor] is (0.5, 1.0)
+  /// * has default tap handling; [consumeTapEvents] is false
+  /// * is stationary; [draggable] is false
+  /// * is drawn against the screen, not the map; [flat] is false
+  /// * has a default icon; [icon] is `BitmapDescriptor.defaultMarker`
+  /// * anchors the info window at top center; [infoWindowAnchor] is (0.5, 0.0)
+  /// * has no info window text; [infoWindowText] is `InfoWindowText.noText`
+  /// * is positioned at 0, 0; [position] is `LatLng(0.0, 0.0)`
+  /// * has an axis-aligned icon; [rotation] is 0.0
+  /// * is visible; [visible] is true
+  /// * is placed at the base of the drawing order; [zIndex] is 0.0
+  static const MarkerOptions defaultOptions = MarkerOptions(
     alpha: 1.0,
     anchor: const Offset(0.5, 1.0),
     consumeTapEvents: false,
@@ -94,12 +155,20 @@
     icon: BitmapDescriptor.defaultMarker,
     infoWindowAnchor: const Offset(0.5, 0.0),
     infoWindowText: InfoWindowText.noText,
+    position: LatLng(0.0, 0.0),
     rotation: 0.0,
     visible: true,
     zIndex: 0.0,
   );
 
-  MarkerOptions _updateWith(MarkerOptions changes) {
+  /// Creates a new options object whose values are the same as this instance,
+  /// unless overwritten by the specified [changes].
+  ///
+  /// Returns this instance, if [changes] is null.
+  MarkerOptions copyWith(MarkerOptions changes) {
+    if (changes == null) {
+      return this;
+    }
     return new MarkerOptions(
       alpha: changes.alpha ?? alpha,
       anchor: changes.anchor ?? anchor,
diff --git a/packages/google_maps_flutter/lib/src/platform_overlay.dart b/packages/google_maps_flutter/lib/src/platform_overlay.dart
index f56d4bb..5f61e30 100644
--- a/packages/google_maps_flutter/lib/src/platform_overlay.dart
+++ b/packages/google_maps_flutter/lib/src/platform_overlay.dart
@@ -4,25 +4,48 @@
 
 part of google_maps_flutter;
 
-/// Controller of platform overlays, supporting a very limited form
-/// of compositing with Flutter Widgets.
+/// 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-specific views that are
-/// created, shown on top of the Flutter view, or hidden below it,
-/// under control of the Flutter app. The platform overlay is
-/// typically placed on top of a [Texture] widget acting as stand-in
-/// while Flutter movement or transformations are ongoing.
+/// 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:
 ///
-/// Overlays are attached to a [BuildContext] when used in a Widget and
-/// are deactivated when the ambient ModalRoute (if any) is not on top of the
-/// navigator stack.
+/// * 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)
 ///
-/// *Warning*: Platform overlays cannot be freely composed with
-/// over widgets.
+/// 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.
 ///
-/// Limitations and caveats:
+/// *Warning*: Platform overlays cannot be freely composed with other widgets.
 ///
-/// * TODO(mravn)
+/// 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;
@@ -184,11 +207,6 @@
     }
   }
 
-  @override
-  void didRemove(Route<dynamic> route, Route<dynamic> previousRoute) {
-    // TODO(mravn)
-  }
-
   void dispose() {
     if (!_disposed) {
       overlay.dispose();
@@ -204,12 +222,14 @@
   }
 }
 
-/// Platform overlay.
+/// 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
-  /// [showOverlay].
+  /// [show].
   Future<int> create(Size size);
 
   /// Shows the platform view at the specified [offset].
diff --git a/packages/google_maps_flutter/lib/src/ui.dart b/packages/google_maps_flutter/lib/src/ui.dart
index 496d45b..fcc870e 100644
--- a/packages/google_maps_flutter/lib/src/ui.dart
+++ b/packages/google_maps_flutter/lib/src/ui.dart
@@ -5,41 +5,61 @@
 part of google_maps_flutter;
 
 /// Type of map tiles to display.
-///
-/// Enum constants must be indexed to match the corresponding int constants of
-/// the platform APIs, see
-/// <https://developers.google.com/android/reference/com/google/android/gms/maps/GoogleMap.html#MAP_TYPE_NORMAL>
+// Enum constants must be indexed to match the corresponding int constants of
+// the Android platform API, see
+// <https://developers.google.com/android/reference/com/google/android/gms/maps/GoogleMap.html#MAP_TYPE_NORMAL>
 enum MapType {
+  /// Do not display map tiles.
   none,
+
+  /// Normal tiles (traffic and labels, subtle terrain information).
   normal,
+
+  /// Satellite imaging tiles (aerial photos)
   satellite,
+
+  /// Terrain tiles (indicates type and height of terrain)
   terrain,
+
+  /// Hybrid tiles (satellite images with some labels/overlays)
   hybrid,
 }
 
 /// Bounds for the map camera target.
+// Used with [GoogleMapOptions] to wrap a [LatLngBounds] value. This allows
+// distinguishing between specifying an unbounded target (null `LatLngBounds`)
+// from not specifying anything (null `CameraTargetBounds`).
 class CameraTargetBounds {
+  /// Creates a camera target bounds with the specified bounding box, or null
+  /// to indicate that the camera target is not bounded.
   const CameraTargetBounds(this.bounds);
 
-  /// The current bounds or null, if the camera target is unbounded.
+  /// The geographical bounding box for the map camera target.
+  ///
+  /// A null value means the camera target is unbounded.
   final LatLngBounds bounds;
 
+  /// Unbounded camera target.
   static const CameraTargetBounds unbounded = const CameraTargetBounds(null);
 
   dynamic _toJson() => <dynamic>[bounds?._toJson()];
 }
 
 /// Preferred bounds for map camera zoom level.
+// Used with [GoogleMapOptions] to wrap min and max zoom. This allows
+// distinguishing between specifying unbounded zooming (null `minZoom` and
+// `maxZoom`) from not specifying anything (null `MinMaxZoomPreference`).
 class MinMaxZoomPreference {
   const MinMaxZoomPreference(this.minZoom, this.maxZoom)
       : assert(minZoom == null || maxZoom == null || minZoom <= maxZoom);
 
-  /// The current minimum zoom level or null, if unbounded from below.
+  /// The preferred minimum zoom level or null, if unbounded from below.
   final double minZoom;
 
-  /// The current maximum zoom level or null, if unbounded from above.
+  /// The preferred maximum zoom level or null, if unbounded from above.
   final double maxZoom;
 
+  /// Unbounded zooming.
   static const MinMaxZoomPreference unbounded =
       const MinMaxZoomPreference(null, null);
 
@@ -49,20 +69,50 @@
 /// Configuration options for the GoogleMaps user interface.
 ///
 /// When used to change configuration, null values will be interpreted as
-/// "do not change this configuration item".
+/// "do not change this configuration option".
 class GoogleMapOptions {
+  /// The desired position of the map camera.
+  ///
+  /// This field is used to indicate initial camera position and to update that
+  /// position programmatically along with other changes to the map user
+  /// interface. It does not track the camera position through animations or
+  /// reflect movements caused by user touch events.
   final CameraPosition cameraPosition;
+
+  /// True if the map should show a compass when rotated.
   final bool compassEnabled;
+
+  /// Geographical bounding box for the camera target.
   final CameraTargetBounds cameraTargetBounds;
+
+  /// Type of map tiles to be rendered.
   final MapType mapType;
+
+  /// Preferred bounds for the camera zoom level.
+  ///
+  /// Actual bounds depend on map data and device.
   final MinMaxZoomPreference minMaxZoomPreference;
+
+  /// True if the map view should respond to rotate gestures.
   final bool rotateGesturesEnabled;
+
+  /// True if the map view should respond to scroll gestures.
   final bool scrollGesturesEnabled;
+
+  /// True if the map view should respond to tilt gestures.
   final bool tiltGesturesEnabled;
+
+  /// True if the map view should relay camera move events to Flutter.
   final bool trackCameraPosition;
+
+  /// True if the map view should respond to zoom gestures.
   final bool zoomGesturesEnabled;
 
-  const GoogleMapOptions({
+  /// Creates a set of map user interface configuration options.
+  ///
+  /// By default, every non-specified field is null, meaning no desire to change
+  /// user interface defaults or current configuration.
+  GoogleMapOptions({
     this.cameraPosition,
     this.compassEnabled,
     this.cameraTargetBounds,
@@ -75,8 +125,22 @@
     this.zoomGesturesEnabled,
   });
 
-  static const GoogleMapOptions defaultOptions = const GoogleMapOptions(
+  /// Default user interface options.
+  ///
+  /// Specifies a map view that
+  /// * displays a compass when rotated; [compassEnabled] is true
+  /// * positions the camera at 0,0; [cameraPosition] has target `LatLng(0.0, 0.0)`
+  /// * does not bound the camera target; [cameraTargetBounds] is `CameraTargetBounds.unbounded`
+  /// * uses normal map tiles; [mapType] is `MapType.normal`
+  /// * does not bound zooming; [minMaxZoomPreference] is `MinMaxZoomPreference.unbounded`
+  /// * responds to rotate gestures; [rotateGesturesEnabled] is true
+  /// * responds to scroll gestures; [scrollGesturesEnabled] is true
+  /// * responds to tilt gestures; [tiltGesturesEnabled] is true
+  /// * is silent about camera movement; [trackCameraPosition] is false
+  /// * responds to zoom gestures; [zoomGesturesEnabled] is true
+  static final GoogleMapOptions defaultOptions = GoogleMapOptions(
     compassEnabled: true,
+    cameraPosition: const CameraPosition(target: LatLng(0.0, 0.0)),
     cameraTargetBounds: CameraTargetBounds.unbounded,
     mapType: MapType.normal,
     minMaxZoomPreference: MinMaxZoomPreference.unbounded,
@@ -87,7 +151,14 @@
     zoomGesturesEnabled: true,
   );
 
-  GoogleMapOptions _updateWith(GoogleMapOptions change) {
+  /// Creates a new options object whose values are the same as this instance,
+  /// unless overwritten by the specified [changes].
+  ///
+  /// Returns this instance, if [changes] is null.
+  GoogleMapOptions copyWith(GoogleMapOptions change) {
+    if (change == null) {
+      return this;
+    }
     return new GoogleMapOptions(
       cameraPosition: change.cameraPosition ?? cameraPosition,
       compassEnabled: change.compassEnabled ?? compassEnabled,