From 9e9233c48e9b05912b08c5eed02b63a559315a8f Mon Sep 17 00:00:00 2001 From: Joonas Kerttula Date: Tue, 15 Oct 2024 16:08:12 +0300 Subject: [PATCH] fix: improve reliability of navigation event handling on Android --- .../android/react/navsdk/NavModule.java | 272 +++++++++++------- .../google/android/react/navsdk/Package.java | 2 +- 2 files changed, 170 insertions(+), 104 deletions(-) diff --git a/android/src/main/java/com/google/android/react/navsdk/NavModule.java b/android/src/main/java/com/google/android/react/navsdk/NavModule.java index facc198..f944851 100644 --- a/android/src/main/java/com/google/android/react/navsdk/NavModule.java +++ b/android/src/main/java/com/google/android/react/navsdk/NavModule.java @@ -19,6 +19,7 @@ import androidx.lifecycle.Observer; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.CatalystInstance; +import com.facebook.react.bridge.LifecycleEventListener; import com.facebook.react.bridge.NativeArray; import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.ReactApplicationContext; @@ -58,7 +59,8 @@ * This exposes a series of methods that can be called diretly from the React Native code. They have * been implemented using promises as it's not recommended for them to be synchronous. */ -public class NavModule extends ReactContextBaseJavaModule implements INavigationCallback { +public class NavModule extends ReactContextBaseJavaModule + implements INavigationCallback, LifecycleEventListener { public static final String REACT_CLASS = "NavModule"; private static final String TAG = "NavModule"; private static NavModule instance; @@ -73,6 +75,12 @@ public class NavModule extends ReactContextBaseJavaModule implements INavigation private final CopyOnWriteArrayList mNavigationReadyListeners = new CopyOnWriteArrayList<>(); private boolean mIsListeningRoadSnappedLocation = false; + private LocationListener mLocationListener; + private Navigator.ArrivalListener mArrivalListener; + private Navigator.RouteChangedListener mRouteChangedListener; + private Navigator.TrafficUpdatedListener mTrafficUpdatedListener; + private Navigator.ReroutingListener mReroutingListener; + private Navigator.RemainingTimeOrDistanceChangedListener mRemainingTimeOrDistanceChangedListener; private HashMap tocParamsMap; private @Navigator.TaskRemovedBehavior int taskRemovedBehaviour; @@ -87,19 +95,22 @@ public interface NavigationReadyListener { public NavModule(ReactApplicationContext reactContext, NavViewManager navViewManager) { super(reactContext); - this.reactContext = reactContext; - mNavViewManager = navViewManager; - instance = this; + setReactContext(reactContext); + setViewManager(navViewManager); if (moduleReadyListener != null) { moduleReadyListener.onModuleReady(); } } - public static void setModuleReadyListener(ModuleReadyListener listener) { - moduleReadyListener = listener; - if (instance != null && moduleReadyListener != null) { - moduleReadyListener.onModuleReady(); + public static synchronized NavModule getInstance( + ReactApplicationContext reactContext, NavViewManager navViewManager) { + if (instance == null) { + instance = new NavModule(reactContext, navViewManager); + } else { + instance.setReactContext(reactContext); + instance.setViewManager(navViewManager); } + return instance; } public static synchronized NavModule getInstance() { @@ -109,6 +120,22 @@ public static synchronized NavModule getInstance() { return instance; } + public void setReactContext(ReactApplicationContext reactContext) { + this.reactContext = reactContext; + this.reactContext.addLifecycleEventListener(this); + } + + public void setViewManager(NavViewManager navViewManager) { + mNavViewManager = navViewManager; + } + + public static void setModuleReadyListener(ModuleReadyListener listener) { + moduleReadyListener = listener; + if (instance != null && moduleReadyListener != null) { + moduleReadyListener.onModuleReady(); + } + } + public Navigator getNavigator() { return mNavigator; } @@ -124,85 +151,10 @@ public Map getConstants() { return constants; } - private Navigator.ArrivalListener mArrivalListener = - new Navigator.ArrivalListener() { - @Override - public void onArrival(ArrivalEvent arrivalEvent) { - WritableMap map = Arguments.createMap(); - map.putMap( - "waypoint", ObjectTranslationUtil.getMapFromWaypoint(arrivalEvent.getWaypoint())); - map.putBoolean("isFinalDestination", arrivalEvent.isFinalDestination()); - - WritableNativeArray params = new WritableNativeArray(); - params.pushMap(map); - - sendCommandToReactNative("onArrival", params); - } - }; - - private LocationListener mLocationListener = - new LocationListener() { - @Override - public void onLocationChanged(final Location location) { - WritableNativeArray params = new WritableNativeArray(); - params.pushMap(ObjectTranslationUtil.getMapFromLocation(location)); - - sendCommandToReactNative("onLocationChanged", params); - } - - @Override - public void onRawLocationUpdate(final Location location) { - WritableNativeArray params = new WritableNativeArray(); - params.pushMap(ObjectTranslationUtil.getMapFromLocation(location)); - - sendCommandToReactNative("onRawLocationChanged", params); - } - }; - - private Navigator.RouteChangedListener mRouteChangedListener = - new Navigator.RouteChangedListener() { - @Override - public void onRouteChanged() { - sendCommandToReactNative("onRouteChanged", (NativeArray) null); - } - }; - - private Navigator.TrafficUpdatedListener mTrafficUpdatedListener = - new Navigator.TrafficUpdatedListener() { - @Override - public void onTrafficUpdated() { - sendCommandToReactNative("onTrafficUpdated", (NativeArray) null); - } - }; - - private Navigator.ReroutingListener mReroutingListener = - new Navigator.ReroutingListener() { - @Override - public void onReroutingRequestedByOffRoute() { - sendCommandToReactNative("onReroutingRequestedByOffRoute", (NativeArray) null); - } - }; - - private Navigator.RemainingTimeOrDistanceChangedListener mRemainingTimeOrDistanceChangedListener = - new Navigator.RemainingTimeOrDistanceChangedListener() { - @Override - public void onRemainingTimeOrDistanceChanged() { - sendCommandToReactNative("onRemainingTimeOrDistanceChanged", (NativeArray) null); - } - }; - @ReactMethod private void cleanup() { - if (mIsListeningRoadSnappedLocation) { - mRoadSnappedLocationProvider.removeLocationListener(mLocationListener); - } - mNavigator.unregisterServiceForNavUpdates(); - mNavigator.removeArrivalListener(mArrivalListener); - mNavigator.removeReroutingListener(mReroutingListener); - mNavigator.removeRouteChangedListener(mRouteChangedListener); - mNavigator.removeTrafficUpdatedListener(mTrafficUpdatedListener); - mNavigator.removeRemainingTimeOrDistanceChangedListener( - mRemainingTimeOrDistanceChangedListener); + stopUpdatingLocation(); + removeNavigationListeners(); mWaypoints.clear(); for (NavigationReadyListener listener : mNavigationReadyListeners) { @@ -276,8 +228,12 @@ public void onNavigatorReady(Navigator navigator) { // Keep a reference to the Navigator (used to configure and start nav) mNavigator = navigator; mNavigator.setTaskRemovedBehavior(taskRemovedBehaviour); - mRoadSnappedLocationProvider = - NavigationApi.getRoadSnappedLocationProvider(getCurrentActivity().getApplication()); + if (mRoadSnappedLocationProvider == null) { + mRoadSnappedLocationProvider = + NavigationApi.getRoadSnappedLocationProvider( + getCurrentActivity().getApplication()); + } + registerNavigationListeners(); onNavigationReady(); } @@ -330,21 +286,80 @@ public void setTurnByTurnLoggingEnabled(boolean isEnabled) { * navigation events occur (e.g. the driver's route changes or the destination is reached). */ private void registerNavigationListeners() { + removeNavigationListeners(); + + mArrivalListener = + new Navigator.ArrivalListener() { + @Override + public void onArrival(ArrivalEvent arrivalEvent) { + WritableMap map = Arguments.createMap(); + map.putMap( + "waypoint", ObjectTranslationUtil.getMapFromWaypoint(arrivalEvent.getWaypoint())); + map.putBoolean("isFinalDestination", arrivalEvent.isFinalDestination()); + + WritableNativeArray params = new WritableNativeArray(); + params.pushMap(map); + + sendCommandToReactNative("onArrival", params); + } + }; mNavigator.addArrivalListener(mArrivalListener); + + mRouteChangedListener = + new Navigator.RouteChangedListener() { + @Override + public void onRouteChanged() { + sendCommandToReactNative("onRouteChanged", (NativeArray) null); + } + }; mNavigator.addRouteChangedListener(mRouteChangedListener); + + mTrafficUpdatedListener = + new Navigator.TrafficUpdatedListener() { + @Override + public void onTrafficUpdated() { + sendCommandToReactNative("onTrafficUpdated", (NativeArray) null); + } + }; mNavigator.addTrafficUpdatedListener(mTrafficUpdatedListener); + + mReroutingListener = + new Navigator.ReroutingListener() { + @Override + public void onReroutingRequestedByOffRoute() { + sendCommandToReactNative("onReroutingRequestedByOffRoute", (NativeArray) null); + } + }; mNavigator.addReroutingListener(mReroutingListener); + + mRemainingTimeOrDistanceChangedListener = + new Navigator.RemainingTimeOrDistanceChangedListener() { + @Override + public void onRemainingTimeOrDistanceChanged() { + sendCommandToReactNative("onRemainingTimeOrDistanceChanged", (NativeArray) null); + } + }; mNavigator.addRemainingTimeOrDistanceChangedListener( 0, 0, mRemainingTimeOrDistanceChangedListener); } private void removeNavigationListeners() { - mNavigator.removeArrivalListener(mArrivalListener); - mNavigator.removeRouteChangedListener(mRouteChangedListener); - mNavigator.removeTrafficUpdatedListener(mTrafficUpdatedListener); - mNavigator.removeReroutingListener(mReroutingListener); - mNavigator.removeRemainingTimeOrDistanceChangedListener( - mRemainingTimeOrDistanceChangedListener); + if (mArrivalListener != null) { + mNavigator.removeArrivalListener(mArrivalListener); + } + if (mRouteChangedListener != null) { + mNavigator.removeRouteChangedListener(mRouteChangedListener); + } + if (mTrafficUpdatedListener != null) { + mNavigator.removeTrafficUpdatedListener(mTrafficUpdatedListener); + } + if (mReroutingListener != null) { + mNavigator.removeReroutingListener(mReroutingListener); + } + if (mRemainingTimeOrDistanceChangedListener != null) { + mNavigator.removeRemainingTimeOrDistanceChangedListener( + mRemainingTimeOrDistanceChangedListener); + } } private void createWaypoint(Map map) { @@ -468,14 +483,6 @@ private void setOnResultListener(IRouteStatusResult listener) { @Override public void onResult(Navigator.RouteStatus code) { listener.onResult(code); - switch (code) { - case OK: - removeNavigationListeners(); - registerNavigationListeners(); - break; - default: - break; - } } }); } @@ -725,14 +732,54 @@ public void resetTermsAccepted() { @ReactMethod public void startUpdatingLocation() { - mRoadSnappedLocationProvider.addLocationListener(mLocationListener); + registerLocationListener(); mIsListeningRoadSnappedLocation = true; } @ReactMethod public void stopUpdatingLocation() { mIsListeningRoadSnappedLocation = false; - mRoadSnappedLocationProvider.removeLocationListener(mLocationListener); + removeLocationListener(); + } + + private void registerLocationListener() { + // Unregister existing location listener if available. + removeLocationListener(); + + if (mRoadSnappedLocationProvider != null) { + mLocationListener = + new LocationListener() { + @Override + public void onLocationChanged(final Location location) { + if (mIsListeningRoadSnappedLocation) { + WritableNativeArray params = new WritableNativeArray(); + params.pushMap(ObjectTranslationUtil.getMapFromLocation(location)); + + sendCommandToReactNative("onLocationChanged", params); + } + } + + @Override + public void onRawLocationUpdate(final Location location) { + if (mIsListeningRoadSnappedLocation) { + WritableNativeArray params = new WritableNativeArray(); + params.pushMap(ObjectTranslationUtil.getMapFromLocation(location)); + + sendCommandToReactNative("onRawLocationChanged", params); + } + } + }; + + mRoadSnappedLocationProvider.resetFreeNav(); + mRoadSnappedLocationProvider.addLocationListener(mLocationListener); + } + } + + private void removeLocationListener() { + if (mRoadSnappedLocationProvider != null && mLocationListener != null) { + mRoadSnappedLocationProvider.removeLocationListener(mLocationListener); + mLocationListener = null; + } } private void showNavInfo(NavInfo navInfo) { @@ -781,6 +828,25 @@ public boolean canOverrideExistingModule() { return true; } + @Override + public void onHostResume() { + if (mNavigator != null) { + registerNavigationListeners(); + if (mIsListeningRoadSnappedLocation) { + registerLocationListener(); + } + } + } + + @Override + public void onHostPause() { + removeLocationListener(); + removeNavigationListeners(); + } + + @Override + public void onHostDestroy() {} + private interface IRouteStatusResult { void onResult(Navigator.RouteStatus code); } diff --git a/android/src/main/java/com/google/android/react/navsdk/Package.java b/android/src/main/java/com/google/android/react/navsdk/Package.java index e7a9a2b..a345940 100644 --- a/android/src/main/java/com/google/android/react/navsdk/Package.java +++ b/android/src/main/java/com/google/android/react/navsdk/Package.java @@ -34,7 +34,7 @@ public List createViewManagers(ReactApplicationContext reactContext public List createNativeModules(ReactApplicationContext reactContext) { List modules = new ArrayList<>(); NavViewManager viewManager = NavViewManager.getInstance(reactContext); - modules.add(new NavModule(reactContext, viewManager)); + modules.add(NavModule.getInstance(reactContext, viewManager)); modules.add(new NavAutoModule(reactContext)); modules.add(new NavViewModule(reactContext, viewManager));