Skip to content

Commit 6128cc6

Browse files
authored
feat: add interface for listening new navigation sessions (#530)
1 parent 8dc5bc8 commit 6128cc6

File tree

12 files changed

+294
-58
lines changed

12 files changed

+294
-58
lines changed

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ constructor(
7070
RoadSnappedLocationProvider.GpsAvailabilityEnhancedLocationListener? =
7171
null
7272
private var speedingListener: SpeedingListener? = null
73+
private var navigationSessionListener: Navigator.NavigationSessionListener? = null
7374
private var weakActivity: WeakReference<Activity>? = null
7475
private var navInfoObserver: Observer<NavInfo>? = null
7576
private var weakLifecycleOwner: WeakReference<LifecycleOwner>? = null
@@ -315,6 +316,10 @@ constructor(
315316
navigator.setSpeedingListener(null)
316317
speedingListener = null
317318
}
319+
if (navigationSessionListener != null) {
320+
navigator.removeNavigationSessionListener(navigationSessionListener)
321+
navigationSessionListener = null
322+
}
318323
}
319324
if (roadSnappedLocationListener != null) {
320325
disableRoadSnappedLocationUpdates()
@@ -391,6 +396,12 @@ constructor(
391396
}
392397
navigator.setSpeedingListener(speedingListener)
393398
}
399+
400+
if (navigationSessionListener == null) {
401+
navigationSessionListener =
402+
Navigator.NavigationSessionListener { navigationSessionEventApi.onNewNavigationSession {} }
403+
navigator.addNavigationSessionListener(navigationSessionListener)
404+
}
394405
}
395406

