From 87a2354147f9880988fa5fdb7a9ea217f6a94a55 Mon Sep 17 00:00:00 2001 From: d4vidi Date: Sun, 17 Dec 2023 17:59:53 +0200 Subject: [PATCH 1/4] Busy-wait for activity on getLaunchArgs() --- .../react/NavigationModule.java | 62 ++++++++++--------- .../react/NullRNActivityWorkaround.kt | 22 +++++++ .../com/reactnativenavigation/utils/Sleep.kt | 9 +++ .../react/NavigationModuleTest.java | 4 +- 4 files changed, 67 insertions(+), 30 deletions(-) create mode 100644 lib/android/app/src/main/java/com/reactnativenavigation/react/NullRNActivityWorkaround.kt create mode 100644 lib/android/app/src/main/java/com/reactnativenavigation/utils/Sleep.kt diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/react/NavigationModule.java b/lib/android/app/src/main/java/com/reactnativenavigation/react/NavigationModule.java index 4cc09eb770..4658afbff8 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/react/NavigationModule.java +++ b/lib/android/app/src/main/java/com/reactnativenavigation/react/NavigationModule.java @@ -1,5 +1,9 @@ package com.reactnativenavigation.react; +import static com.reactnativenavigation.utils.UiUtils.pxToDp; + +import android.app.Activity; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -32,10 +36,6 @@ import java.util.ArrayList; import java.util.Objects; -import static com.reactnativenavigation.utils.UiUtils.pxToDp; - -import android.app.Activity; - public class NavigationModule extends ReactContextBaseJavaModule { private static final String NAME = "RNNBridgeModule"; @@ -87,6 +87,12 @@ public String getName() { @ReactMethod public void getLaunchArgs(String commandId, Promise promise) { + // This is work-around for the RN problem described here: + // https://github.com/facebook/react-native/issues/37518 + Activity activity = activity(); + if (activity == null) { + NullRNActivityWorkaround.waitForActivity(getReactApplicationContext()); + } promise.resolve(LaunchArgsParser.parse(activity())); } @@ -114,7 +120,7 @@ public WritableMap getNavigationConstantsSync() { @ReactMethod public void setRoot(String commandId, ReadableMap rawLayoutTree, Promise promise) { final LayoutNode layoutTree = LayoutNodeParser.parse(Objects.requireNonNull(jsonParser.parse(rawLayoutTree).optJSONObject("root"))); - handle(() -> { + runOnUIThread(() -> { final ViewController viewController = layoutFactory.create(layoutTree); navigator().setRoot(viewController, new NativeCommandListener("setRoot", commandId, promise, eventEmitter, now), reactInstanceManager); }); @@ -122,7 +128,7 @@ public void setRoot(String commandId, ReadableMap rawLayoutTree, Promise promise @ReactMethod public void setDefaultOptions(ReadableMap options) { - handle(() -> { + runOnUIThread(() -> { Options defaultOptions = parse(options); layoutFactory.setDefaultOptions(defaultOptions); navigator().setDefaultOptions(defaultOptions); @@ -131,13 +137,13 @@ public void setDefaultOptions(ReadableMap options) { @ReactMethod public void mergeOptions(String onComponentId, @Nullable ReadableMap options) { - handle(() -> navigator().mergeOptions(onComponentId, parse(options))); + runOnUIThread(() -> navigator().mergeOptions(onComponentId, parse(options))); } @ReactMethod public void push(String commandId, String onComponentId, ReadableMap rawLayoutTree, Promise promise) { final LayoutNode layoutTree = LayoutNodeParser.parse(jsonParser.parse(rawLayoutTree)); - handle(() -> { + runOnUIThread(() -> { final ViewController viewController = layoutFactory.create(layoutTree); navigator().push(onComponentId, viewController, new NativeCommandListener("push", commandId, promise, eventEmitter, now)); }); @@ -145,7 +151,7 @@ public void push(String commandId, String onComponentId, ReadableMap rawLayoutTr @ReactMethod public void setStackRoot(String commandId, String onComponentId, ReadableArray children, Promise promise) { - handle(() -> { + runOnUIThread(() -> { ArrayList> _children = new ArrayList<>(); for (int i = 0; i < children.size(); i++) { final LayoutNode layoutTree = LayoutNodeParser.parse(jsonParser.parse(children.getMap(i))); @@ -157,23 +163,23 @@ public void setStackRoot(String commandId, String onComponentId, ReadableArray c @ReactMethod public void pop(String commandId, String componentId, @Nullable ReadableMap mergeOptions, Promise promise) { - handle(() -> navigator().pop(componentId, parse(mergeOptions), new NativeCommandListener("pop", commandId, promise, eventEmitter, now))); + runOnUIThread(() -> navigator().pop(componentId, parse(mergeOptions), new NativeCommandListener("pop", commandId, promise, eventEmitter, now))); } @ReactMethod public void popTo(String commandId, String componentId, @Nullable ReadableMap mergeOptions, Promise promise) { - handle(() -> navigator().popTo(componentId, parse(mergeOptions), new NativeCommandListener("popTo", commandId, promise, eventEmitter, now))); + runOnUIThread(() -> navigator().popTo(componentId, parse(mergeOptions), new NativeCommandListener("popTo", commandId, promise, eventEmitter, now))); } @ReactMethod public void popToRoot(String commandId, String componentId, @Nullable ReadableMap mergeOptions, Promise promise) { - handle(() -> navigator().popToRoot(componentId, parse(mergeOptions), new NativeCommandListener("popToRoot", commandId, promise, eventEmitter, now))); + runOnUIThread(() -> navigator().popToRoot(componentId, parse(mergeOptions), new NativeCommandListener("popToRoot", commandId, promise, eventEmitter, now))); } @ReactMethod public void showModal(String commandId, ReadableMap rawLayoutTree, Promise promise) { final LayoutNode layoutTree = LayoutNodeParser.parse(jsonParser.parse(rawLayoutTree)); - handle(() -> { + runOnUIThread(() -> { final ViewController viewController = layoutFactory.create(layoutTree); navigator().showModal(viewController, new NativeCommandListener("showModal", commandId, promise, eventEmitter, now)); }); @@ -181,7 +187,7 @@ public void showModal(String commandId, ReadableMap rawLayoutTree, Promise promi @ReactMethod public void dismissModal(String commandId, String componentId, @Nullable ReadableMap mergeOptions, Promise promise) { - handle(() -> { + runOnUIThread(() -> { navigator().mergeOptions(componentId, parse(mergeOptions)); navigator().dismissModal(componentId, new NativeCommandListener("dismissModal", commandId, promise, eventEmitter, now)); }); @@ -189,13 +195,13 @@ public void dismissModal(String commandId, String componentId, @Nullable Readabl @ReactMethod public void dismissAllModals(String commandId, @Nullable ReadableMap mergeOptions, Promise promise) { - handle(() -> navigator().dismissAllModals(parse(mergeOptions), new NativeCommandListener("dismissAllModals", commandId, promise, eventEmitter, now))); + runOnUIThread(() -> navigator().dismissAllModals(parse(mergeOptions), new NativeCommandListener("dismissAllModals", commandId, promise, eventEmitter, now))); } @ReactMethod public void showOverlay(String commandId, ReadableMap rawLayoutTree, Promise promise) { final LayoutNode layoutTree = LayoutNodeParser.parse(jsonParser.parse(rawLayoutTree)); - handle(() -> { + runOnUIThread(() -> { final ViewController viewController = layoutFactory.create(layoutTree); navigator().showOverlay(viewController, new NativeCommandListener("showOverlay", commandId, promise, eventEmitter, now)); }); @@ -203,12 +209,21 @@ public void showOverlay(String commandId, ReadableMap rawLayoutTree, Promise pro @ReactMethod public void dismissOverlay(String commandId, String componentId, Promise promise) { - handle(() -> navigator().dismissOverlay(componentId, new NativeCommandListener("dismissOverlay", commandId, promise, eventEmitter, now))); + runOnUIThread(() -> navigator().dismissOverlay(componentId, new NativeCommandListener("dismissOverlay", commandId, promise, eventEmitter, now))); } @ReactMethod public void dismissAllOverlays(String commandId, Promise promise) { - handle(() -> navigator().dismissAllOverlays(new NativeCommandListener("dismissAllOverlays", commandId, promise, eventEmitter, now))); + runOnUIThread(() -> navigator().dismissAllOverlays(new NativeCommandListener("dismissAllOverlays", commandId, promise, eventEmitter, now))); + } + + @Override + public void onCatalystInstanceDestroy() { + final NavigationActivity navigationActivity = activity(); + if (navigationActivity != null) { + navigationActivity.onCatalystInstanceDestroy(); + } + super.onCatalystInstanceDestroy(); } private Navigator navigator() { @@ -221,7 +236,7 @@ private Options parse(@Nullable ReadableMap mergeOptions) { null ? Options.EMPTY : Options.parse(ctx, new TypefaceLoader(activity()), jsonParser.parse(mergeOptions)); } - protected void handle(Runnable task) { + protected void runOnUIThread(Runnable task) { UiThread.post(() -> { if (getCurrentActivity() != null && !activity().isFinishing()) { task.run(); @@ -232,13 +247,4 @@ protected void handle(Runnable task) { protected NavigationActivity activity() { return (NavigationActivity) getCurrentActivity(); } - - @Override - public void onCatalystInstanceDestroy() { - final NavigationActivity navigationActivity = activity(); - if (navigationActivity != null) { - navigationActivity.onCatalystInstanceDestroy(); - } - super.onCatalystInstanceDestroy(); - } } diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/react/NullRNActivityWorkaround.kt b/lib/android/app/src/main/java/com/reactnativenavigation/react/NullRNActivityWorkaround.kt new file mode 100644 index 0000000000..553bdf6495 --- /dev/null +++ b/lib/android/app/src/main/java/com/reactnativenavigation/react/NullRNActivityWorkaround.kt @@ -0,0 +1,22 @@ +package com.reactnativenavigation.react + +import com.facebook.react.bridge.ReactApplicationContext +import com.reactnativenavigation.utils.sleep + +private const val ACTIVITY_WAIT_INTERVAL = 100L +private const val ACTIVITY_WAIT_TRIES = 150 + +object NullRNActivityWorkaround { + @JvmStatic + fun waitForActivity(reactAppContext: ReactApplicationContext) { + var tries = 0 + while (tries < ACTIVITY_WAIT_TRIES && !isActivityReady(reactAppContext)) { + sleep(ACTIVITY_WAIT_INTERVAL) + tries++ + } + } + + private fun isActivityReady(reactAppContext: ReactApplicationContext): Boolean { + return reactAppContext.hasCurrentActivity() + } +} diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/utils/Sleep.kt b/lib/android/app/src/main/java/com/reactnativenavigation/utils/Sleep.kt new file mode 100644 index 0000000000..a539b98b74 --- /dev/null +++ b/lib/android/app/src/main/java/com/reactnativenavigation/utils/Sleep.kt @@ -0,0 +1,9 @@ +package com.reactnativenavigation.utils + +fun sleep(ms: Long) { + try { + Thread.sleep(ms) + } catch (e: InterruptedException) { + e.printStackTrace() + } +} diff --git a/lib/android/app/src/test/java/com/reactnativenavigation/react/NavigationModuleTest.java b/lib/android/app/src/test/java/com/reactnativenavigation/react/NavigationModuleTest.java index cece228e45..35b820abd8 100644 --- a/lib/android/app/src/test/java/com/reactnativenavigation/react/NavigationModuleTest.java +++ b/lib/android/app/src/test/java/com/reactnativenavigation/react/NavigationModuleTest.java @@ -69,13 +69,13 @@ public void postCommandsOnMainThread_doesNotCrashIfActivityIsNull() { NavigationModule spy = spy(uut); when(spy.activity()).thenReturn(mock(NavigationActivity.class)); Runnable runnable = mock(Runnable.class); - spy.handle(runnable); + spy.runOnUIThread(runnable); ShadowLooper.idleMainLooper(); verify(runnable).run(); when(spy.activity()).thenReturn(null); Runnable dontRun = mock(Runnable.class); - spy.handle(dontRun); + spy.runOnUIThread(dontRun); ShadowLooper.idleMainLooper(); verify(dontRun, times(0)).run(); } From 892b41b3c47f102f7f8eca572808df0836139c8e Mon Sep 17 00:00:00 2001 From: d4vidi Date: Sun, 17 Dec 2023 19:00:32 +0200 Subject: [PATCH 2/4] Revert NavigationModule.handle() method rename --- .../react/NavigationModule.java | 33 +++++++++---------- .../react/NavigationModuleTest.java | 4 +-- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/react/NavigationModule.java b/lib/android/app/src/main/java/com/reactnativenavigation/react/NavigationModule.java index 4658afbff8..a12f8898e0 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/react/NavigationModule.java +++ b/lib/android/app/src/main/java/com/reactnativenavigation/react/NavigationModule.java @@ -89,8 +89,7 @@ public String getName() { public void getLaunchArgs(String commandId, Promise promise) { // This is work-around for the RN problem described here: // https://github.com/facebook/react-native/issues/37518 - Activity activity = activity(); - if (activity == null) { + if (activity() == null) { NullRNActivityWorkaround.waitForActivity(getReactApplicationContext()); } promise.resolve(LaunchArgsParser.parse(activity())); @@ -120,7 +119,7 @@ public WritableMap getNavigationConstantsSync() { @ReactMethod public void setRoot(String commandId, ReadableMap rawLayoutTree, Promise promise) { final LayoutNode layoutTree = LayoutNodeParser.parse(Objects.requireNonNull(jsonParser.parse(rawLayoutTree).optJSONObject("root"))); - runOnUIThread(() -> { + handle(() -> { final ViewController viewController = layoutFactory.create(layoutTree); navigator().setRoot(viewController, new NativeCommandListener("setRoot", commandId, promise, eventEmitter, now), reactInstanceManager); }); @@ -128,7 +127,7 @@ public void setRoot(String commandId, ReadableMap rawLayoutTree, Promise promise @ReactMethod public void setDefaultOptions(ReadableMap options) { - runOnUIThread(() -> { + handle(() -> { Options defaultOptions = parse(options); layoutFactory.setDefaultOptions(defaultOptions); navigator().setDefaultOptions(defaultOptions); @@ -137,13 +136,13 @@ public void setDefaultOptions(ReadableMap options) { @ReactMethod public void mergeOptions(String onComponentId, @Nullable ReadableMap options) { - runOnUIThread(() -> navigator().mergeOptions(onComponentId, parse(options))); + handle(() -> navigator().mergeOptions(onComponentId, parse(options))); } @ReactMethod public void push(String commandId, String onComponentId, ReadableMap rawLayoutTree, Promise promise) { final LayoutNode layoutTree = LayoutNodeParser.parse(jsonParser.parse(rawLayoutTree)); - runOnUIThread(() -> { + handle(() -> { final ViewController viewController = layoutFactory.create(layoutTree); navigator().push(onComponentId, viewController, new NativeCommandListener("push", commandId, promise, eventEmitter, now)); }); @@ -151,7 +150,7 @@ public void push(String commandId, String onComponentId, ReadableMap rawLayoutTr @ReactMethod public void setStackRoot(String commandId, String onComponentId, ReadableArray children, Promise promise) { - runOnUIThread(() -> { + handle(() -> { ArrayList> _children = new ArrayList<>(); for (int i = 0; i < children.size(); i++) { final LayoutNode layoutTree = LayoutNodeParser.parse(jsonParser.parse(children.getMap(i))); @@ -163,23 +162,23 @@ public void setStackRoot(String commandId, String onComponentId, ReadableArray c @ReactMethod public void pop(String commandId, String componentId, @Nullable ReadableMap mergeOptions, Promise promise) { - runOnUIThread(() -> navigator().pop(componentId, parse(mergeOptions), new NativeCommandListener("pop", commandId, promise, eventEmitter, now))); + handle(() -> navigator().pop(componentId, parse(mergeOptions), new NativeCommandListener("pop", commandId, promise, eventEmitter, now))); } @ReactMethod public void popTo(String commandId, String componentId, @Nullable ReadableMap mergeOptions, Promise promise) { - runOnUIThread(() -> navigator().popTo(componentId, parse(mergeOptions), new NativeCommandListener("popTo", commandId, promise, eventEmitter, now))); + handle(() -> navigator().popTo(componentId, parse(mergeOptions), new NativeCommandListener("popTo", commandId, promise, eventEmitter, now))); } @ReactMethod public void popToRoot(String commandId, String componentId, @Nullable ReadableMap mergeOptions, Promise promise) { - runOnUIThread(() -> navigator().popToRoot(componentId, parse(mergeOptions), new NativeCommandListener("popToRoot", commandId, promise, eventEmitter, now))); + handle(() -> navigator().popToRoot(componentId, parse(mergeOptions), new NativeCommandListener("popToRoot", commandId, promise, eventEmitter, now))); } @ReactMethod public void showModal(String commandId, ReadableMap rawLayoutTree, Promise promise) { final LayoutNode layoutTree = LayoutNodeParser.parse(jsonParser.parse(rawLayoutTree)); - runOnUIThread(() -> { + handle(() -> { final ViewController viewController = layoutFactory.create(layoutTree); navigator().showModal(viewController, new NativeCommandListener("showModal", commandId, promise, eventEmitter, now)); }); @@ -187,7 +186,7 @@ public void showModal(String commandId, ReadableMap rawLayoutTree, Promise promi @ReactMethod public void dismissModal(String commandId, String componentId, @Nullable ReadableMap mergeOptions, Promise promise) { - runOnUIThread(() -> { + handle(() -> { navigator().mergeOptions(componentId, parse(mergeOptions)); navigator().dismissModal(componentId, new NativeCommandListener("dismissModal", commandId, promise, eventEmitter, now)); }); @@ -195,13 +194,13 @@ public void dismissModal(String commandId, String componentId, @Nullable Readabl @ReactMethod public void dismissAllModals(String commandId, @Nullable ReadableMap mergeOptions, Promise promise) { - runOnUIThread(() -> navigator().dismissAllModals(parse(mergeOptions), new NativeCommandListener("dismissAllModals", commandId, promise, eventEmitter, now))); + handle(() -> navigator().dismissAllModals(parse(mergeOptions), new NativeCommandListener("dismissAllModals", commandId, promise, eventEmitter, now))); } @ReactMethod public void showOverlay(String commandId, ReadableMap rawLayoutTree, Promise promise) { final LayoutNode layoutTree = LayoutNodeParser.parse(jsonParser.parse(rawLayoutTree)); - runOnUIThread(() -> { + handle(() -> { final ViewController viewController = layoutFactory.create(layoutTree); navigator().showOverlay(viewController, new NativeCommandListener("showOverlay", commandId, promise, eventEmitter, now)); }); @@ -209,12 +208,12 @@ public void showOverlay(String commandId, ReadableMap rawLayoutTree, Promise pro @ReactMethod public void dismissOverlay(String commandId, String componentId, Promise promise) { - runOnUIThread(() -> navigator().dismissOverlay(componentId, new NativeCommandListener("dismissOverlay", commandId, promise, eventEmitter, now))); + handle(() -> navigator().dismissOverlay(componentId, new NativeCommandListener("dismissOverlay", commandId, promise, eventEmitter, now))); } @ReactMethod public void dismissAllOverlays(String commandId, Promise promise) { - runOnUIThread(() -> navigator().dismissAllOverlays(new NativeCommandListener("dismissAllOverlays", commandId, promise, eventEmitter, now))); + handle(() -> navigator().dismissAllOverlays(new NativeCommandListener("dismissAllOverlays", commandId, promise, eventEmitter, now))); } @Override @@ -236,7 +235,7 @@ private Options parse(@Nullable ReadableMap mergeOptions) { null ? Options.EMPTY : Options.parse(ctx, new TypefaceLoader(activity()), jsonParser.parse(mergeOptions)); } - protected void runOnUIThread(Runnable task) { + protected void handle(Runnable task) { UiThread.post(() -> { if (getCurrentActivity() != null && !activity().isFinishing()) { task.run(); diff --git a/lib/android/app/src/test/java/com/reactnativenavigation/react/NavigationModuleTest.java b/lib/android/app/src/test/java/com/reactnativenavigation/react/NavigationModuleTest.java index 35b820abd8..cece228e45 100644 --- a/lib/android/app/src/test/java/com/reactnativenavigation/react/NavigationModuleTest.java +++ b/lib/android/app/src/test/java/com/reactnativenavigation/react/NavigationModuleTest.java @@ -69,13 +69,13 @@ public void postCommandsOnMainThread_doesNotCrashIfActivityIsNull() { NavigationModule spy = spy(uut); when(spy.activity()).thenReturn(mock(NavigationActivity.class)); Runnable runnable = mock(Runnable.class); - spy.runOnUIThread(runnable); + spy.handle(runnable); ShadowLooper.idleMainLooper(); verify(runnable).run(); when(spy.activity()).thenReturn(null); Runnable dontRun = mock(Runnable.class); - spy.runOnUIThread(dontRun); + spy.handle(dontRun); ShadowLooper.idleMainLooper(); verify(dontRun, times(0)).run(); } From 2f93b9ef602e6c172ecc63ea5dff3424219fa9b3 Mon Sep 17 00:00:00 2001 From: d4vidi Date: Sun, 17 Dec 2023 19:34:57 +0200 Subject: [PATCH 3/4] Make work-around robust, host termination-aware --- .../react/NavigationModule.java | 11 ++-- .../react/NullRNActivityWorkaround.kt | 53 +++++++++++++++---- .../com/reactnativenavigation/utils/Sleep.kt | 2 +- 3 files changed, 49 insertions(+), 17 deletions(-) diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/react/NavigationModule.java b/lib/android/app/src/main/java/com/reactnativenavigation/react/NavigationModule.java index a12f8898e0..c0ee431b01 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/react/NavigationModule.java +++ b/lib/android/app/src/main/java/com/reactnativenavigation/react/NavigationModule.java @@ -44,6 +44,7 @@ public class NavigationModule extends ReactContextBaseJavaModule { private final JSONParser jsonParser; private final LayoutFactory layoutFactory; private EventEmitter eventEmitter; + private final NullRNActivityWorkaround nullRNActivityWorkaround; @SuppressWarnings("WeakerAccess") public NavigationModule(ReactApplicationContext reactContext, ReactInstanceManager reactInstanceManager, LayoutFactory layoutFactory) { @@ -55,6 +56,8 @@ public NavigationModule(ReactApplicationContext reactContext, ReactInstanceManag this.reactInstanceManager = reactInstanceManager; this.jsonParser = jsonParser; this.layoutFactory = layoutFactory; + this.nullRNActivityWorkaround = new NullRNActivityWorkaround(reactContext); + reactContext.addLifecycleEventListener(new LifecycleEventListenerAdapter() { @Override public void onHostPause() { @@ -87,12 +90,8 @@ public String getName() { @ReactMethod public void getLaunchArgs(String commandId, Promise promise) { - // This is work-around for the RN problem described here: - // https://github.com/facebook/react-native/issues/37518 - if (activity() == null) { - NullRNActivityWorkaround.waitForActivity(getReactApplicationContext()); - } - promise.resolve(LaunchArgsParser.parse(activity())); + Activity activity = nullRNActivityWorkaround.getActivity(); + promise.resolve(LaunchArgsParser.parse(activity)); } private WritableMap createNavigationConstantsMap() { diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/react/NullRNActivityWorkaround.kt b/lib/android/app/src/main/java/com/reactnativenavigation/react/NullRNActivityWorkaround.kt index 553bdf6495..d1cc4fabba 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/react/NullRNActivityWorkaround.kt +++ b/lib/android/app/src/main/java/com/reactnativenavigation/react/NullRNActivityWorkaround.kt @@ -1,22 +1,55 @@ package com.reactnativenavigation.react +import android.app.Activity +import android.util.Log +import com.facebook.react.bridge.LifecycleEventListener import com.facebook.react.bridge.ReactApplicationContext -import com.reactnativenavigation.utils.sleep +import com.reactnativenavigation.utils.sleepSafe +import java.lang.ref.WeakReference private const val ACTIVITY_WAIT_INTERVAL = 100L private const val ACTIVITY_WAIT_TRIES = 150 -object NullRNActivityWorkaround { - @JvmStatic - fun waitForActivity(reactAppContext: ReactApplicationContext) { +private const val LOG_TAG = "RNN.NullActivity" + +/** + * This is a helper class to work-around this RN problem: https://github.com/facebook/react-native/issues/37518 + * + * It is, hopefully, temporary. + */ +class NullRNActivityWorkaround(reactAppContext: ReactApplicationContext) { + private val reactAppContext: WeakReference + @Volatile + private var hasHost = true + + init { + this.reactAppContext = WeakReference(reactAppContext) + reactAppContext.addLifecycleEventListener(object: LifecycleEventListener { + override fun onHostDestroy() { + Log.d(LOG_TAG, "HOST_DESTROY") + hasHost = false + } + override fun onHostResume() { + Log.d(LOG_TAG, "HOST_RESUME") + hasHost = true + } + override fun onHostPause() {} + }) + } + + fun getActivity(): Activity? { + waitForActivity() + return reactAppContext.get()?.currentActivity + } + + private fun waitForActivity() { + val activityReady = { (reactAppContext.get()?.hasCurrentActivity() ?: false) } + var tries = 0 - while (tries < ACTIVITY_WAIT_TRIES && !isActivityReady(reactAppContext)) { - sleep(ACTIVITY_WAIT_INTERVAL) + while (tries < ACTIVITY_WAIT_TRIES && hasHost && !activityReady()) { + Log.d(LOG_TAG, "Busy-waiting for activity! Try: #$tries...") + sleepSafe(ACTIVITY_WAIT_INTERVAL) tries++ } } - - private fun isActivityReady(reactAppContext: ReactApplicationContext): Boolean { - return reactAppContext.hasCurrentActivity() - } } diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/utils/Sleep.kt b/lib/android/app/src/main/java/com/reactnativenavigation/utils/Sleep.kt index a539b98b74..93da616026 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/utils/Sleep.kt +++ b/lib/android/app/src/main/java/com/reactnativenavigation/utils/Sleep.kt @@ -1,6 +1,6 @@ package com.reactnativenavigation.utils -fun sleep(ms: Long) { +fun sleepSafe(ms: Long) { try { Thread.sleep(ms) } catch (e: InterruptedException) { From 426af8a5bff17b074b4ae4f829c18067bc789434 Mon Sep 17 00:00:00 2001 From: d4vidi Date: Mon, 18 Dec 2023 17:36:03 +0200 Subject: [PATCH 4/4] Improve code quality --- .../react/NullRNActivityWorkaround.kt | 14 +++++++------- .../java/com/reactnativenavigation/utils/Retry.kt | 9 +++++++++ 2 files changed, 16 insertions(+), 7 deletions(-) create mode 100644 lib/android/app/src/main/java/com/reactnativenavigation/utils/Retry.kt diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/react/NullRNActivityWorkaround.kt b/lib/android/app/src/main/java/com/reactnativenavigation/react/NullRNActivityWorkaround.kt index d1cc4fabba..0534e98426 100644 --- a/lib/android/app/src/main/java/com/reactnativenavigation/react/NullRNActivityWorkaround.kt +++ b/lib/android/app/src/main/java/com/reactnativenavigation/react/NullRNActivityWorkaround.kt @@ -4,7 +4,7 @@ import android.app.Activity import android.util.Log import com.facebook.react.bridge.LifecycleEventListener import com.facebook.react.bridge.ReactApplicationContext -import com.reactnativenavigation.utils.sleepSafe +import com.reactnativenavigation.utils.busyRetry import java.lang.ref.WeakReference private const val ACTIVITY_WAIT_INTERVAL = 100L @@ -43,13 +43,13 @@ class NullRNActivityWorkaround(reactAppContext: ReactApplicationContext) { } private fun waitForActivity() { - val activityReady = { (reactAppContext.get()?.hasCurrentActivity() ?: false) } + val isActivityReady = { (reactAppContext.get()?.hasCurrentActivity() ?: false) } - var tries = 0 - while (tries < ACTIVITY_WAIT_TRIES && hasHost && !activityReady()) { - Log.d(LOG_TAG, "Busy-waiting for activity! Try: #$tries...") - sleepSafe(ACTIVITY_WAIT_INTERVAL) - tries++ + busyRetry(ACTIVITY_WAIT_TRIES, ACTIVITY_WAIT_INTERVAL) { tries -> + if (!isActivityReady() && hasHost) { + Log.d(LOG_TAG, "Busy-waiting for activity! Try: #$tries...") + true + } else false } } } diff --git a/lib/android/app/src/main/java/com/reactnativenavigation/utils/Retry.kt b/lib/android/app/src/main/java/com/reactnativenavigation/utils/Retry.kt new file mode 100644 index 0000000000..7b61e16b62 --- /dev/null +++ b/lib/android/app/src/main/java/com/reactnativenavigation/utils/Retry.kt @@ -0,0 +1,9 @@ +package com.reactnativenavigation.utils + +fun busyRetry(times: Int, interval: Long, callback: (tries: Int) -> Boolean) { + var tries = 0 + while (tries < times && !callback(tries)) { + sleepSafe(interval) + tries++ + } +}