Skip to content

Commit 3d9246e

Browse files
fix(android): prevent NullPointerException crashes during setRoot when activity is destroyed
Initialize layoutFactory early if activity is available on TurboModule init, and guard against null activity before creating the view tree. Adds null-safe access to activity, window, and decorView across Presenter, LayoutDirectionApplier, and RootPresenter.
1 parent 04ad933 commit 3d9246e

4 files changed

Lines changed: 17 additions & 3 deletions

File tree

android/src/main/java/com/reactnativenavigation/react/NavigationTurboModule.kt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,14 @@ class NavigationTurboModule(
3232
private lateinit var eventEmitter: EventEmitter
3333

3434
init {
35+
activity()?.let { act ->
36+
act.navigator?.let { nav ->
37+
eventEmitter = EventEmitter(reactContext)
38+
layoutFactory.init(act, eventEmitter, nav.childRegistry,
39+
(reactApplicationContext.applicationContext as NavigationApplication).externalComponents)
40+
}
41+
}
42+
3543
reactContext.addLifecycleEventListener(object : LifecycleEventListenerAdapter() {
3644
override fun onHostPause() {
3745
super.onHostPause()
@@ -83,12 +91,12 @@ class NavigationTurboModule(
8391
)
8492
handle {
8593
Log.d("NavigationTurboModule", "setRoot handle ${Thread.currentThread()}")
86-
val viewController = layoutFactory.create(layoutTree)
8794
val activity = currentActivity
8895
if (activity == null) {
8996
promise.reject("ACTIVITY_NULL", "Activity is null")
9097
return@handle
9198
}
99+
val viewController = layoutFactory.create(layoutTree)
92100
navigator()?.setRoot(
93101
viewController,
94102
NativeCommandListener("setRoot", commandId, promise, eventEmitter, now)

android/src/main/java/com/reactnativenavigation/viewcontrollers/viewcontroller/LayoutDirectionApplier.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ class LayoutDirectionApplier {
1010
val currentContext = root.view?.context ?: return
1111

1212
if (options.layout.direction.hasValue()) {
13-
root.activity.window.decorView.layoutDirection = options.layout.direction.get()
13+
val direction = options.layout.direction.get()
14+
root.activity?.window?.decorView?.let { decor ->
15+
decor.layoutDirection = direction
16+
}
1417
I18nUtil.instance.allowRTL(currentContext, options.layout.direction.isRtl)
1518
I18nUtil.instance.forceRTL(currentContext, options.layout.direction.isRtl)
1619
}

android/src/main/java/com/reactnativenavigation/viewcontrollers/viewcontroller/Presenter.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,7 @@ private void applyNavigationBarVisibility(NavigationBarOptions options) {
137137
}
138138

139139
private void setNavigationBarBackgroundColor(NavigationBarOptions navigationBar) {
140+
if (activity == null) return;
140141
int defaultColor = SystemUiUtils.getDefaultNavBarColor();
141142
if (navigationBar.backgroundColor.canApplyValue()) {
142143
int color = navigationBar.backgroundColor.get(defaultColor);

android/src/main/java/com/reactnativenavigation/viewcontrollers/viewcontroller/RootPresenter.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,11 @@ public RootPresenter(RootAnimator animator, LayoutDirectionApplier layoutDirecti
3535
}
3636

3737
public void setRoot(ViewController appearingRoot, ViewController<?> disappearingRoot, Options defaultOptions, CommandListener listener) {
38-
layoutDirectionApplier.apply(appearingRoot, defaultOptions);
3938
if (appearingRoot.isDestroyed()) {
4039
listener.onError("Could not set root - appearingRoot is already destroyed");
4140
return;
4241
}
42+
layoutDirectionApplier.apply(appearingRoot, defaultOptions);
4343
rootLayout.addView(appearingRoot.getView(), matchParentWithBehaviour(new BehaviourDelegate(appearingRoot)));
4444
Options options = appearingRoot.resolveCurrentOptions(defaultOptions);
4545
AnimationOptions enter = options.animations.setRoot.getEnter();
@@ -78,6 +78,8 @@ private void animateSetRootAndReportSuccess(ViewController root,
7878
() -> {
7979
if (!root.isDestroyed()) {
8080
listener.onSuccess(root.getId());
81+
} else {
82+
listener.onError("Could not set root - root was destroyed before animation completed");
8183
}
8284
return Unit.INSTANCE;
8385
});

0 commit comments

Comments
 (0)