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..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 @@ -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"; @@ -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,7 +90,8 @@ public String getName() { @ReactMethod public void getLaunchArgs(String commandId, Promise promise) { - promise.resolve(LaunchArgsParser.parse(activity())); + Activity activity = nullRNActivityWorkaround.getActivity(); + promise.resolve(LaunchArgsParser.parse(activity)); } private WritableMap createNavigationConstantsMap() { @@ -211,6 +215,15 @@ public void dismissAllOverlays(String commandId, Promise promise) { handle(() -> 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() { return activity().getNavigator(); } @@ -232,13 +245,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..0534e98426 --- /dev/null +++ b/lib/android/app/src/main/java/com/reactnativenavigation/react/NullRNActivityWorkaround.kt @@ -0,0 +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.busyRetry +import java.lang.ref.WeakReference + +private const val ACTIVITY_WAIT_INTERVAL = 100L +private const val ACTIVITY_WAIT_TRIES = 150 + +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 isActivityReady = { (reactAppContext.get()?.hasCurrentActivity() ?: false) } + + 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++ + } +} 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..93da616026 --- /dev/null +++ b/lib/android/app/src/main/java/com/reactnativenavigation/utils/Sleep.kt @@ -0,0 +1,9 @@ +package com.reactnativenavigation.utils + +fun sleepSafe(ms: Long) { + try { + Thread.sleep(ms) + } catch (e: InterruptedException) { + e.printStackTrace() + } +}