diff --git a/packages/react-native-executorch/android/src/main/java/com/swmansion/rnexecutorch/ETInstallerUnavailable.kt b/packages/react-native-executorch/android/src/main/java/com/swmansion/rnexecutorch/ETInstallerUnavailable.kt new file mode 100644 index 0000000000..0b4c57bb60 --- /dev/null +++ b/packages/react-native-executorch/android/src/main/java/com/swmansion/rnexecutorch/ETInstallerUnavailable.kt @@ -0,0 +1,27 @@ +package com.swmansion.rnexecutorch + +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReactMethod +import com.facebook.react.common.annotations.FrameworkAPI +import com.facebook.react.module.annotations.ReactModule + +/** + * Fallback TurboModule returned when native ExecuTorch libraries cannot be + * loaded (e.g. 32-bit Android devices where only arm64-v8a binaries are + * shipped). Extends the same spec as ETInstaller so JS sees a real linked + * module, but install() returns false to signal unavailability. + */ +@OptIn(FrameworkAPI::class) +@ReactModule(name = ETInstallerUnavailable.NAME) +class ETInstallerUnavailable( + reactContext: ReactApplicationContext, +) : NativeETInstallerSpec(reactContext) { + companion object { + const val NAME = NativeETInstallerSpec.NAME + } + + @ReactMethod(isBlockingSynchronousMethod = true) + override fun install(): Boolean { + return false + } +} diff --git a/packages/react-native-executorch/android/src/main/java/com/swmansion/rnexecutorch/RnExecutorchPackage.kt b/packages/react-native-executorch/android/src/main/java/com/swmansion/rnexecutorch/RnExecutorchPackage.kt index 0b15e216a5..46abca85d7 100644 --- a/packages/react-native-executorch/android/src/main/java/com/swmansion/rnexecutorch/RnExecutorchPackage.kt +++ b/packages/react-native-executorch/android/src/main/java/com/swmansion/rnexecutorch/RnExecutorchPackage.kt @@ -15,7 +15,18 @@ class RnExecutorchPackage : TurboReactPackage() { reactContext: ReactApplicationContext, ): NativeModule? = if (name == ETInstaller.NAME) { - ETInstaller(reactContext) + try { + ETInstaller(reactContext) + } catch (e: RuntimeException) { + if (e.cause is UnsatisfiedLinkError) { + // Native library not available (e.g. 32-bit device without arm64-v8a .so). + // Return a fallback module whose install() returns false so JS can + // distinguish "unsupported ABI" from "package not linked." + ETInstallerUnavailable(reactContext) + } else { + throw e + } + } } else { null } diff --git a/packages/react-native-executorch/src/index.ts b/packages/react-native-executorch/src/index.ts index 3f6b9fc4ce..afd90bc780 100644 --- a/packages/react-native-executorch/src/index.ts +++ b/packages/react-native-executorch/src/index.ts @@ -134,9 +134,20 @@ if ( `Failed to install react-native-executorch: The native module could not be found.` ); } + // install() returns false when the native library is intentionally unavailable + // (e.g. ETInstallerUnavailable on unsupported ABIs). In that case, JSI + // bindings are not injected and globals remain unset. ETInstallerNativeModule.install(); } +/** + * Whether the native ExecuTorch runtime is available on this device. + * Returns `false` when native libraries cannot be loaded (e.g. 32-bit Android + * devices where only arm64-v8a binaries are shipped). + * @category Utilities - General + */ +export const isAvailable = typeof global.loadExecutorchModule === 'function'; + // hooks export * from './hooks/computer_vision/useClassification'; export * from './hooks/computer_vision/useObjectDetection';