Skip to content

Commit

Permalink
fix: handle navigation listeners on NavigationProvider mount and unmo…
Browse files Browse the repository at this point in the history
…unt (googlemaps#294)

- Initialized navigation listeners on NavigationProvider mount to avoid cases where the native layer sends messages without corresponding listener mappings.
- Keep navigation listeners registered even when NavigationProvider is disposed.
- Make Android navigation event handling more reliable by re-registering handlers on app lifecycle events; keeping them up to date with react context all the time.
  • Loading branch information
jokerttu authored Oct 24, 2024
1 parent aa56cbe commit 6674a4f
Show file tree
Hide file tree
Showing 3 changed files with 180 additions and 117 deletions.
270 changes: 167 additions & 103 deletions android/src/main/java/com/google/android/react/navsdk/NavModule.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -73,6 +75,12 @@ public class NavModule extends ReactContextBaseJavaModule implements INavigation
private final CopyOnWriteArrayList<NavigationReadyListener> 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<String, Object> tocParamsMap;
private @Navigator.TaskRemovedBehavior int taskRemovedBehaviour;
Expand All @@ -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() {
Expand All @@ -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;
}
Expand All @@ -124,85 +151,10 @@ public Map<String, Object> 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) {
Expand Down Expand Up @@ -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();
}

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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;
}
}
});
}
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -781,6 +828,23 @@ public boolean canOverrideExistingModule() {
return true;
}

@Override
public void onHostResume() {
// Re-register listeners on resume.
if (mNavigator != null) {
registerNavigationListeners();
if (mIsListeningRoadSnappedLocation) {
registerLocationListener();
}
}
}

@Override
public void onHostPause() {}

@Override
public void onHostDestroy() {}

private interface IRouteStatusResult {
void onResult(Navigator.RouteStatus code);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public List<ViewManager> createViewManagers(ReactApplicationContext reactContext
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> 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));

Expand Down
Loading

0 comments on commit 6674a4f

Please sign in to comment.