From 7d105571ce13c160b093f6e05b04fec13edfb450 Mon Sep 17 00:00:00 2001 From: Oskar Eichler Date: Mon, 31 Aug 2026 03:01:34 +0300 Subject: [PATCH] Fixed commands with non-navigation activities --- .../react/NavigationModule.java | 28 +++++++++++++------ .../react/NavigationTurboModule.kt | 16 +++++------ .../react/NavigationModuleTest.java | 19 +++++++++++++ .../react/NavigationTurboModuleTest.kt | 20 +++++++++++++ 4 files changed, 66 insertions(+), 17 deletions(-) create mode 100644 android/src/test/java/com/reactnativenavigation/react/NavigationTurboModuleTest.kt diff --git a/android/src/main/java/com/reactnativenavigation/react/NavigationModule.java b/android/src/main/java/com/reactnativenavigation/react/NavigationModule.java index 2b773d0fdb7..d68aeb9b7cf 100644 --- a/android/src/main/java/com/reactnativenavigation/react/NavigationModule.java +++ b/android/src/main/java/com/reactnativenavigation/react/NavigationModule.java @@ -35,6 +35,7 @@ import static com.reactnativenavigation.utils.UiUtils.pxToDp; import android.app.Activity; +import android.util.Log; public class NavigationModule extends ReactContextBaseJavaModule { private static final String NAME = "RNNBridgeModule"; @@ -58,21 +59,25 @@ public NavigationModule(ReactApplicationContext reactContext, JSONParser jsonPa public void onHostPause() { super.onHostPause(); UiUtils.runOnMainThread(() -> { - if (activity() != null) navigator().onHostPause(); + final NavigationActivity activity = activity(); + if (activity != null) activity.getNavigator().onHostPause(); }); } @Override public void onHostResume() { + final NavigationActivity activity = activity(); + if (activity == null) return; + final Navigator navigator = activity.getNavigator(); eventEmitter = new EventEmitter(reactContext); - navigator().setEventEmitter(eventEmitter); + navigator.setEventEmitter(eventEmitter); layoutFactory.init( - activity(), + activity, eventEmitter, - navigator().getChildRegistry(), - ((NavigationApplication) activity().getApplication()).getExternalComponents() + navigator.getChildRegistry(), + ((NavigationApplication) activity.getApplication()).getExternalComponents() ); - UiUtils.runOnMainThread(() -> navigator().onHostResume()); + UiUtils.runOnMainThread(navigator::onHostResume); } }); } @@ -221,14 +226,21 @@ private Options parse(@Nullable ReadableMap mergeOptions) { protected void handle(Runnable task) { UiThread.post(() -> { - if (getCurrentActivity() != null && !activity().isFinishing()) { + final NavigationActivity activity = activity(); + if (activity != null && !activity.isFinishing()) { task.run(); } }); } + @Nullable protected NavigationActivity activity() { - return (NavigationActivity) getCurrentActivity(); + final Activity activity = getReactApplicationContext().getCurrentActivity(); + if (!(activity instanceof NavigationActivity)) { + Log.e("NavigationModule", "current activity is not a NavigationActivity"); + return null; + } + return (NavigationActivity) activity; } @Override diff --git a/android/src/main/java/com/reactnativenavigation/react/NavigationTurboModule.kt b/android/src/main/java/com/reactnativenavigation/react/NavigationTurboModule.kt index de36aa609a5..5a2fdc093fb 100644 --- a/android/src/main/java/com/reactnativenavigation/react/NavigationTurboModule.kt +++ b/android/src/main/java/com/reactnativenavigation/react/NavigationTurboModule.kt @@ -65,7 +65,10 @@ class NavigationTurboModule( UiUtils.getBottomTabsHeight(reactApplicationContext).toFloat() ).toDouble() constants[Constants.STATUS_BAR_HEIGHT_KEY] = - UiUtils.pxToDp(reactApplicationContext, getStatusBarHeight(currentActivity).toFloat()) + UiUtils.pxToDp( + reactApplicationContext, + getStatusBarHeight(reactApplicationContext.getCurrentActivity()).toFloat() + ) .toDouble() constants[Constants.TOP_BAR_HEIGHT_KEY] = UiUtils.pxToDp( reactApplicationContext, @@ -84,11 +87,6 @@ class NavigationTurboModule( handle { Log.d("NavigationTurboModule", "setRoot handle ${Thread.currentThread()}") val viewController = layoutFactory.create(layoutTree) - val activity = currentActivity - if (activity == null) { - promise.reject("ACTIVITY_NULL", "Activity is null") - return@handle - } navigator()?.setRoot( viewController, NativeCommandListener("setRoot", commandId, promise, eventEmitter, now) @@ -295,11 +293,11 @@ class NavigationTurboModule( } private fun activity(): NavigationActivity? { - val activity = reactApplicationContext.currentActivity as NavigationActivity? + val activity = reactApplicationContext.getCurrentActivity() as? NavigationActivity if (activity == null) { - Log.e("NavigationTurboModule", "current activity is null!") + Log.e("NavigationTurboModule", "current activity is not a NavigationActivity") } - return currentActivity as NavigationActivity? + return activity } companion object { diff --git a/android/src/test/java/com/reactnativenavigation/react/NavigationModuleTest.java b/android/src/test/java/com/reactnativenavigation/react/NavigationModuleTest.java index 875e1e870c1..a1b89056edf 100644 --- a/android/src/test/java/com/reactnativenavigation/react/NavigationModuleTest.java +++ b/android/src/test/java/com/reactnativenavigation/react/NavigationModuleTest.java @@ -9,6 +9,7 @@ import static org.mockito.Mockito.when; import com.facebook.react.bridge.Promise; +import com.facebook.react.bridge.LifecycleEventListener; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReadableMap; import com.reactnativenavigation.BaseTest; @@ -22,6 +23,7 @@ import org.json.JSONException; import org.json.JSONObject; import org.junit.Test; +import org.mockito.ArgumentCaptor; import org.robolectric.annotation.LooperMode; import org.robolectric.shadows.ShadowLooper; @@ -78,6 +80,23 @@ public void postCommandsOnMainThread_doesNotCrashIfActivityIsNull() { verify(dontRun, times(0)).run(); } + @Test + public void foreignActivity_isIgnoredByCommandsLifecycleAndInvalidate() { + when(reactApplicationContext.getCurrentActivity()).thenReturn(newActivity()); + Runnable command = mock(Runnable.class); + + uut.handle(command); + ShadowLooper.idleMainLooper(); + verify(command, times(0)).run(); + + ArgumentCaptor listener = ArgumentCaptor.forClass(LifecycleEventListener.class); + verify(reactApplicationContext).addLifecycleEventListener(listener.capture()); + listener.getValue().onHostResume(); + listener.getValue().onHostPause(); + ShadowLooper.idleMainLooper(); + uut.invalidate(); + } + private NavigationActivity mockActivity() { NavigationActivity activity = mock(NavigationActivity.class); when(activity.getNavigator()).thenReturn(navigator); diff --git a/android/src/test/java/com/reactnativenavigation/react/NavigationTurboModuleTest.kt b/android/src/test/java/com/reactnativenavigation/react/NavigationTurboModuleTest.kt new file mode 100644 index 00000000000..4886d8a65e2 --- /dev/null +++ b/android/src/test/java/com/reactnativenavigation/react/NavigationTurboModuleTest.kt @@ -0,0 +1,20 @@ +package com.reactnativenavigation.react + +import com.facebook.react.bridge.ReactApplicationContext +import com.reactnativenavigation.BaseTest +import com.reactnativenavigation.options.LayoutFactory +import org.junit.Test +import org.mockito.kotlin.mock +import org.mockito.kotlin.whenever + +class NavigationTurboModuleTest : BaseTest() { + @Test + fun command_handlesNonNavigationActivity() { + val reactContext = mock() + whenever(reactContext.getCurrentActivity()).thenReturn(newActivity()) + val uut = NavigationTurboModule(reactContext, mock()) + + uut.setDefaultOptions(null) + idleMainLooper() + } +}