[google_maps_flutter]ChangeNotifier is replaced with granular callbacks (#1302)

* ChangeNotifier is replaced with granular callbacks

This facilitates better interaction with the map
than adding a listener to the controller.

* Make docs better

* Remove camera update call backs for map and marker update

* Fix up some links and caps.

* Update changelog and pubspec.

* fix unused var
diff --git a/packages/google_maps_flutter/CHANGELOG.md b/packages/google_maps_flutter/CHANGELOG.md
index 618af9b..36915e5 100644
--- a/packages/google_maps_flutter/CHANGELOG.md
+++ b/packages/google_maps_flutter/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 0.4.0
+
+* Change events are call backs on GoogleMap widget.
+* GoogleMapController no longer handles change events.
+
 ## 0.3.0+3
 
 * Update Android play-services-maps to 16.1.0
diff --git a/packages/google_maps_flutter/example/lib/map_ui.dart b/packages/google_maps_flutter/example/lib/map_ui.dart
index 993bddd..486d87b 100644
--- a/packages/google_maps_flutter/example/lib/map_ui.dart
+++ b/packages/google_maps_flutter/example/lib/map_ui.dart
@@ -36,8 +36,8 @@
     zoom: 11.0,
   );
 
-  GoogleMapController mapController;
   CameraPosition _position = _kInitialPosition;
+  bool _isMapCreated = false;
   bool _isMoving = false;
   bool _compassEnabled = true;
   CameraTargetBounds _cameraTargetBounds = CameraTargetBounds.unbounded;
@@ -54,20 +54,8 @@
     super.initState();
   }
 
-  void _onMapChanged() {
-    setState(() {
-      _extractMapInfo();
-    });
-  }
-
-  void _extractMapInfo() {
-    _position = mapController.cameraPosition;
-    _isMoving = mapController.isCameraMoving;
-  }
-
   @override
   void dispose() {
-    mapController.removeListener(_onMapChanged);
     super.dispose();
   }
 
@@ -197,6 +185,7 @@
       tiltGesturesEnabled: _tiltGesturesEnabled,
       zoomGesturesEnabled: _zoomGesturesEnabled,
       myLocationEnabled: _myLocationEnabled,
+      onCameraMove: _updateCameraPosition,
     );
 
     final List<Widget> columnChildren = <Widget>[
@@ -212,7 +201,7 @@
       ),
     ];
 
