Skip to content

Commit f9d7c4a

Browse files
committed
fix: fragment disposal issues
- add better null checks for Android and iOS views
1 parent 9848783 commit f9d7c4a

File tree

11 files changed

+469
-255
lines changed

11 files changed

+469
-255
lines changed

android/src/main/java/com/google/android/react/navsdk/Command.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ public enum Command {
2323
SET_NAVIGATION_UI_ENABLED(5, "setNavigationUIEnabled"),
2424
SET_FOLLOWING_PERSPECTIVE(6, "setFollowingPerspective"),
2525
SET_NIGHT_MODE(7, "setNightMode"),
26-
DELETE_FRAGMENT(8, "deleteFragment"),
2726
SET_SPEEDOMETER_ENABLED(9, "setSpeedometerEnabled"),
2827
SET_SPEED_LIMIT_ICON_ENABLED(10, "setSpeedLimitIconEnabled"),
2928
SET_ZOOM_LEVEL(11, "setZoomLevel"),

android/src/main/java/com/google/android/react/navsdk/NavViewManager.java

Lines changed: 234 additions & 82 deletions
Large diffs are not rendered by default.

android/src/main/java/com/google/android/react/navsdk/NavViewModule.java

Lines changed: 21 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -160,32 +160,29 @@ public void isMyLocationEnabled(Integer viewId, final Promise promise) {
160160
public void addMarker(int viewId, ReadableMap markerOptionsMap, final Promise promise) {
161161
UiThreadUtil.runOnUiThread(
162162
() -> {
163-
if (mNavViewManager.getGoogleMap(viewId) != null) {
164-
Marker marker =
165-
mNavViewManager
166-
.getFragmentForViewId(viewId)
167-
.getMapController()
168-
.addMarker(markerOptionsMap.toHashMap());
169-
170-
promise.resolve(ObjectTranslationUtil.getMapFromMarker(marker));
163+
IMapViewFragment fragment = mNavViewManager.getFragmentForViewId(viewId);
164+
if (fragment == null) {
165+
promise.reject(JsErrors.NO_MAP_ERROR_CODE, JsErrors.NO_MAP_ERROR_MESSAGE);
166+
return;
171167
}
168+
169+
Marker marker = fragment.getMapController().addMarker(markerOptionsMap.toHashMap());
170+
promise.resolve(ObjectTranslationUtil.getMapFromMarker(marker));
172171
});
173172
}
174173

175174
@ReactMethod
176175
public void addPolyline(int viewId, ReadableMap polylineOptionsMap, final Promise promise) {
177176
UiThreadUtil.runOnUiThread(
178177
() -> {
179-
if (mNavViewManager.getGoogleMap(viewId) == null) {
178+
IMapViewFragment fragment = mNavViewManager.getFragmentForViewId(viewId);
179+
if (fragment == null) {
180180
promise.reject(JsErrors.NO_MAP_ERROR_CODE, JsErrors.NO_MAP_ERROR_MESSAGE);
181181
return;
182182
}
183-
Polyline polyline =
184-
mNavViewManager
185-
.getFragmentForViewId(viewId)
186-
.getMapController()
187-
.addPolyline(polylineOptionsMap.toHashMap());
188183

184+
Polyline polyline =
185+
fragment.getMapController().addPolyline(polylineOptionsMap.toHashMap());
189186
promise.resolve(ObjectTranslationUtil.getMapFromPolyline(polyline));
190187
});
191188
}
@@ -194,16 +191,13 @@ public void addPolyline(int viewId, ReadableMap polylineOptionsMap, final Promis
194191
public void addPolygon(int viewId, ReadableMap polygonOptionsMap, final Promise promise) {
195192
UiThreadUtil.runOnUiThread(
196193
() -> {
197-
if (mNavViewManager.getGoogleMap(viewId) == null) {
194+
IMapViewFragment fragment = mNavViewManager.getFragmentForViewId(viewId);
195+
if (fragment == null) {
198196
promise.reject(JsErrors.NO_MAP_ERROR_CODE, JsErrors.NO_MAP_ERROR_MESSAGE);
199197
return;
200198
}
201-
Polygon polygon =
202-
mNavViewManager
203-
.getFragmentForViewId(viewId)
204-
.getMapController()
205-
.addPolygon(polygonOptionsMap.toHashMap());
206199

200+
Polygon polygon = fragment.getMapController().addPolygon(polygonOptionsMap.toHashMap());
207201
promise.resolve(ObjectTranslationUtil.getMapFromPolygon(polygon));
208202
});
209203
}
@@ -212,16 +206,13 @@ public void addPolygon(int viewId, ReadableMap polygonOptionsMap, final Promise
212206
public void addCircle(int viewId, ReadableMap circleOptionsMap, final Promise promise) {
213207
UiThreadUtil.runOnUiThread(
214208
() -> {
215-
if (mNavViewManager.getGoogleMap(viewId) == null) {
209+
IMapViewFragment fragment = mNavViewManager.getFragmentForViewId(viewId);
210+
if (fragment == null) {
216211
promise.reject(JsErrors.NO_MAP_ERROR_CODE, JsErrors.NO_MAP_ERROR_MESSAGE);
217212
return;
218213
}
219-
Circle circle =
220-
mNavViewManager
221-
.getFragmentForViewId(viewId)
222-
.getMapController()
223-
.addCircle(circleOptionsMap.toHashMap());
224214

215+
Circle circle = fragment.getMapController().addCircle(circleOptionsMap.toHashMap());
225216
promise.resolve(ObjectTranslationUtil.getMapFromCircle(circle));
226217
});
227218
}
@@ -230,16 +221,14 @@ public void addCircle(int viewId, ReadableMap circleOptionsMap, final Promise pr
230221
public void addGroundOverlay(int viewId, ReadableMap overlayOptionsMap, final Promise promise) {
231222
UiThreadUtil.runOnUiThread(
232223
() -> {
233-
if (mNavViewManager.getGoogleMap(viewId) == null) {
224+
IMapViewFragment fragment = mNavViewManager.getFragmentForViewId(viewId);
225+
if (fragment == null) {
234226
promise.reject(JsErrors.NO_MAP_ERROR_CODE, JsErrors.NO_MAP_ERROR_MESSAGE);
235227
return;
236228
}
237-
GroundOverlay overlay =
238-
mNavViewManager
239-
.getFragmentForViewId(viewId)
240-
.getMapController()
241-
.addGroundOverlay(overlayOptionsMap.toHashMap());
242229

230+
GroundOverlay overlay =
231+
fragment.getMapController().addGroundOverlay(overlayOptionsMap.toHashMap());
243232
promise.resolve(ObjectTranslationUtil.getMapFromGroundOverlay(overlay));
244233
});
245234
}

example/ios/SampleApp.xcodeproj/project.pbxproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -817,6 +817,7 @@
817817
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
818818
SDKROOT = iphoneos;
819819
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) DEBUG";
820+
SWIFT_ENABLE_EXPLICIT_MODULES = NO;
820821
USE_HERMES = true;
821822
};
822823
name = Debug;
@@ -884,6 +885,7 @@
884885
);
885886
REACT_NATIVE_PATH = "${PODS_ROOT}/../../node_modules/react-native";
886887
SDKROOT = iphoneos;
888+
SWIFT_ENABLE_EXPLICIT_MODULES = NO;
887889
USE_HERMES = true;
888890
VALIDATE_PRODUCT = YES;
889891
};

ios/react-native-navigation-sdk/NavModule.m

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -411,8 +411,9 @@ - (void)showTermsAndConditionsDialog {
411411
}
412412

413413
- (void)setDisplayOptionsToViews:(NSDictionary *)options {
414-
for (NavViewController *viewController in [NavViewModule sharedInstance]
415-
.viewControllers.allValues) {
414+
NSEnumerator *enumerator = [[NavViewModule sharedInstance].viewControllers objectEnumerator];
415+
NavViewController *viewController;
416+
while ((viewController = [enumerator nextObject])) {
416417
if (options[@"showDestinationMarkers"] != nil) {
417418
[viewController
418419
setShowDestinationMarkersEnabled:[options[@"showDestinationMarkers"] boolValue]];

ios/react-native-navigation-sdk/NavView.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@
3535
@property(nonatomic, copy) RCTDirectEventBlock onGroundOverlayClick;
3636
@property(nonatomic, copy) RCTDirectEventBlock onPromptVisibilityChanged;
3737

38+
// Cleanup block that will be called when the view is removed from superview
39+
@property(nonatomic, copy) void (^cleanupBlock)(NSNumber *reactTag);
40+
3841
- (NavViewController *)initializeViewControllerWithStylingOptions:(NSDictionary *)stylingOptions
3942
fragmentType:(FragmentType)fragmentType;
4043
- (NavViewController *)getViewController;

ios/react-native-navigation-sdk/NavView.m

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,4 +126,14 @@ - (void)handleGroundOverlayClick:(GMSGroundOverlay *)groundOverlay {
126126
}
127127
}
128128

129+
- (void)willMoveToSuperview:(UIView *)newSuperview {
130+
[super willMoveToSuperview:newSuperview];
131+
if (newSuperview == nil && _viewController && self.cleanupBlock) {
132+
// As newSuperview is nil, the view is being removed from its superview, call the cleanup block
133+
// provided by the view manager
134+
self.cleanupBlock(self.reactTag);
135+
_viewController = nil;
136+
}
137+
}
138+
129139
@end

ios/react-native-navigation-sdk/NavViewController.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,6 @@ typedef void (^OnStringResult)(NSString *result);
3030
typedef void (^OnBooleanResult)(BOOL result);
3131
typedef void (^OnDictionaryResult)(NSDictionary *_Nullable result);
3232
typedef void (^OnArrayResult)(NSArray *_Nullable result);
33-
- (instancetype)initWithWidth:(CGFloat)width height:(CGFloat)height;
34-
- (void)updateLayoutWithWidth:(CGFloat)width height:(CGFloat)height;
3533
- (void)setStylingOptions:(nonnull NSDictionary *)stylingOptions;
3634
- (void)getCameraPosition:(OnDictionaryResult)completionBlock;
3735
- (void)getMyLocation:(OnDictionaryResult)completionBlock;

ios/react-native-navigation-sdk/NavViewModule.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
NS_ASSUME_NONNULL_BEGIN
2121

2222
@interface NavViewModule : NSObject <RCTBridgeModule>
23-
@property(nonatomic, strong) NSMutableDictionary<NSNumber *, NavViewController *> *viewControllers;
23+
@property(nonatomic, strong) NSMapTable<NSNumber *, NavViewController *> *viewControllers;
2424

2525
- (void)attachViewsToNavigationSession:(GMSNavigationSession *)session;
2626
- (void)informPromptVisibilityChange:(BOOL)visible;

ios/react-native-navigation-sdk/NavViewModule.m

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,25 +38,31 @@ + (instancetype)sharedInstance {
3838
}
3939

4040
- (void)attachViewsToNavigationSession:(GMSNavigationSession *)session {
41-
for (NavViewController *viewController in self.viewControllers.allValues) {
41+
NSEnumerator *enumerator = [self.viewControllers objectEnumerator];
42+
NavViewController *viewController;
43+
while ((viewController = [enumerator nextObject])) {
4244
[viewController attachToNavigationSession:session];
4345
}
4446
}
4547

4648
- (void)informPromptVisibilityChange:(BOOL)visible {
47-
for (NavViewController *viewController in self.viewControllers.allValues) {
49+
NSEnumerator *enumerator = [self.viewControllers objectEnumerator];
50+
NavViewController *viewController;
51+
while ((viewController = [enumerator nextObject])) {
4852
[viewController onPromptVisibilityChange:visible];
4953
}
5054
}
5155

5256
- (void)setTravelMode:(GMSNavigationTravelMode)travelMode {
53-
for (NavViewController *viewController in self.viewControllers.allValues) {
57+
NSEnumerator *enumerator = [self.viewControllers objectEnumerator];
58+
NavViewController *viewController;
59+
while ((viewController = [enumerator nextObject])) {
5460
[viewController setTravelMode:travelMode];
5561
}
5662
}
5763

5864
- (NavViewController *)getViewControllerForTag:(NSNumber *)reactTag {
59-
NavViewController *viewController = self.viewControllers[reactTag];
65+
NavViewController *viewController = [self.viewControllers objectForKey:reactTag];
6066
return viewController;
6167
}
6268

0 commit comments

Comments
 (0)