[google_maps_flutter_platfomr_interface] Add Marker drag events (#2653)
This PR adds onDragStart(LatLng) and onDrag(LatLng) events to Marker objects, in addition to the already existing onDragEnd.
diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter_platform_interface/CHANGELOG.md
index 5d361d8..464c33e 100644
--- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/CHANGELOG.md
+++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 2.1.2
+
+* Add additional marker drag events
+
## 2.1.1
* Method `buildViewWithTextDirection` has been added to the platform interface.
diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/events/map_event.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/events/map_event.dart
index be42648..614cbe8 100644
--- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/events/map_event.dart
+++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/events/map_event.dart
@@ -102,6 +102,26 @@
InfoWindowTapEvent(int mapId, MarkerId markerId) : super(mapId, markerId);
}
+/// An event fired when a [Marker] is starting to be dragged to a new [LatLng].
+class MarkerDragStartEvent extends _PositionedMapEvent<MarkerId> {
+ /// Build a MarkerDragStart Event triggered from the map represented by `mapId`.
+ ///
+ /// The `position` on this event is the [LatLng] on which the Marker was picked up from.
+ /// The `value` of this event is a [MarkerId] object that represents the Marker.
+ MarkerDragStartEvent(int mapId, LatLng position, MarkerId markerId)
+ : super(mapId, position, markerId);
+}
+
+/// An event fired when a [Marker] is being dragged to a new [LatLng].
+class MarkerDragEvent extends _PositionedMapEvent<MarkerId> {
+ /// Build a MarkerDrag Event triggered from the map represented by `mapId`.
+ ///
+ /// The `position` on this event is the [LatLng] on which the Marker was dragged to.
+ /// The `value` of this event is a [MarkerId] object that represents the Marker.
+ MarkerDragEvent(int mapId, LatLng position, MarkerId markerId)
+ : super(mapId, position, markerId);
+}
+
/// An event fired when a [Marker] is dragged to a new [LatLng].
class MarkerDragEndEvent extends _PositionedMapEvent<MarkerId> {
/// Build a MarkerDragEnd Event triggered from the map represented by `mapId`.
diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/method_channel/method_channel_google_maps_flutter.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/method_channel/method_channel_google_maps_flutter.dart
index 2b9c71e..99f4fdd 100644
--- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/method_channel/method_channel_google_maps_flutter.dart
+++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/method_channel/method_channel_google_maps_flutter.dart
@@ -125,6 +125,16 @@
}
@override
+ Stream<MarkerDragStartEvent> onMarkerDragStart({required int mapId}) {
+ return _events(mapId).whereType<MarkerDragStartEvent>();
+ }
+
+ @override
+ Stream<MarkerDragEvent> onMarkerDrag({required int mapId}) {
+ return _events(mapId).whereType<MarkerDragEvent>();
+ }
+
+ @override
Stream<MarkerDragEndEvent> onMarkerDragEnd({required int mapId}) {
return _events(mapId).whereType<MarkerDragEndEvent>();
}
@@ -174,6 +184,20 @@
MarkerId(call.arguments['markerId']),
));
break;
+ case 'marker#onDragStart':
+ _mapEventStreamController.add(MarkerDragStartEvent(
+ mapId,
+ LatLng.fromJson(call.arguments['position'])!,
+ MarkerId(call.arguments['markerId']),
+ ));
+ break;
+ case 'marker#onDrag':
+ _mapEventStreamController.add(MarkerDragEvent(
+ mapId,
+ LatLng.fromJson(call.arguments['position'])!,
+ MarkerId(call.arguments['markerId']),
+ ));
+ break;
case 'marker#onDragEnd':
_mapEventStreamController.add(MarkerDragEndEvent(
mapId,
diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/platform_interface/google_maps_flutter_platform.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/platform_interface/google_maps_flutter_platform.dart
index 2bb0ab2..08b4872 100644
--- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/platform_interface/google_maps_flutter_platform.dart
+++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/platform_interface/google_maps_flutter_platform.dart
@@ -304,6 +304,16 @@
}
/// A [Marker] has been dragged to a different [LatLng] position.
+ Stream<MarkerDragStartEvent> onMarkerDragStart({required int mapId}) {
+ throw UnimplementedError('onMarkerDragEnd() has not been implemented.');
+ }
+
+ /// A [Marker] has been dragged to a different [LatLng] position.
+ Stream<MarkerDragEvent> onMarkerDrag({required int mapId}) {
+ throw UnimplementedError('onMarkerDragEnd() has not been implemented.');
+ }
+
+ /// A [Marker] has been dragged to a different [LatLng] position.
Stream<MarkerDragEndEvent> onMarkerDragEnd({required int mapId}) {
throw UnimplementedError('onMarkerDragEnd() has not been implemented.');
}
diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/marker.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/marker.dart
index 0d1b780..52255f8 100644
--- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/marker.dart
+++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/lib/src/types/marker.dart
@@ -147,6 +147,8 @@
this.visible = true,
this.zIndex = 0.0,
this.onTap,
+ this.onDrag,
+ this.onDragStart,
this.onDragEnd,
}) : assert(alpha == null || (0.0 <= alpha && alpha <= 1.0));
@@ -207,9 +209,15 @@
/// Callbacks to receive tap events for markers placed on this map.
final VoidCallback? onTap;
+ /// Signature reporting the new [LatLng] at the start of a drag event.
+ final ValueChanged<LatLng>? onDragStart;
+
/// Signature reporting the new [LatLng] at the end of a drag event.
final ValueChanged<LatLng>? onDragEnd;
+ /// Signature reporting the new [LatLng] during the drag event.
+ final ValueChanged<LatLng>? onDrag;
+
/// Creates a new [Marker] object whose values are the same as this instance,
/// unless overwritten by the specified parameters.
Marker copyWith({
@@ -225,6 +233,8 @@
bool? visibleParam,
double? zIndexParam,
VoidCallback? onTapParam,
+ ValueChanged<LatLng>? onDragStartParam,
+ ValueChanged<LatLng>? onDragParam,
ValueChanged<LatLng>? onDragEndParam,
}) {
return Marker(
@@ -241,6 +251,8 @@
visible: visibleParam ?? visible,
zIndex: zIndexParam ?? zIndex,
onTap: onTapParam ?? onTap,
+ onDragStart: onDragStartParam ?? onDragStart,
+ onDrag: onDragParam ?? onDrag,
onDragEnd: onDragEndParam ?? onDragEnd,
);
}
@@ -300,6 +312,7 @@
return 'Marker{markerId: $markerId, alpha: $alpha, anchor: $anchor, '
'consumeTapEvents: $consumeTapEvents, draggable: $draggable, flat: $flat, '
'icon: $icon, infoWindow: $infoWindow, position: $position, rotation: $rotation, '
- 'visible: $visible, zIndex: $zIndex, onTap: $onTap}';
+ 'visible: $visible, zIndex: $zIndex, onTap: $onTap, onDragStart: $onDragStart, '
+ 'onDrag: $onDrag, onDragEnd: $onDragEnd}';
}
}
diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/pubspec.yaml b/packages/google_maps_flutter/google_maps_flutter_platform_interface/pubspec.yaml
index 1dc73f4..2a2c9cf 100644
--- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/pubspec.yaml
+++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/pubspec.yaml
@@ -4,7 +4,7 @@
issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+maps%22
# NOTE: We strongly prefer non-breaking changes, even at the expense of a
# less-clean API. See https://flutter.dev/go/platform-interface-breaking-changes
-version: 2.1.1
+version: 2.1.2
environment:
sdk: '>=2.12.0 <3.0.0'
@@ -19,6 +19,7 @@
stream_transform: ^2.0.0
dev_dependencies:
+ async: ^2.5.0
flutter_test:
sdk: flutter
mockito: ^5.0.0
diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/method_channel/method_channel_google_maps_flutter_test.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/method_channel/method_channel_google_maps_flutter_test.dart
index 19e81c9..176f702 100644
--- a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/method_channel/method_channel_google_maps_flutter_test.dart
+++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/method_channel/method_channel_google_maps_flutter_test.dart
@@ -5,8 +5,12 @@
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
+import 'package:google_maps_flutter_platform_interface/src/events/map_event.dart';
import 'package:google_maps_flutter_platform_interface/src/method_channel/method_channel_google_maps_flutter.dart';
import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
+import 'dart:async';
+
+import 'package:async/async.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
@@ -33,6 +37,15 @@
});
}
+ Future<void> sendPlatformMessage(
+ int mapId, String method, Map<dynamic, dynamic> data) async {
+ final ByteData byteData = const StandardMethodCodec()
+ .encodeMethodCall(MethodCall(method, data));
+ await TestDefaultBinaryMessengerBinding.instance!.defaultBinaryMessenger
+ .handlePlatformMessage(
+ "plugins.flutter.io/google_maps_$mapId", byteData, (data) {});
+ }
+
// Calls each method that uses invokeMethod with a return type other than
// void to ensure that the casting/nullability handling succeeds.
//
@@ -68,5 +81,46 @@
'map#takeSnapshot',
]);
});
+ test('markers send drag event to correct streams', () async {
+ const int mapId = 1;
+ final jsonMarkerDragStartEvent = <dynamic, dynamic>{
+ "mapId": mapId,
+ "markerId": "drag-start-marker",
+ "position": <double>[1.0, 1.0]
+ };
+ final jsonMarkerDragEvent = <dynamic, dynamic>{
+ "mapId": mapId,
+ "markerId": "drag-marker",
+ "position": <double>[1.0, 1.0]
+ };
+ final jsonMarkerDragEndEvent = <dynamic, dynamic>{
+ "mapId": mapId,
+ "markerId": "drag-end-marker",
+ "position": <double>[1.0, 1.0]
+ };
+
+ final MethodChannelGoogleMapsFlutter maps =
+ MethodChannelGoogleMapsFlutter();
+ maps.ensureChannelInitialized(mapId);
+
+ final StreamQueue<MarkerDragStartEvent> markerDragStartStream =
+ StreamQueue(maps.onMarkerDragStart(mapId: mapId));
+ final StreamQueue<MarkerDragEvent> markerDragStream =
+ StreamQueue(maps.onMarkerDrag(mapId: mapId));
+ final StreamQueue<MarkerDragEndEvent> markerDragEndStream =
+ StreamQueue(maps.onMarkerDragEnd(mapId: mapId));
+
+ await sendPlatformMessage(
+ mapId, "marker#onDragStart", jsonMarkerDragStartEvent);
+ await sendPlatformMessage(mapId, "marker#onDrag", jsonMarkerDragEvent);
+ await sendPlatformMessage(
+ mapId, "marker#onDragEnd", jsonMarkerDragEndEvent);
+
+ expect((await markerDragStartStream.next).value.value,
+ equals("drag-start-marker"));
+ expect((await markerDragStream.next).value.value, equals("drag-marker"));
+ expect((await markerDragEndStream.next).value.value,
+ equals("drag-end-marker"));
+ });
});
}
diff --git a/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/marker_test.dart b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/marker_test.dart
new file mode 100644
index 0000000..c8f6fa5
--- /dev/null
+++ b/packages/google_maps_flutter/google_maps_flutter_platform_interface/test/types/marker_test.dart
@@ -0,0 +1,167 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter/cupertino.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import 'package:google_maps_flutter_platform_interface/google_maps_flutter_platform_interface.dart';
+
+void main() {
+ TestWidgetsFlutterBinding.ensureInitialized();
+
+ group('$Marker', () {
+ test('constructor defaults', () {
+ final Marker marker = Marker(markerId: MarkerId("ABC123"));
+
+ expect(marker.alpha, equals(1.0));
+ expect(marker.anchor, equals(const Offset(0.5, 1.0)));
+ expect(marker.consumeTapEvents, equals(false));
+ expect(marker.draggable, equals(false));
+ expect(marker.flat, equals(false));
+ expect(marker.icon, equals(BitmapDescriptor.defaultMarker));
+ expect(marker.infoWindow, equals(InfoWindow.noText));
+ expect(marker.position, equals(const LatLng(0.0, 0.0)));
+ expect(marker.rotation, equals(0.0));
+ expect(marker.visible, equals(true));
+ expect(marker.zIndex, equals(0.0));
+ expect(marker.onTap, equals(null));
+ expect(marker.onDrag, equals(null));
+ expect(marker.onDragStart, equals(null));
+ expect(marker.onDragEnd, equals(null));
+ });
+ test('constructor alpha is >= 0.0 and <= 1.0', () {
+ final ValueSetter<double> initWithAlpha = (double alpha) {
+ Marker(markerId: MarkerId("ABC123"), alpha: alpha);
+ };
+ expect(() => initWithAlpha(-0.5), throwsAssertionError);
+ expect(() => initWithAlpha(0.0), isNot(throwsAssertionError));
+ expect(() => initWithAlpha(0.5), isNot(throwsAssertionError));
+ expect(() => initWithAlpha(1.0), isNot(throwsAssertionError));
+ expect(() => initWithAlpha(100), throwsAssertionError);
+ });
+
+ test('toJson', () {
+ final BitmapDescriptor testDescriptor =
+ BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueCyan);
+ final Marker marker = Marker(
+ markerId: MarkerId("ABC123"),
+ alpha: 0.12345,
+ anchor: Offset(100, 100),
+ consumeTapEvents: true,
+ draggable: true,
+ flat: true,
+ icon: testDescriptor,
+ infoWindow: InfoWindow(
+ title: "Test title",
+ snippet: "Test snippet",
+ anchor: Offset(100, 200),
+ ),
+ position: LatLng(50, 50),
+ rotation: 100,
+ visible: false,
+ zIndex: 100,
+ onTap: () {},
+ onDragStart: (LatLng latLng) {},
+ onDrag: (LatLng latLng) {},
+ onDragEnd: (LatLng latLng) {},
+ );
+
+ final Map<String, Object> json = marker.toJson() as Map<String, Object>;
+
+ expect(json, <String, Object>{
+ 'markerId': "ABC123",
+ 'alpha': 0.12345,
+ 'anchor': <double>[100, 100],
+ 'consumeTapEvents': true,
+ 'draggable': true,
+ 'flat': true,
+ 'icon': testDescriptor.toJson(),
+ 'infoWindow': <String, Object>{
+ 'title': "Test title",
+ 'snippet': "Test snippet",
+ 'anchor': <Object>[100.0, 200.0],
+ },
+ 'position': <double>[50, 50],
+ 'rotation': 100.0,
+ 'visible': false,
+ 'zIndex': 100.0,
+ });
+ });
+ test('clone', () {
+ final Marker marker = Marker(markerId: MarkerId("ABC123"));
+ final Marker clone = marker.clone();
+
+ expect(identical(clone, marker), isFalse);
+ expect(clone, equals(marker));
+ });
+ test('copyWith', () {
+ final Marker marker = Marker(markerId: MarkerId("ABC123"));
+
+ final BitmapDescriptor testDescriptor =
+ BitmapDescriptor.defaultMarkerWithHue(BitmapDescriptor.hueCyan);
+ final double testAlphaParam = 0.12345;
+ final Offset testAnchorParam = Offset(100, 100);
+ final bool testConsumeTapEventsParam = !marker.consumeTapEvents;
+ final bool testDraggableParam = !marker.draggable;
+ final bool testFlatParam = !marker.flat;
+ final BitmapDescriptor testIconParam = testDescriptor;
+ final InfoWindow testInfoWindowParam = InfoWindow(title: "Test");
+ final LatLng testPositionParam = LatLng(100, 100);
+ final double testRotationParam = 100;
+ final bool testVisibleParam = !marker.visible;
+ final double testZIndexParam = 100;
+ final List<String> log = [];
+
+ final copy = marker.copyWith(
+ alphaParam: testAlphaParam,
+ anchorParam: testAnchorParam,
+ consumeTapEventsParam: testConsumeTapEventsParam,
+ draggableParam: testDraggableParam,
+ flatParam: testFlatParam,
+ iconParam: testIconParam,
+ infoWindowParam: testInfoWindowParam,
+ positionParam: testPositionParam,
+ rotationParam: testRotationParam,
+ visibleParam: testVisibleParam,
+ zIndexParam: testZIndexParam,
+ onTapParam: () {
+ log.add("onTapParam");
+ },
+ onDragStartParam: (LatLng latLng) {
+ log.add("onDragStartParam");
+ },
+ onDragParam: (LatLng latLng) {
+ log.add("onDragParam");
+ },
+ onDragEndParam: (LatLng latLng) {
+ log.add("onDragEndParam");
+ },
+ );
+
+ expect(copy.alpha, equals(testAlphaParam));
+ expect(copy.anchor, equals(testAnchorParam));
+ expect(copy.consumeTapEvents, equals(testConsumeTapEventsParam));
+ expect(copy.draggable, equals(testDraggableParam));
+ expect(copy.flat, equals(testFlatParam));
+ expect(copy.icon, equals(testIconParam));
+ expect(copy.infoWindow, equals(testInfoWindowParam));
+ expect(copy.position, equals(testPositionParam));
+ expect(copy.rotation, equals(testRotationParam));
+ expect(copy.visible, equals(testVisibleParam));
+ expect(copy.zIndex, equals(testZIndexParam));
+
+ copy.onTap!();
+ expect(log, contains("onTapParam"));
+
+ copy.onDragStart!(LatLng(0, 1));
+ expect(log, contains("onDragStartParam"));
+
+ copy.onDrag!(LatLng(0, 1));
+ expect(log, contains("onDragParam"));
+
+ copy.onDragEnd!(LatLng(0, 1));
+ expect(log, contains("onDragEndParam"));
+ });
+ });
+}