396407
/**

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

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5693,7 +5693,6 @@ class ViewEventApi(
56935693

56945694
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */
56955695
interface NavigationSessionApi {
5696-
/** General. */
56975696
fun createNavigationSession(
56985697
abnormalTerminationReportingEnabled: Boolean,
56995698
behavior: TaskRemovedBehaviorDto,
@@ -5717,7 +5716,6 @@ interface NavigationSessionApi {
57175716

57185717
fun getNavSDKVersion(): String
57195718

5720-
/** Navigation. */
57215719
fun isGuidanceRunning(): Boolean
57225720

57235721
fun startGuidance()
@@ -5742,7 +5740,6 @@ interface NavigationSessionApi {
57425740

57435741
fun getCurrentRouteSegment(): RouteSegmentDto?
57445742

5745-
/** Simulation */
57465743
fun setUserLocation(location: LatLngDto)
57475744

57485745
fun removeUserLocation()
@@ -5773,15 +5770,13 @@ interface NavigationSessionApi {
57735770

57745771
fun resumeSimulation()
57755772

5776-
/** Simulation (iOS only) */
5773+
/** iOS-only method. */
57775774
fun allowBackgroundLocationUpdates(allow: Boolean)
57785775

5779-
/** Road snapped location updates. */
57805776
fun enableRoadSnappedLocationUpdates()
57815777

57825778
fun disableRoadSnappedLocationUpdates()
57835779

5784-
/** Enable Turn-by-Turn navigation events. */
57855780
fun enableTurnByTurnNavigationEvents(numNextStepsToPreview: Long?)
57865781

57875782
fun disableTurnByTurnNavigationEvents()
@@ -6810,6 +6805,26 @@ class NavigationSessionEventApi(
68106805
}
68116806
}
68126807
}
6808+
6809+
/** Navigation session event. Called when a new navigation session starts with active guidance. */
6810+
fun onNewNavigationSession(callback: (Result<Unit>) -> Unit) {
6811+
val separatedMessageChannelSuffix =
6812+
if (messageChannelSuffix.isNotEmpty()) ".$messageChannelSuffix" else ""
6813+
val channelName =
6814+
"dev.flutter.pigeon.google_navigation_flutter.NavigationSessionEventApi.onNewNavigationSession$separatedMessageChannelSuffix"
6815+
val channel = BasicMessageChannel<Any?>(binaryMessenger, channelName, codec)
6816+
channel.send(null) {
6817+
if (it is List<*>) {
6818+
if (it.size > 1) {
6819+
callback(Result.failure(FlutterError(it[0] as String, it[1] as String, it[2] as String?)))
6820+
} else {
6821+
callback(Result.success(Unit))
6822+
}
6823+
} else {
6824+
callback(Result.failure(MessagesPigeonUtils.createConnectionError(channelName)))
6825+
}
6826+
}
6827+
}
68136828
}
68146829

68156830
/** Generated interface from Pigeon that represents a handler of messages from Flutter. */

example/integration_test/t03_navigation_test.dart

Lines changed: 82 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -59,22 +59,12 @@ void main() {
5959
PatrolIntegrationTester $,
6060
) async {
6161
final Completer<void> hasArrived = Completer<void>();
62+
final Completer<void> newSessionFired = Completer<void>();
6263

6364
/// Set up navigation view and controller.
6465
final GoogleNavigationViewController viewController =
6566
await startNavigationWithoutDestination($);
6667

67-
/// Set audio guidance settings.
68-
/// Cannot be verified, because native SDK lacks getter methods,
69-
/// but exercise the API for basic sanity testing
70-
final NavigationAudioGuidanceSettings settings =
71-
NavigationAudioGuidanceSettings(
72-
isBluetoothAudioEnabled: true,
73-
isVibrationEnabled: true,
74-
guidanceType: NavigationAudioGuidanceType.alertsAndGuidance,
75-
);
76-
await GoogleMapsNavigator.setAudioGuidance(settings);
77-
7868
/// Specify tolerance and navigation end coordinates.
7969
const double tolerance = 0.001;
8070
const double endLat = 68.59451829688189, endLng = 23.512277951523007;
@@ -86,8 +76,28 @@ void main() {
8676
await GoogleMapsNavigator.stopGuidance();
8777
}
8878

79+
/// Set up listener for new navigation session event.
80+
Future<void> onNewNavigationSession() async {
81+
newSessionFired.complete();
82+
83+
/// Sets audio guidance settings for the current navigation session.
84+
/// Cannot be verified, because native SDK lacks getter methods,
85+
/// but exercise the API for basic sanity testing.
86+
await GoogleMapsNavigator.setAudioGuidance(
87+
NavigationAudioGuidanceSettings(
88+
isBluetoothAudioEnabled: true,
89+
isVibrationEnabled: true,
90+
guidanceType: NavigationAudioGuidanceType.alertsAndGuidance,
91+
),
92+
);
93+
}
94+
8995
final StreamSubscription<OnArrivalEvent> onArrivalSubscription =
9096
GoogleMapsNavigator.setOnArrivalListener(onArrivalEvent);
97+
final StreamSubscription<void> onNewNavigationSessionSubscription =
98+
GoogleMapsNavigator.setOnNewNavigationSessionListener(
99+
onNewNavigationSession,
100+
);
91101

92102
/// Simulate location and test it.
93103
await setSimulatedUserLocationWithCheck(
@@ -143,11 +153,24 @@ void main() {
143153
await GoogleMapsNavigator.simulator.simulateLocationsAlongExistingRoute();
144154

145155
expect(await GoogleMapsNavigator.isGuidanceRunning(), true);
156+
157+
/// Wait for new navigation session event.
158+
await newSessionFired.future.timeout(
159+
const Duration(seconds: 30),
160+
onTimeout:
161+
() =>
162+
throw TimeoutException(
163+
'New navigation session event was not fired',
164+
),
165+
);
166+
expect(newSessionFired.isCompleted, true);
167+
146168
await hasArrived.future;
147169
expect(await GoogleMapsNavigator.isGuidanceRunning(), false);
148170

149171
// Cancel subscriptions before cleanup
150172
await onArrivalSubscription.cancel();
173+
await onNewNavigationSessionSubscription.cancel();
151174
await roadSnappedSubscription.cancel();
152175
await GoogleMapsNavigator.cleanup();
153176
});
@@ -156,24 +179,14 @@ void main() {
156179
'Test navigating to multiple destinations',
157180
(PatrolIntegrationTester $) async {
158181
final Completer<void> navigationFinished = Completer<void>();
182+
Completer<void> newSessionFired = Completer<void>();
159183
int arrivalEventCount = 0;
160184
List<NavigationWaypoint> waypoints = <NavigationWaypoint>[];
161185

162186
/// Set up navigation view and controller.
163187
final GoogleNavigationViewController viewController =
164188
await startNavigationWithoutDestination($);
165189

166-
/// Set audio guidance settings.
167-
/// Cannot be verified, because native SDK lacks getter methods,
168-
/// but exercise the API for basic sanity testing
169-
final NavigationAudioGuidanceSettings settings =
170-
NavigationAudioGuidanceSettings(
171-
isBluetoothAudioEnabled: false,
172-
isVibrationEnabled: false,
173-
guidanceType: NavigationAudioGuidanceType.alertsOnly,
174-
);
175-
await GoogleMapsNavigator.setAudioGuidance(settings);
176-
177190
/// Specify tolerance and navigation destination coordinates.
178191
const double tolerance = 0.001;
179192
const double midLat = 68.59781164189049,
@@ -184,6 +197,9 @@ void main() {
184197
Future<void> onArrivalEvent(OnArrivalEvent msg) async {
185198
arrivalEventCount += 1;
186199
if (arrivalEventCount < 2) {
200+
// Reset the completer to test that new session event fires again
201+
newSessionFired = Completer<void>();
202+
187203
if (multipleDestinationsVariants.currentValue ==
188204
'continueToNextDestination') {
189205
// Note: continueToNextDestination is deprecated.
@@ -220,12 +236,23 @@ void main() {
220236
),
221237
);
222238
await GoogleMapsNavigator.setDestinations(updatedDestinations);
239+
223240
await GoogleMapsNavigator.simulator
224241
.simulateLocationsAlongExistingRouteWithOptions(
225242
SimulationOptions(speedMultiplier: 5),
226243
);
227244
}
228245
}
246+
247+
// Wait for new session event after updating destinations
248+
await newSessionFired.future.timeout(
249+
const Duration(seconds: 10),
250+
onTimeout:
251+
() =>
252+
throw TimeoutException(
253+
'New navigation session event was not fired after updating destinations',
254+
),
255+
);
229256
} else {
230257
$.log('Got second arrival event, stopping guidance');
231258
// Stop guidance after the last destination
@@ -234,8 +261,28 @@ void main() {
234261
}
235262
}
236263

264+
/// Set up listener for new navigation session event.
265+
Future<void> onNewNavigationSession() async {
266+
newSessionFired.complete();
267+
268+
/// Sets audio guidance settings for the current navigation session.
269+
/// Cannot be verified, because native SDK lacks getter methods,
270+
/// but exercise the API for basic sanity testing.
271+
await GoogleMapsNavigator.setAudioGuidance(
272+
NavigationAudioGuidanceSettings(
273+
isBluetoothAudioEnabled: true,
274+
isVibrationEnabled: true,
275+
guidanceType: NavigationAudioGuidanceType.alertsAndGuidance,
276+
),
277+
);
278+
}
279+
237280
final StreamSubscription<OnArrivalEvent> onArrivalSubscription =
238281
GoogleMapsNavigator.setOnArrivalListener(onArrivalEvent);
282+
final StreamSubscription<void> onNewNavigationSessionSubscription =
283+
GoogleMapsNavigator.setOnNewNavigationSessionListener(
284+
onNewNavigationSession,
285+
);
239286

240287
/// Simulate location and test it.
241288
await setSimulatedUserLocationWithCheck(
@@ -317,11 +364,24 @@ void main() {
317364
);
318365

319366
expect(await GoogleMapsNavigator.isGuidanceRunning(), true);
367+
368+
/// Wait for new navigation session event.
369+
await newSessionFired.future.timeout(
370+
const Duration(seconds: 30),
371+
onTimeout:
372+
() =>
373+
throw TimeoutException(
374+
'New navigation session event was not fired',
375+
),
376+
);
377+
expect(newSessionFired.isCompleted, true);
378+
320379
await navigationFinished.future;
321380
expect(await GoogleMapsNavigator.isGuidanceRunning(), false);
322381

323382
// Cancel subscriptions before cleanup
324383
await onArrivalSubscription.cancel();
384+
await onNewNavigationSessionSubscription.cancel();
325385
await roadSnappedSubscription.cancel();
326386
await GoogleMapsNavigator.cleanup();
327387
},

example/lib/pages/navigation.dart

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ class _NavigationPageState extends ExamplePageState<NavigationPage> {
9898
int _onRecenterButtonClickedEventCallCount = 0;
9999
int _onRemainingTimeOrDistanceChangedEventCallCount = 0;
100100
int _onNavigationUIEnabledChangedEventCallCount = 0;
101+
int _onNewNavigationSessionEventCallCount = 0;
101102

102103
bool _navigationHeaderEnabled = true;
103104
bool _navigationFooterEnabled = true;
@@ -147,6 +148,7 @@ class _NavigationPageState extends ExamplePageState<NavigationPage> {
147148
_roadSnappedLocationUpdatedSubscription;
148149
StreamSubscription<RoadSnappedRawLocationUpdatedEvent>?
149150
_roadSnappedRawLocationUpdatedSubscription;
151+
StreamSubscription<void>? _newNavigationSessionSubscription;
150152

151153
int _nextWaypointIndex = 0;
152154

@@ -379,6 +381,11 @@ class _NavigationPageState extends ExamplePageState<NavigationPage> {
379381
await GoogleMapsNavigator.setRoadSnappedRawLocationUpdatedListener(
380382
_onRoadSnappedRawLocationUpdatedEvent,
381383
);
384+
385+
_newNavigationSessionSubscription =
386+
GoogleMapsNavigator.setOnNewNavigationSessionListener(
387+
_onNewNavigationSessionEvent,
388+
);
382389
}
383390

384391
void _clearListeners() {
@@ -408,6 +415,24 @@ class _NavigationPageState extends ExamplePageState<NavigationPage> {
408415

409416
_roadSnappedRawLocationUpdatedSubscription?.cancel();
410417
_roadSnappedRawLocationUpdatedSubscription = null;
418+
419+
_newNavigationSessionSubscription?.cancel();
420+
_newNavigationSessionSubscription = null;
421+
}
422+
423+
void _onNewNavigationSessionEvent() {
424+
if (!mounted) {
425+
return;
426+
}
427+
428+
setState(() {
429+
_onNewNavigationSessionEventCallCount += 1;
430+
});
431+
432+
showMessage('New navigation session started');
433+
434+
// Set audio guidance settings for the new navigation session.
435+
unawaited(_setAudioGuidance());
411436
}
412437

413438
void _onRoadSnappedLocationUpdatedEvent(
@@ -517,6 +542,16 @@ class _NavigationPageState extends ExamplePageState<NavigationPage> {
517542
await _getInitialViewStates();
518543
}
519544

545+
Future<void> _setAudioGuidance() async {
546+
await GoogleMapsNavigator.setAudioGuidance(
547+
NavigationAudioGuidanceSettings(
548+
isBluetoothAudioEnabled: true,
549+
isVibrationEnabled: true,
550+
guidanceType: NavigationAudioGuidanceType.alertsAndGuidance,
551+
),
552+
);
553+
}
554+
520555
Future<void> _getInitialViewStates() async {
521556
assert(_navigationViewController != null);
522557
if (_navigationViewController != null) {
@@ -1445,6 +1480,14 @@ class _NavigationPageState extends ExamplePageState<NavigationPage> {
14451480
),
14461481
),
14471482
),
1483+
Card(
1484+
child: ListTile(
1485+
title: const Text('New navigation session event call count'),
1486+
trailing: Text(
1487+
_onNewNavigationSessionEventCallCount.toString(),
1488+
),
1489+
),
1490+
),
14481491
],
14491492
),
14501493
);

0 commit comments

Comments
 (0)