Skip to content

Commit 53b1c5d

Browse files
committed
feat: implements GpsAvailabilityChangeEvent listener
1 parent 7912c32 commit 53b1c5d

File tree

14 files changed

+510
-95
lines changed

14 files changed

+510
-95
lines changed

android/src/main/kotlin/com/google/maps/flutter/navigation/GoogleMapsBaseMapView.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -544,7 +544,10 @@ abstract class GoogleMapsBaseMapView(
544544
// [GoogleMapsNavigationSessionManager] to fetch the location
545545
// using the [FusedLocationProviderApi].
546546
@Suppress("DEPRECATION")
547-
return getMap().myLocation
547+
val location = getMap().myLocation
548+
// Return null explicitly if location is not available to avoid NullPointerException
549+
// when the platform channel tries to serialize the Location object
550+
return if (location != null && location.provider != null) location else null
548551
}
549552

550553
fun getCameraPosition(): CameraPosition {

android/src/main/kotlin/com/google/maps/flutter/navigation/GoogleMapsNavigationSessionManager.kt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -758,8 +758,15 @@ constructor(
758758
navigationSessionEventApi.onGpsAvailabilityUpdate(isGpsAvailable) {}
759759
}
760760

761-
override fun onGpsAvailabilityChange(p0: GpsAvailabilityChangeEvent?) {
762-
TODO("Not yet implemented")
761+
override fun onGpsAvailabilityChange(event: GpsAvailabilityChangeEvent?) {
762+
if (event != null) {
763+
navigationSessionEventApi.onGpsAvailabilityChange(
764+
GpsAvailabilityChangeEventDto(
765+
isGpsLost = event.isGpsLost,
766+
isGpsValidForNavigation = event.isGpsValidForNavigation,
767+
)
768+
) {}
769+
}
763770
}
764771
}
765772
getRoadSnappedLocationProvider()?.addLocationListener(roadSnappedLocationListener)

android/src/main/kotlin/com/google/maps/flutter/navigation/messages.g.kt

Lines changed: 79 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1765,6 +1765,36 @@ data class SpeedingUpdatedEventDto(
17651765
override fun hashCode(): Int = toList().hashCode()
17661766
}
17671767

1768+
/** Generated class from Pigeon that represents data sent in messages. */
1769+
data class GpsAvailabilityChangeEventDto(
1770+
val isGpsLost: Boolean,
1771+
val isGpsValidForNavigation: Boolean,
1772+
) {
1773+
companion object {
1774+
fun fromList(pigeonVar_list: List<Any?>): GpsAvailabilityChangeEventDto {
1775+
val isGpsLost = pigeonVar_list[0] as Boolean
1776+
val isGpsValidForNavigation = pigeonVar_list[1] as Boolean
1777+
return GpsAvailabilityChangeEventDto(isGpsLost, isGpsValidForNavigation)
1778+
}
1779+
}
1780+
1781+
fun toList(): List<Any?> {
1782+
return listOf(isGpsLost, isGpsValidForNavigation)
1783+
}
1784+
1785+
override fun equals(other: Any?): Boolean {
1786+
if (other !is GpsAvailabilityChangeEventDto) {
1787+
return false
1788+
}
1789+
if (this === other) {
1790+
return true
1791+
}
1792+
return MessagesPigeonUtils.deepEquals(toList(), other.toList())
1793+
}
1794+
1795+
override fun hashCode(): Int = toList().hashCode()
1796+
}
1797+
17681798
/** Generated class from Pigeon that represents data sent in messages. */
17691799
data class SpeedAlertOptionsThresholdPercentageDto(
17701800
val percentage: Double,
@@ -2364,33 +2394,38 @@ private open class messagesPigeonCodec : StandardMessageCodec() {
23642394
}
23652395
182.toByte() -> {
23662396
return (readValue(buffer) as? List<Any?>)?.let {
2367-
SpeedAlertOptionsThresholdPercentageDto.fromList(it)
2397+
GpsAvailabilityChangeEventDto.fromList(it)
23682398
}
23692399
}
23702400
183.toByte() -> {
2371-
return (readValue(buffer) as? List<Any?>)?.let { SpeedAlertOptionsDto.fromList(it) }
2401+
return (readValue(buffer) as? List<Any?>)?.let {
2402+
SpeedAlertOptionsThresholdPercentageDto.fromList(it)
2403+
}
23722404
}
23732405
184.toByte() -> {
2406+
return (readValue(buffer) as? List<Any?>)?.let { SpeedAlertOptionsDto.fromList(it) }
2407+
}
2408+
185.toByte() -> {
23742409
return (readValue(buffer) as? List<Any?>)?.let {
23752410
RouteSegmentTrafficDataRoadStretchRenderingDataDto.fromList(it)
23762411
}
23772412
}
2378-
185.toByte() -> {
2413+
186.toByte() -> {
23792414
return (readValue(buffer) as? List<Any?>)?.let { RouteSegmentTrafficDataDto.fromList(it) }
23802415
}
2381-
186.toByte() -> {
2416+
187.toByte() -> {
23822417
return (readValue(buffer) as? List<Any?>)?.let { RouteSegmentDto.fromList(it) }
23832418
}
2384-
187.toByte() -> {
2419+
188.toByte() -> {
23852420
return (readValue(buffer) as? List<Any?>)?.let { LaneDirectionDto.fromList(it) }
23862421
}
2387-
188.toByte() -> {
2422+
189.toByte() -> {
23882423
return (readValue(buffer) as? List<Any?>)?.let { LaneDto.fromList(it) }
23892424
}
2390-
189.toByte() -> {
2425+
190.toByte() -> {
23912426
return (readValue(buffer) as? List<Any?>)?.let { StepInfoDto.fromList(it) }
23922427
}
2393-
190.toByte() -> {
2428+
191.toByte() -> {
23942429
return (readValue(buffer) as? List<Any?>)?.let { NavInfoDto.fromList(it) }
23952430
}
23962431
else -> super.readValueOfType(type, buffer)
@@ -2611,42 +2646,46 @@ private open class messagesPigeonCodec : StandardMessageCodec() {
26112646
stream.write(181)
26122647
writeValue(stream, value.toList())
26132648
}
2614-
is SpeedAlertOptionsThresholdPercentageDto -> {
2649+
is GpsAvailabilityChangeEventDto -> {
26152650
stream.write(182)
26162651
writeValue(stream, value.toList())
26172652
}
2618-
is SpeedAlertOptionsDto -> {
2653+
is SpeedAlertOptionsThresholdPercentageDto -> {
26192654
stream.write(183)
26202655
writeValue(stream, value.toList())
26212656
}
2622-
is RouteSegmentTrafficDataRoadStretchRenderingDataDto -> {
2657+
is SpeedAlertOptionsDto -> {
26232658
stream.write(184)
26242659
writeValue(stream, value.toList())
26252660
}
2626-
is RouteSegmentTrafficDataDto -> {
2661+
is RouteSegmentTrafficDataRoadStretchRenderingDataDto -> {
26272662
stream.write(185)
26282663
writeValue(stream, value.toList())
26292664
}
2630-
is RouteSegmentDto -> {
2665+
is RouteSegmentTrafficDataDto -> {
26312666
stream.write(186)
26322667
writeValue(stream, value.toList())
26332668
}
2634-
is LaneDirectionDto -> {
2669+
is RouteSegmentDto -> {
26352670
stream.write(187)
26362671
writeValue(stream, value.toList())
26372672
}
2638-
is LaneDto -> {
2673+
is LaneDirectionDto -> {
26392674
stream.write(188)
26402675
writeValue(stream, value.toList())
26412676
}
2642-
is StepInfoDto -> {
2677+
is LaneDto -> {
26432678
stream.write(189)
26442679
writeValue(stream, value.toList())
26452680
}
2646-
is NavInfoDto -> {
2681+
is StepInfoDto -> {
26472682
stream.write(190)
26482683
writeValue(stream, value.toList())
26492684
}
2685+
is NavInfoDto -> {
2686+
stream.write(191)
2687+
writeValue(stream, value.toList())
2688+
}
26502689
else -> super.writeValue(stream, value)
26512690
}
26522691
}
@@ -6922,6 +6961,29 @@ class NavigationSessionEventApi(
69226961
}
69236962
}
69246963

6964+
/** Android-only event. */
6965+
fun onGpsAvailabilityChange(
6966+
eventArg: GpsAvailabilityChangeEventDto,
6967+
callback: (Result<Unit>) -> Unit,
6968+
) {
6969+
val separatedMessageChannelSuffix =
6970+
if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
6971+
val channelName =
6972+
"dev.flutter.pigeon.google_navigation_flutter.NavigationSessionEventApi.onGpsAvailabilityChange$separatedMessageChannelSuffix"
6973+
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
6974+
channel.send(listOf(eventArg)) {
6975+
if (it is List<*>) {
6976+
if (it.size > 1) {
6977+
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
6978+
} else {
6979+
callback(Result.success(Unit))
6980+
}
6981+
} else {
6982+
callback(Result.failure(MessagesPigeonUtils.createConnectionError(channelName)))
6983+
}
6984+
}
6985+
}
6986+
69256987
/** Turn-by-Turn navigation events. */
69266988
fun onNavInfo(navInfoArg: NavInfoDto, callback: (Result<Unit>) -> Unit) {
69276989
val separatedMessageChannelSuffix =

example/integration_test/t02_session_test.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ void main() {
2828
patrol('Test terms and conditions (TOS) dialog acceptance', (
2929
PatrolIntegrationTester $,
3030
) async {
31+
// Be sure location is enabled.
32+
await $.native.enableLocation();
33+
3134
// Grant the location permission.
3235
await checkLocationDialogAcceptance($);
3336

example/integration_test/t07_event_listener_test.dart

Lines changed: 68 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -398,9 +398,12 @@ void main() {
398398
});
399399

400400
patrol(
401-
'Test navigation onRerouting and onGpsAvailability event listeners',
401+
'Test navigation onRerouting, onGpsAvailability and onGpsAvailabilityChange event listeners',
402402
(PatrolIntegrationTester $) async {
403-
final Completer<void> eventReceived = Completer<void>();
403+
final Completer<void> reroutingEventReceived = Completer<void>();
404+
final Completer<void> gpsAvailabilityEventReceived = Completer<void>();
405+
final Completer<void> gpsAvailabilityChangeEventReceived =
406+
Completer<void>();
404407

405408
/// Set up navigation.
406409
await startNavigationWithoutDestination($);
@@ -409,26 +412,46 @@ void main() {
409412
final StreamSubscription<void> onReroutingSubscription =
410413
GoogleMapsNavigator.setOnReroutingListener(
411414
expectAsync0(() {
415+
$.log('Rerouting event received');
416+
412417
/// Complete the eventReceived completer only once.
413-
if (!eventReceived.isCompleted) {
414-
eventReceived.complete();
418+
if (!reroutingEventReceived.isCompleted) {
419+
reroutingEventReceived.complete();
415420
}
416421
}, max: -1),
417422
);
418423
await $.pumpAndSettle();
419424

420-
/// The events are not tested because there's currently no reliable way to trigger them.
425+
/// Set up GPS availability listeners with completers.
421426
void onGpsAvailability(GpsAvailabilityUpdatedEvent event) {
422-
$.log('GpsAvailabilityEvent: $event');
427+
$.log('GpsAvailabilityEvent (deprecated): $event');
428+
if (!gpsAvailabilityEventReceived.isCompleted) {
429+
gpsAvailabilityEventReceived.complete();
430+
}
431+
}
432+
433+
void onGpsAvailabilityChange(GpsAvailabilityChangeEvent event) {
434+
$.log(
435+
'GpsAvailabilityChangeEvent: isGpsLost=${event.isGpsLost}, isGpsValidForNavigation=${event.isGpsValidForNavigation}',
436+
);
437+
if (!gpsAvailabilityChangeEventReceived.isCompleted) {
438+
gpsAvailabilityChangeEventReceived.complete();
439+
}
423440
}
424441

425-
/// Set up the gpsAvailability listener with the test.
442+
/// Set up both the old (deprecated) and new gpsAvailability listeners.
426443
final StreamSubscription<GpsAvailabilityUpdatedEvent>
427444
onGpsAvailabilitySubscription =
428445
await GoogleMapsNavigator.setOnGpsAvailabilityListener(
429446
onGpsAvailability,
430447
);
431448

449+
final StreamSubscription<GpsAvailabilityChangeEvent>
450+
onGpsAvailabilityChangeSubscription =
451+
await GoogleMapsNavigator.setOnGpsAvailabilityChangeListener(
452+
onGpsAvailabilityChange,
453+
);
454+
432455
/// Simulate location.
433456
await GoogleMapsNavigator.simulator.setUserLocation(
434457
const LatLng(latitude: 37.790693, longitude: -122.4132157),
@@ -463,6 +486,7 @@ void main() {
463486

464487
/// Start guidance.
465488
await GoogleMapsNavigator.startGuidance();
489+
466490
await $.pumpAndSettle();
467491

468492
/// Start simulation to a different destination.
@@ -474,10 +498,43 @@ void main() {
474498
);
475499
await $.pumpAndSettle();
476500

477-
/// Wait until the event is received and then test cancelling the subscriptions.
478-
await eventReceived.future;
479-
await onReroutingSubscription.cancel();
480-
await onGpsAvailabilitySubscription.cancel();
501+
Future<void> cancelSubscriptionsAndResetState() async {
502+
await $.native.enableLocation();
503+
await onReroutingSubscription.cancel();
504+
await onGpsAvailabilitySubscription.cancel();
505+
await onGpsAvailabilityChangeSubscription.cancel();
506+
}
507+
508+
/// Wait for rerouting event (this should fire reliably).
509+
await reroutingEventReceived.future.timeout(
510+
const Duration(seconds: 10),
511+
onTimeout: () {
512+
cancelSubscriptionsAndResetState();
513+
fail('Rerouting event timed out');
514+
},
515+
);
516+
517+
await $.native.disableLocation();
518+
519+
/// Wait for GPS availability events with timeout.
520+
await gpsAvailabilityEventReceived.future.timeout(
521+
const Duration(seconds: 20),
522+
onTimeout: () {
523+
cancelSubscriptionsAndResetState();
524+
fail('GpsAvailabilityEvent timed out');
525+
},
526+
);
527+
528+
await gpsAvailabilityChangeEventReceived.future.timeout(
529+
const Duration(seconds: 20),
530+
onTimeout: () {
531+
cancelSubscriptionsAndResetState();
532+
fail('GpsAvailabilityChangeEvent timed out');
533+
},
534+
);
535+
536+
/// Cancel all subscriptions.
537+
await cancelSubscriptionsAndResetState();
481538
},
482539
skip: !Platform.isAndroid,
483540
);

0 commit comments

Comments
 (0)