-    if (mapController != null) {
+    if (_isMapCreated) {
       columnChildren.add(
         Expanded(
           child: ListView(
@@ -245,10 +234,15 @@
     );
   }
 
+  void _updateCameraPosition(CameraPosition position) {
+    setState(() {
+      _position = position;
+    });
+  }
+
   void onMapCreated(GoogleMapController controller) {
-    mapController = controller;
-    mapController.addListener(_onMapChanged);
-    _extractMapInfo();
-    setState(() {});
+    setState(() {
+      _isMapCreated = true;
+    });
   }
 }
diff --git a/packages/google_maps_flutter/lib/src/controller.dart b/packages/google_maps_flutter/lib/src/controller.dart
index ecfc4d8..3d56400 100644
--- a/packages/google_maps_flutter/lib/src/controller.dart
+++ b/packages/google_maps_flutter/lib/src/controller.dart
@@ -5,22 +5,13 @@
 part of google_maps_flutter;
 
 /// Controller for a single GoogleMap instance running on the host platform.
-///
-/// Change listeners are notified upon changes to any of
-///
-/// * the [options] property
-/// * the [isCameraMoving] property
-/// * the [cameraPosition] property
-///
-/// Listeners are notified after changes have been applied on the platform side.
-class GoogleMapController extends ChangeNotifier {
+class GoogleMapController {
   GoogleMapController._(
     MethodChannel channel,
     CameraPosition initialCameraPosition,
     this._googleMapState,
   )   : assert(channel != null),
         _channel = channel {
-    _cameraPosition = initialCameraPosition;
     _channel.setMethodCallHandler(_handleMethodCall);
   }
 
@@ -45,30 +36,26 @@
 
   final MethodChannel _channel;
 
-  /// True if the map camera is currently moving.
-  bool get isCameraMoving => _isCameraMoving;
-  bool _isCameraMoving = false;
-
   final _GoogleMapState _googleMapState;
 
-  /// Returns the most recent camera position reported by the platform side.
-  /// Will be null, if [GoogleMap.trackCameraPosition] is false.
-  CameraPosition get cameraPosition => _cameraPosition;
-  CameraPosition _cameraPosition;
-
   Future<dynamic> _handleMethodCall(MethodCall call) async {
     switch (call.method) {
       case 'camera#onMoveStarted':
-        _isCameraMoving = true;
-        notifyListeners();
+        if (_googleMapState.widget.onCameraMoveStarted != null) {
+          _googleMapState.widget.onCameraMoveStarted();
+        }
         break;
       case 'camera#onMove':
-        _cameraPosition = CameraPosition.fromMap(call.arguments['position']);
-        notifyListeners();
+        if (_googleMapState.widget.onCameraMove != null) {
+          _googleMapState.widget.onCameraMove(
+            CameraPosition.fromMap(call.arguments['position']),
+          );
+        }
         break;
       case 'camera#onIdle':
-        _isCameraMoving = false;
-        notifyListeners();
+        if (_googleMapState.widget.onCameraIdle != null) {
+          _googleMapState.widget.onCameraIdle();
+        }
         break;
       case 'marker#onTap':
         _googleMapState.onMarkerTap(call.arguments['markerId']);
@@ -92,14 +79,12 @@
     // TODO(amirh): remove this on when the invokeMethod update makes it to stable Flutter.
     // https://github.com/flutter/flutter/issues/26431
     // ignore: strong_mode_implicit_dynamic_method
-    final dynamic json = await _channel.invokeMethod(
+    await _channel.invokeMethod(
       'map#update',
       <String, dynamic>{
         'options': optionsUpdate,
       },
     );
-    _cameraPosition = CameraPosition.fromMap(json);
-    notifyListeners();
   }
 
   /// Updates marker configuration.
@@ -117,7 +102,6 @@
       'markers#update',
       markerUpdates._toMap(),
     );
-    notifyListeners();
   }
 
   /// Starts an animated change of the map camera position.
diff --git a/packages/google_maps_flutter/lib/src/google_map.dart b/packages/google_maps_flutter/lib/src/google_map.dart
index 365ac0c..fca62f1 100644
--- a/packages/google_maps_flutter/lib/src/google_map.dart
+++ b/packages/google_maps_flutter/lib/src/google_map.dart
@@ -6,6 +6,15 @@
 
 typedef void MapCreatedCallback(GoogleMapController controller);
 
+/// Callback that receives updates to the camera position.
+///
+/// This callback is triggered when the platform Google Map
+/// registers a camera movement. This will be called with null if
+/// [GoogleMap.trackCameraPosition] is false.
+///
+/// This is used in [GoogleMap.onCameraMove] and [GoogleMap.onMapOptionsUpdate].
+typedef void CameraPositionCallback(CameraPosition position);
+
 class GoogleMap extends StatefulWidget {
   const GoogleMap({
     @required this.initialCameraPosition,
@@ -22,6 +31,9 @@
     this.trackCameraPosition = false,
     this.myLocationEnabled = false,
     this.markers,
+    this.onCameraMoveStarted,
+    this.onCameraMove,
+    this.onCameraIdle,
   }) : assert(initialCameraPosition != null);
 
   final MapCreatedCallback onMapCreated;
@@ -58,9 +70,34 @@
   /// True if the map view should relay camera move events to Flutter.
   final bool trackCameraPosition;
 
-  // Markers to be placed on the map.
+  /// Markers to be placed on the map.
   final Set<Marker> markers;
 
+  /// Called when the camera starts moving.
+  ///
+  /// This can be initiated by the following:
+  /// 1. Non-gesture animation initiated in response to user actions.
+  ///    For example: zoom buttons, my location button, or marker clicks.
+  /// 2. Programmatically initiated animation.
+  /// 3. Camera motion initiated in response to user gestures on the map.
+  ///    For example: pan, tilt, pinch to zoom, or rotate.
+  ///
+  /// Note: This is callback is called even if [trackCameraPosition] is false.
+  final VoidCallback onCameraMoveStarted;
+
+  /// Called repeatedly as the camera continues to move after an
+  /// onCameraMoveStarted call.
+  ///
+  /// This may be called as often as once every frame and should
+  /// not perform expensive operations.
+  ///
+  /// This is only called if [trackCameraPosition] is true.
+  final CameraPositionCallback onCameraMove;
+
+  /// Called when camera movement has ended, there are no pending
+  /// animations and the user has stopped interacting with the map.
+  final VoidCallback onCameraIdle;
+
   /// True if a "My Location" layer should be shown on the map.
   ///
   /// This layer includes a location indicator at the current device location,
diff --git a/packages/google_maps_flutter/pubspec.yaml b/packages/google_maps_flutter/pubspec.yaml
index 4aadb3f..81169e4 100644
--- a/packages/google_maps_flutter/pubspec.yaml
+++ b/packages/google_maps_flutter/pubspec.yaml
@@ -2,7 +2,7 @@
 description: A Flutter plugin for integrating Google Maps in iOS and Android applications.
 author: Flutter Team <flutter-dev@googlegroups.com>
 homepage: https://github.com/flutter/plugins/tree/master/packages/google_maps_flutter
-version: 0.3.0+3
+version: 0.4.0
 
 dependencies:
   flutter: