Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bridgeless Mode support (New Architecture) [0.74] #1769

Open
angelo-hub opened this issue Apr 11, 2024 · 50 comments
Open

Bridgeless Mode support (New Architecture) [0.74] #1769

angelo-hub opened this issue Apr 11, 2024 · 50 comments

Comments

@angelo-hub
Copy link

Bridgeless Mode is going to be default in 0.74 can offer some bandwidth to migrate to bridgeless mode as this is a super cool project

@enahum
Copy link

enahum commented May 20, 2024

Have you been able to make it work even with the bridge on 74? On Android there is no longer getJSIModulePackage I cannot seem to find a solution to this yet.

@enahum
Copy link

enahum commented May 20, 2024

@radex do you think you can take a look at this to solve the breaking change introduced by RN 74 ?

@karengharibyan
Copy link

@hawadlu
Copy link

hawadlu commented May 31, 2024

@angelo-hub Any progress on this one?

@yuyongmao
Copy link

Have you been able to make it work even with the bridge on 74? On Android there is no longer getJSIModulePackage I cannot seem to find a solution to this yet.

@enahum Is there any progress on this issue? I have the same problem.

@suman379
Copy link

@radex any plan for new arch?

@LukasMod
Copy link

@radex Can we get an answer in this thread?
Are there any plans to restore the compatibility of the library with the latest version of react native (current 0.74.3)?

@radex
Copy link
Collaborator

radex commented Jul 24, 2024

Yes, sorry for staying quiet about it. Yes, I'm planning to work on this likely in August.

@3210jr
Copy link

3210jr commented Aug 9, 2024

Hey @radex - I am happy to help with chores and low hanging fruit re new architecture support. Not my area of expertise but if there is a pull request I will contribute

@lucaswitch
Copy link

Hey @radex - I am happy to help with chores and low hanging fruit re new architecture support. Not my area of expertise but if there is a pull request I will contribute

I'm happy to help too

@juliesaia-vendora
Copy link

I got it working on 0.74.5 (with bridge) with this patch, it reverts the deprecation and removes BridgelessCatalystInstance.kt.

It might be possible to do the same with 0.75, but it will be more difficult because there they actually removed the JSIModule stuff. I might try it later though

Be sure to enable building from source on Android (android/settings.gradle):

includeBuild('../node_modules/react-native') {
    dependencySubstitution {
        substitute(module("com.facebook.react:react-android")).using(project(":packages:react-native:ReactAndroid"))
        substitute(module("com.facebook.react:react-native")).using(project(":packages:react-native:ReactAndroid"))
        substitute(module("com.facebook.react:hermes-android")).using(project(":packages:react-native:ReactAndroid:hermes-engine"))
        substitute(module("com.facebook.react:hermes-engine")).using(project(":packages:react-native:ReactAndroid:hermes-engine"))
    }
}

The patch:

diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java
index 7258ab9..6df97c6 100644
--- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java
+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManager.java
@@ -55,6 +55,8 @@ import com.facebook.react.bridge.CatalystInstance;
 import com.facebook.react.bridge.CatalystInstanceImpl;
 import com.facebook.react.bridge.JSBundleLoader;
 import com.facebook.react.bridge.JSExceptionHandler;
+import com.facebook.react.bridge.JSIModulePackage;
+import com.facebook.react.bridge.JSIModuleType;
 import com.facebook.react.bridge.JavaJSExecutor;
 import com.facebook.react.bridge.JavaScriptExecutor;
 import com.facebook.react.bridge.JavaScriptExecutorFactory;
@@ -184,6 +186,7 @@ public class ReactInstanceManager {
   private volatile Boolean mHasStartedDestroying = false;
   private final MemoryPressureRouter mMemoryPressureRouter;
   private final @Nullable JSExceptionHandler mJSExceptionHandler;
+    private final @Nullable JSIModulePackage mJSIModulePackage;
   private final @Nullable UIManagerProvider mUIManagerProvider;
   private final @Nullable ReactPackageTurboModuleManagerDelegate.Builder mTMMDelegateBuilder;
   private List<ViewManager> mViewManagers;
@@ -232,6 +235,7 @@ public class ReactInstanceManager {
       @Nullable DevBundleDownloadListener devBundleDownloadListener,
       int minNumShakes,
       int minTimeLeftInFrameForNonBatchedOperationMs,
+      @Nullable JSIModulePackage jsiModulePackage,
       @Nullable UIManagerProvider uIManagerProvider,
       @Nullable Map<String, RequestHandler> customPackagerCommandHandlers,
       @Nullable ReactPackageTurboModuleManagerDelegate.Builder tmmDelegateBuilder,
@@ -292,6 +296,7 @@ public class ReactInstanceManager {
       }
       mPackages.addAll(packages);
     }
+    mJSIModulePackage = jsiModulePackage;
     mUIManagerProvider = uIManagerProvider;
 
     // Instantiate ReactChoreographer in UI thread.
@@ -1399,15 +1404,20 @@ public class ReactInstanceManager {
       }
     }
 
+    if (mJSIModulePackage != null) {
+      catalystInstance.addJSIModules(
+          mJSIModulePackage.getJSIModules(
+              reactContext, catalystInstance.getJavaScriptContextHolder()));
+    }
+
     if (mUIManagerProvider != null) {
       UIManager uiManager = mUIManagerProvider.createUIManager(reactContext);
       if (uiManager != null) {
-        catalystInstance.setFabricUIManager(uiManager);
-
-        // Initialize the UIManager
         uiManager.initialize();
         catalystInstance.setFabricUIManager(uiManager);
       }
+    } else if (ReactFeatureFlags.enableFabricRenderer) {
+    catalystInstance.getJSIModule(JSIModuleType.UIManager);
     }
     if (mBridgeIdleDebugListener != null) {
       catalystInstance.addBridgeIdleDebugListener(mBridgeIdleDebugListener);
diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerBuilder.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerBuilder.java
index d467ab9..3ae051f 100644
--- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerBuilder.java
+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactInstanceManagerBuilder.java
@@ -20,6 +20,7 @@ import com.facebook.hermes.reactexecutor.HermesExecutorFactory;
 import com.facebook.infer.annotation.Assertions;
 import com.facebook.react.bridge.JSBundleLoader;
 import com.facebook.react.bridge.JSExceptionHandler;
+import com.facebook.react.bridge.JSIModulePackage;
 import com.facebook.react.bridge.JavaScriptExecutorFactory;
 import com.facebook.react.bridge.NotThreadSafeBridgeIdleDebugListener;
 import com.facebook.react.bridge.UIManagerProvider;
@@ -67,6 +68,7 @@ public class ReactInstanceManagerBuilder {
   private @Nullable JavaScriptExecutorFactory mJavaScriptExecutorFactory;
   private int mMinNumShakes = 1;
   private int mMinTimeLeftInFrameForNonBatchedOperationMs = -1;
+  private @Nullable JSIModulePackage mJSIModulesPackage;
   private @Nullable UIManagerProvider mUIManagerProvider;
   private @Nullable Map<String, RequestHandler> mCustomPackagerCommandHandlers;
   private @Nullable ReactPackageTurboModuleManagerDelegate.Builder mTMMDelegateBuilder;
@@ -77,6 +79,12 @@ public class ReactInstanceManagerBuilder {
 
   /* package protected */ ReactInstanceManagerBuilder() {}
 
+  public ReactInstanceManagerBuilder setJSIModulesPackage(
+      @Nullable JSIModulePackage jsiModulePackage) {
+    mJSIModulesPackage = jsiModulePackage;
+    return this;
+  }
+
   /** Factory for desired implementation of JavaScriptExecutor. */
   public ReactInstanceManagerBuilder setJavaScriptExecutorFactory(
       @Nullable JavaScriptExecutorFactory javaScriptExecutorFactory) {
@@ -353,6 +361,7 @@ public class ReactInstanceManagerBuilder {
         mDevBundleDownloadListener,
         mMinNumShakes,
         mMinTimeLeftInFrameForNonBatchedOperationMs,
+        mJSIModulesPackage,
         mUIManagerProvider,
         mCustomPackagerCommandHandlers,
         mTMMDelegateBuilder,
diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactNativeHost.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactNativeHost.java
index 6551937..3f1fb6f 100644
--- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactNativeHost.java
+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/ReactNativeHost.java
@@ -10,6 +10,7 @@ package com.facebook.react;
 import android.app.Application;
 import androidx.annotation.Nullable;
 import com.facebook.infer.annotation.Assertions;
+import com.facebook.react.bridge.JSIModulePackage;
 import com.facebook.react.bridge.JavaScriptExecutorFactory;
 import com.facebook.react.bridge.ReactMarker;
 import com.facebook.react.bridge.ReactMarkerConstants;
@@ -84,6 +85,7 @@ public abstract class ReactNativeHost {
             .setLazyViewManagersEnabled(getLazyViewManagersEnabled())
             .setRedBoxHandler(getRedBoxHandler())
             .setJavaScriptExecutorFactory(getJavaScriptExecutorFactory())
+            .setJSIModulesPackage(getJSIModulePackage())
             .setUIManagerProvider(getUIManagerProvider())
             .setInitialLifecycleState(LifecycleState.BEFORE_CREATE)
             .setReactPackageTurboModuleManagerDelegateBuilder(
@@ -125,6 +127,10 @@ public abstract class ReactNativeHost {
     return mApplication;
   }
 
+  protected @Nullable JSIModulePackage getJSIModulePackage() {
+    return null;
+  }
+
   protected @Nullable UIManagerProvider getUIManagerProvider() {
     return reactApplicationContext -> null;
   }
diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java
index b2e2d7c..0ee0dd6 100644
--- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java
+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstance.java
@@ -120,7 +120,7 @@ public interface CatalystInstance
   RuntimeScheduler getRuntimeScheduler();
 
   @Deprecated
-  <T extends JSIModule> void addJSIModules(List<JSIModuleSpec<T>> jsiModules);
+  void addJSIModules(List<JSIModuleSpec> jsiModules);
 
   /**
    * Returns a hybrid object that contains a pointer to a JS CallInvoker, which is used to schedule
diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java
index f86dffb..67e5e35 100644
--- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java
+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/CatalystInstanceImpl.java
@@ -537,8 +537,7 @@ public class CatalystInstanceImpl implements CatalystInstance {
   public native RuntimeScheduler getRuntimeScheduler();
 
   @Override
-  @Deprecated
-  public <T extends JSIModule> void addJSIModules(List<JSIModuleSpec<T>> jsiModules) {
+  public void addJSIModules(List<JSIModuleSpec> jsiModules) {
     mJSIModuleRegistry.registerModules(jsiModules);
   }
   
diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/JSIModuleRegistry.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/JSIModuleRegistry.java
index 90d5cde..e81b361 100644
--- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/JSIModuleRegistry.java
+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/bridge/JSIModuleRegistry.java
@@ -26,8 +26,8 @@ class JSIModuleRegistry {
     return Assertions.assertNotNull(jsiModuleHolder.getJSIModule());
   }
 
-  public <T extends JSIModule> void registerModules(List<JSIModuleSpec<T>> jsiModules) {
-    for (JSIModuleSpec<T> spec : jsiModules) {
+  public void registerModules(List<JSIModuleSpec> jsiModules) {
+    for (JSIModuleSpec spec : jsiModules) {
       mModules.put(spec.getJSIModuleType(), new JSIModuleHolder(spec));
     }
   }
diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/BridgelessCatalystInstance.kt b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/BridgelessCatalystInstance.kt
deleted file mode 100644
index 03a18c6..0000000
--- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/BridgelessCatalystInstance.kt
+++ /dev/null
@@ -1,194 +0,0 @@
-/*
- * Copyright (c) Meta Platforms, Inc. and affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- */
-
-package com.facebook.react.runtime
-
-import android.content.res.AssetManager
-import com.facebook.proguard.annotations.DoNotStrip
-import com.facebook.react.bridge.CatalystInstance
-import com.facebook.react.bridge.JSIModule
-import com.facebook.react.bridge.JSIModuleSpec
-import com.facebook.react.bridge.JSIModuleType
-import com.facebook.react.bridge.JavaScriptContextHolder
-import com.facebook.react.bridge.JavaScriptModule
-import com.facebook.react.bridge.NativeArray
-import com.facebook.react.bridge.NativeArrayInterface
-import com.facebook.react.bridge.NativeModule
-import com.facebook.react.bridge.NativeModuleRegistry
-import com.facebook.react.bridge.NotThreadSafeBridgeIdleDebugListener
-import com.facebook.react.bridge.RuntimeExecutor
-import com.facebook.react.bridge.RuntimeScheduler
-import com.facebook.react.bridge.UIManager
-import com.facebook.react.bridge.queue.ReactQueueConfiguration
-import com.facebook.react.common.annotations.DeprecatedInNewArchitecture
-import com.facebook.react.common.annotations.VisibleForTesting
-import com.facebook.react.internal.turbomodule.core.interfaces.TurboModuleRegistry
-import com.facebook.react.turbomodule.core.interfaces.CallInvokerHolder
-import com.facebook.react.turbomodule.core.interfaces.NativeMethodCallInvokerHolder
-
-@DoNotStrip
-@DeprecatedInNewArchitecture
-public class BridgelessCatalystInstance(private val reactHost: ReactHostImpl) : CatalystInstance {
-
-  override fun handleMemoryPressure(level: Int) {
-    throw UnsupportedOperationException("Unimplemented method 'handleMemoryPressure'")
-  }
-
-  override fun loadScriptFromAssets(
-      assetManager: AssetManager,
-      assetURL: String,
-      loadSynchronously: Boolean
-  ) {
-    throw UnsupportedOperationException("Unimplemented method 'loadScriptFromAssets'")
-  }
-
-  override fun loadScriptFromFile(fileName: String, sourceURL: String, loadSynchronously: Boolean) {
-    throw UnsupportedOperationException("Unimplemented method 'loadScriptFromFile'")
-  }
-
-  override fun loadSplitBundleFromFile(fileName: String, sourceURL: String) {
-    throw UnsupportedOperationException("Unimplemented method 'loadSplitBundleFromFile'")
-  }
-
-  override fun setSourceURLs(deviceURL: String, remoteURL: String) {
-    throw UnsupportedOperationException("Unimplemented method 'setSourceURLs'")
-  }
-
-  override fun runJSBundle() {
-    throw UnsupportedOperationException("Unimplemented method 'runJSBundle'")
-  }
-
-  override fun hasRunJSBundle(): Boolean {
-    throw UnsupportedOperationException("Unimplemented method 'hasRunJSBundle'")
-  }
-
-  override fun getSourceURL(): String? {
-    throw UnsupportedOperationException("Unimplemented method 'getSourceURL'")
-  }
-
-  @DoNotStrip
-  override fun invokeCallback(callbackID: Int, arguments: NativeArrayInterface) {
-    throw UnsupportedOperationException("Unimplemented method 'invokeCallback'")
-  }
-
-  override fun callFunction(module: String, method: String, arguments: NativeArray) {
-    throw UnsupportedOperationException("Unimplemented method 'callFunction'")
-  }
-
-  override fun destroy() {
-    throw UnsupportedOperationException("Unimplemented method 'destroy'")
-  }
-
-  override fun isDestroyed(): Boolean {
-    throw UnsupportedOperationException("Unimplemented method 'isDestroyed'")
-  }
-
-  @VisibleForTesting
-  override fun initialize() {
-    throw UnsupportedOperationException("Unimplemented method 'initialize'")
-  }
-
-  override fun getReactQueueConfiguration(): ReactQueueConfiguration =
-      reactHost.reactQueueConfiguration!!
-
-  override fun <T : JavaScriptModule> getJSModule(jsInterface: Class<T>): T =
-      reactHost.currentReactContext?.getJSModule(jsInterface)!!
-
-  override fun <T : NativeModule> hasNativeModule(nativeModuleInterface: Class<T>): Boolean =
-      reactHost.hasNativeModule(nativeModuleInterface)
-
-  override fun <T : NativeModule> getNativeModule(nativeModuleInterface: Class<T>): T? =
-      reactHost.getNativeModule(nativeModuleInterface)
-
-  override fun getNativeModule(moduleName: String): NativeModule? =
-      reactHost.getNativeModule(moduleName)
-
-  @Deprecated(
-      message =
-          "getJSIModule(JSIModuleType moduleType) is deprecated and will be deleted in the future. Please use ReactInstanceEventListener to subscribe for react instance events instead.")
-  override fun getJSIModule(moduleType: JSIModuleType): JSIModule {
-    throw UnsupportedOperationException("Unimplemented method 'getJSIModule'")
-  }
-
-  override fun getNativeModules(): Collection<NativeModule> = reactHost.getNativeModules()
-
-  override fun extendNativeModules(modules: NativeModuleRegistry) {
-    throw UnsupportedOperationException("Unimplemented method 'extendNativeModules'")
-  }
-
-  override fun addBridgeIdleDebugListener(listener: NotThreadSafeBridgeIdleDebugListener) {
-    throw UnsupportedOperationException("Unimplemented method 'addBridgeIdleDebugListener'")
-  }
-
-  override fun removeBridgeIdleDebugListener(listener: NotThreadSafeBridgeIdleDebugListener) {
-    throw UnsupportedOperationException("Unimplemented method 'removeBridgeIdleDebugListener'")
-  }
-
-  override fun registerSegment(segmentId: Int, path: String) {
-    throw UnsupportedOperationException("Unimplemented method 'registerSegment'")
-  }
-
-  @VisibleForTesting
-  override fun setGlobalVariable(propName: String, jsonValue: String) {
-    throw UnsupportedOperationException("Unimplemented method 'setGlobalVariable'")
-  }
-
-  @Deprecated(message = "This API is unsupported in the New Architecture.")
-  override fun getJavaScriptContextHolder(): JavaScriptContextHolder? {
-    return reactHost.getJavaScriptContextHolder()
-  }
-
-  override fun getRuntimeExecutor(): RuntimeExecutor? {
-    return reactHost.getRuntimeExecutor()
-  }
-
-  override fun getRuntimeScheduler(): RuntimeScheduler {
-    throw UnsupportedOperationException("Unimplemented method 'getRuntimeScheduler'")
-  }
-
-  @Deprecated(message = "This API is unsupported in the New Architecture.")
-  override fun <T : JSIModule> addJSIModules(jsiModules: List<JSIModuleSpec<T>>) {
-    throw UnsupportedOperationException("Unimplemented method 'addJSIModules'")
-  }
-
-  override fun getJSCallInvokerHolder(): CallInvokerHolder? {
-    return reactHost.getJSCallInvokerHolder()
-  }
-
-  override fun getNativeMethodCallInvokerHolder(): NativeMethodCallInvokerHolder {
-    throw UnsupportedOperationException("Unimplemented method 'getNativeMethodCallInvokerHolder'")
-  }
-
-  @Deprecated(
-      message =
-          "setTurboModuleManager(JSIModule getter) is deprecated and will be deleted in the future. Please use setTurboModuleRegistry(TurboModuleRegistry turboModuleRegistry) instead.",
-      replaceWith = ReplaceWith("setTurboModuleRegistry(turboModuleRegistry)"))
-  override fun setTurboModuleManager(getter: JSIModule) {
-    throw UnsupportedOperationException("Unimplemented method 'setTurboModuleManager'")
-  }
-
-  @DeprecatedInNewArchitecture(
-      message =
-          "This method will be deprecated later as part of Stable APIs with bridge removal and not encouraged usage.")
-  override fun setTurboModuleRegistry(turboModuleRegistry: TurboModuleRegistry) {
-    throw UnsupportedOperationException("Unimplemented method 'setTurboModuleRegistry'")
-  }
-
-  @DeprecatedInNewArchitecture(
-      message =
-          "This method will be deprecated later as part of Stable APIs with bridge removal and not encouraged usage.")
-  override fun setFabricUIManager(fabricUIManager: UIManager) {
-    throw UnsupportedOperationException("Unimplemented method 'setFabricUIManager'")
-  }
-
-  @DeprecatedInNewArchitecture(
-      message =
-          "This method will be deprecated later as part of Stable APIs with bridge removal and not encouraged usage.")
-  override fun getFabricUIManager(): UIManager {
-    throw UnsupportedOperationException("Unimplemented method 'getFabricUIManager'")
-  }
-}
diff --git a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/BridgelessReactContext.java b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/BridgelessReactContext.java
index d1cdaa9..24cc18a 100644
--- a/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/BridgelessReactContext.java
+++ b/node_modules/react-native/ReactAndroid/src/main/java/com/facebook/react/runtime/BridgelessReactContext.java
@@ -81,14 +81,6 @@ class BridgelessReactContext extends ReactApplicationContext implements EventDis
     return mReactHost.getUIManager();
   }
 
-  @Override
-  public CatalystInstance getCatalystInstance() {
-    Log.w(
-        TAG,
-        "[WARNING] Bridgeless doesn't support CatalystInstance. Accessing an API that's not part of the new architecture is not encouraged usage.");
-    return new BridgelessCatalystInstance(mReactHost);
-  }
-
   @Override
   public boolean hasActiveReactInstance() {
     return mReactHost.isInstanceInitialized();

Really frustrating that the RN changelog mentions that this was unused in open source, idk how they missed this huge repo

@LukasMod
Copy link

@radex Could you update how you see the progress of this work?

We're trying to get the app ready for the new architecture as of version 0.76 and are looking for issues among the key libraries. Without watermelonDB it probably won't be possible, so any information about the progress/plans would be super valuable. Maybe someone advanced from the community would be able to help solve some problems.

@swapnil001
Copy link

Hi, Any update on solution for this?

@prathameshmm02
Copy link
Contributor

WatermelonDB JSI module can be registered like this in React Native 0.74+:

MainApplication.kt

import others....
import com.facebook.react.ReactInstanceManager
import com.nozbe.watermelondb.jsi.JSIInstaller
import com.facebook.react.bridge.ReactContext
import com.facebook.react.bridge.UiThreadUtil

class MainApplication : Application(), ReactApplication {
....
    private var listenerAdded = false
    override fun onCreate() {
        super.onCreate()
        ....
        registerJSIModules()
    }

    private fun runOnJSQueueThread(action: () -> Unit) {
        reactNativeHost.reactInstanceManager.currentReactContext?.runOnJSQueueThread {
            action()
        } ?: UiThreadUtil.runOnUiThread {
            reactNativeHost.reactInstanceManager.currentReactContext?.runOnJSQueueThread {
                action()
            }
        }
    }

    @Suppress("DEPRECATION")
    private fun registerJSIModules() {
        val reactInstanceManager = reactNativeHost.reactInstanceManager

        if (!listenerAdded) {
            listenerAdded = true
            reactInstanceManager.addReactInstanceEventListener(object : ReactInstanceManager.ReactInstanceEventListener {
                override fun onReactContextInitialized(context: ReactContext) {
                    runOnJSQueueThread {
                        registerWatermelonJSI(context)
                    }
                }
            })
        }
    }

    private fun registerWatermelonJSI(context: ReactContext) {
        val holder = context.javaScriptContextHolder?.get()
        if (holder != null) {
            JSIInstaller.install(context, holder)
        }
    }
}

A patch is also required to make JSIInstaller class and methods public.

diff --git a/native/android-jsi/src/main/java/com/nozbe/watermelondb/jsi/JSIInstaller.java b/native/android-jsi/src/main/java/com/nozbe/watermelondb/jsi/JSIInstaller.java
index 055cede2f20cd6a75ffb79d156e35396c1438c91..fb7ca33847aa9ad1349b10b35d4e27575e843479 100755
--- a/native/android-jsi/src/main/java/com/nozbe/watermelondb/jsi/JSIInstaller.java
+++ b/native/android-jsi/src/main/java/com/nozbe/watermelondb/jsi/JSIInstaller.java
@@ -1,8 +1,8 @@
 package com.nozbe.watermelondb.jsi;
 
 import android.content.Context;
-class JSIInstaller {
-    static void install(Context context, long javaScriptContextHolder) {
+public class JSIInstaller {
+    public static void install(Context context, long javaScriptContextHolder) {
         JSIInstaller.context = context;
         new JSIInstaller().installBinding(javaScriptContextHolder);
 

Reference: https://github.com/mattermost/mattermost-mobile/blob/main/android/app/src/main/java/com/mattermost/rnbeta/MainApplication.kt

Didn't get the time to test this, but probably should work?

@enahum
Copy link

enahum commented Sep 27, 2024

The above works, but I have not tested it with 0.75.x

@ishan-chhabra
Copy link

Hi @prathameshmm02 @enahum!

I did try the workaround above but it did not work for me.
I still get this error while syncing in turbo.

Error while syncing [Error: provideSyncJson unavailable. Use JSI mode to enable.]

MainApplication.kt

package something.something

import android.app.Application
import android.content.res.Configuration
import com.facebook.react.PackageList
import com.facebook.react.ReactApplication
import com.facebook.react.ReactHost
import com.facebook.react.ReactNativeHost
import com.facebook.react.ReactPackage
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load
import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
import com.facebook.react.defaults.DefaultReactNativeHost
import com.facebook.soloader.SoLoader
import java.util.Arrays
import com.facebook.react.bridge.JSIModulePackage
import com.facebook.react.bridge.JSIModule
import com.facebook.react.bridge.JSIModuleSpec
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.JavaScriptContextHolder
import expo.modules.ApplicationLifecycleDispatcher
import expo.modules.ReactNativeHostWrapper
import com.nozbe.watermelondb.jsi.WatermelonDBJSIPackage
import com.facebook.react.ReactInstanceManager
import com.nozbe.watermelondb.jsi.JSIInstaller
import com.facebook.react.bridge.ReactContext
import com.facebook.react.bridge.UiThreadUtil

class MainApplication : Application(), ReactApplication {

  private var listenerAdded = false
  override val reactNativeHost: ReactNativeHost =
      ReactNativeHostWrapper(this, object : DefaultReactNativeHost(this) {
        override fun getPackages(): List<ReactPackage> =
            PackageList(this).packages.apply {
              // Packages that cannot be autolinked yet can be added manually here, for example:
              // add(MyReactNativePackage())
            }

        override fun getJSMainModuleName(): String = "index"

        override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG

        override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
        override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED
      })

  override val reactHost: ReactHost
    get() = ReactNativeHostWrapper.createReactHost(applicationContext, reactNativeHost)

    override fun onCreate() {
    super.onCreate()
    
    SoLoader.init(this, false)
    if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
      // If you opted-in for the New Architecture, we load the native entry point for this app.
      load()
    }
    ApplicationLifecycleDispatcher.onApplicationCreate(this)
    registerJSIModules()
  }

    private fun runOnJSQueueThread(action: () -> Unit) {
        reactNativeHost.reactInstanceManager.currentReactContext?.runOnJSQueueThread {
            action()
        } ?: UiThreadUtil.runOnUiThread {
            reactNativeHost.reactInstanceManager.currentReactContext?.runOnJSQueueThread {
                action()
            }
        }
    }

  @Suppress("DEPRECATION")
  private fun registerJSIModules() {
    val reactInstanceManager = reactNativeHost.reactInstanceManager

    if (!listenerAdded) {
        listenerAdded = true
        reactInstanceManager.addReactInstanceEventListener(object : ReactInstanceManager.ReactInstanceEventListener {
          override fun onReactContextInitialized(context: ReactContext) {
              runOnJSQueueThread {
                registerWatermelonJSI(context)
              }
            }
          })
        }
    }

  private fun registerWatermelonJSI(context: ReactContext) {
      val holder = context.javaScriptContextHolder?.get()
      if (holder != null) {
          JSIInstaller.install(context, holder)
      }
  }

  override fun onConfigurationChanged(newConfig: Configuration) {
    super.onConfigurationChanged(newConfig)
    ApplicationLifecycleDispatcher.onConfigurationChanged(this, newConfig)
  }
}

I also patched WatermelonDB as follows:

@nozbe+watermelondb+0.27.1.patch

diff --git a/node_modules/@nozbe/watermelondb/native/android-jsi/src/main/java/com/nozbe/watermelondb/jsi/JSIInstaller.java b/node_modules/@nozbe/watermelondb/native/android-jsi/src/main/java/com/nozbe/watermelondb/jsi/JSIInstaller.java
index 055cede..fb7ca33 100755
--- a/node_modules/@nozbe/watermelondb/native/android-jsi/src/main/java/com/nozbe/watermelondb/jsi/JSIInstaller.java
+++ b/node_modules/@nozbe/watermelondb/native/android-jsi/src/main/java/com/nozbe/watermelondb/jsi/JSIInstaller.java
@@ -1,8 +1,8 @@
 package com.nozbe.watermelondb.jsi;
 
 import android.content.Context;
-class JSIInstaller {
-    static void install(Context context, long javaScriptContextHolder) {
+public class JSIInstaller {
+    public static void install(Context context, long javaScriptContextHolder) {
         JSIInstaller.context = context;
         new JSIInstaller().installBinding(javaScriptContextHolder);

I have also verified that all the changes stated in JSI setup on https://watermelondb.dev/docs/Installation#android-react-native are applied.

Any help will be greatly appreciated. Thank youu! :)

@prathameshmm02
Copy link
Contributor

prathameshmm02 commented Sep 29, 2024

@ishan-chhabra Do you have JSI enabled in your adapter?

const adapter = new SQLiteAdapter({
  schema,
  migrations,
  jsi: true,
})

@arun-saleth
Copy link

arun-saleth commented Oct 21, 2024

@prathameshmm02
i followed step by step.But after that i faced this issue - in android

JSI SQLiteAdapter not available… falling back to asynchronous operation. This will happen if you're using remote debugger, and may happen if you forgot to recompile native app after WatermelonDB update

@prathameshmm02
Copy link
Contributor

Have you followed JSI installation docs in Android section given here? After doing that, recompile your app and it should work fine.

@arun-saleth
Copy link

How to implement this.it show override nothing error

`// ...
import com.nozbe.watermelondb.jsi.WatermelonDBJSIPackage; // ⬅️ This!
import com.facebook.react.bridge.JSIModulePackage; // ⬅️ This!
// ...
private final ReactNativeHost mReactNativeHost =
new ReactNativeHost(this) {
// ...

 @Override
 protected JSIModulePackage getJSIModulePackage() {
   return new WatermelonDBJSIPackage(); // ⬅️ This!
 }

}
`

@aleksey-mukho
Copy link

Looks like this library is no longer supported. Very sad news.

@angelo-hub
Copy link
Author

yeah if you want to continue using SQL on react-native recommend the expo/sqlite next gen library

@elsa17z
Copy link

elsa17z commented Nov 19, 2024

Has anyone thought about migrating to a new library? Currently eyeing https://github.com/OP-Engineering/op-sqlite however not sure how stable it is and also it will be a pain to have to do migration...

@angelo-hub
Copy link
Author

angelo-hub commented Nov 19, 2024 via email

@HieronymusLex
Copy link

Has anyone thought about migrating to a new library? Currently eyeing https://github.com/OP-Engineering/op-sqlite however not sure how stable it is and also it will be a pain to have to do migration...

Have you had any thoughts on a strategy for migrating to something like expo-sqlite could be done for existing apps?

@elsa17z
Copy link

elsa17z commented Nov 24, 2024

Has anyone thought about migrating to a new library? Currently eyeing https://github.com/OP-Engineering/op-sqlite however not sure how stable it is and also it will be a pain to have to do migration...

Have you had any thoughts on a strategy for migrating to something like expo-sqlite could be done for existing apps?

I would very much rather not migrate. It is always a risk, but if I am forced to do it I would suggest migrating it by initially migrating only 1 table with a fallback value provider function. Kind of like maintaining a replica and you should write to both databases, then once you are sure it works switch to it completely.

@HieronymusLex
Copy link

I would very much rather not migrate. It is always a risk, but if I am forced to do it I would suggest migrating it by initially migrating only 1 table with a fallback value provider function. Kind of like maintaining a replica and you should write to both databases, then once you are sure it works switch to it completely.

Agreed I would rather not but fear it might be required. In any case, moving that discussion to #1861

@radex
Copy link
Collaborator

radex commented Nov 29, 2024

Hi everyone,

Apologies for the long time with no updates. Unfortunately, I have not been able to find enough time recently to work on WatermelonDB. I'm very sorry about promising/suggesting that this would be done in August given that I was not able to deliver on that promise.

I have a new pre-release version: v0.28.0-1 - that should fix building of the project on Android/JSI. Note that you need to move WatermelonDBJSIPackage() to getPackages() in your MainApplication.kt.

Feedback on the pre-release would be appreciated!

@LukasMod
Copy link

LukasMod commented Dec 2, 2024

I can confirm v0.28.0-1 is working in our product with react-native: 0.76.3 old arch with db jsi enabled. At first glance, it looks like it's working properly 👍

@CodeWithBlaze
Copy link

CodeWithBlaze commented Dec 2, 2024

will this also work on the 0.76+ new arch and jsi enabled ? because i am getting strange errors like this
Caused by: java.lang.NullPointerException at com.facebook.react.runtime.BridgelessCatalystInstance.getReactQueueConfiguration(BridgelessCatalystInstance.kt:116) at com.nozbe.watermelondb.WMDatabaseBridge.invalidate(WMDatabaseBridge.java:251) at com.facebook.react.internal.turbomodule.core.TurboModuleManager.invalidate(TurboModuleManager.java:417)

when i reload the app

@radex can you confirm this please. Using pre-release version 0.28.1

@MatheusHCP
Copy link

In Expo SDK 52, React Native 0.76, New Arch Enable and JSI Disable on Android, i receiving this message in android:

Diagnostic error: NativeModules.WMDatabaseBridge is not defined! This means that you haven't properly linked WatermelonDB native module. Refer to docs for instructions about installation (and the changelog if this happened after an upgrade).

I implemented manual link described in Installation.MDX, I try to update my application to the new arch.

@caiopratali
Copy link

caiopratali commented Dec 3, 2024

In Expo SDK 52, React Native 0.76, New Arch Enable and JSI Disable on Android, i receiving this message in android:

Diagnostic error: NativeModules.WMDatabaseBridge is not defined! This means that you haven't properly linked WatermelonDB native module. Refer to docs for instructions about installation (and the changelog if this happened after an upgrade).

I implemented manual link described in Installation.MDX, I try to update my application to the new arch.

Did it work with the manual link?

image

@MatheusHCP
Copy link

In Expo SDK 52, React Native 0.76, New Arch Enable and JSI Disable on Android, i receiving this message in android:
Diagnostic error: NativeModules.WMDatabaseBridge is not defined! This means that you haven't properly linked WatermelonDB native module. Refer to docs for instructions about installation (and the changelog if this happened after an upgrade).
I implemented manual link described in Installation.MDX, I try to update my application to the new arch.

Did it work with the manual link?

image

No.

@caiopratali
Copy link

In Expo SDK 52, React Native 0.76, New Arch Enable and JSI Disable on Android, i receiving this message in android:
Diagnostic error: NativeModules.WMDatabaseBridge is not defined! This means that you haven't properly linked WatermelonDB native module. Refer to docs for instructions about installation (and the changelog if this happened after an upgrade).
I implemented manual link described in Installation.MDX, I try to update my application to the new arch.

Did it work with the manual link?
image

No.

I had managed to use it without newArch, for the link on Android I installed @react-native-community/cli and added EXPO_USE_COMMUNITY_AUTOLINKING=1 as suggested in the expo documentation, but my app randomly stopped working.

Expo Autolinking: https://docs.expo.dev/modules/autolinking/#opting-out-of-expo-autolinking

@juliesaia-vendora
Copy link

anyone gotten this to link properly on android yet? still getting Proprety 'WMDatabaseJSIBridge' doesn't exist with this MainApplication.kt:

        override fun getPackages(): List<ReactPackage> =
            PackageList(this).packages.apply {
              // Packages that cannot be autolinked yet can be added manually here, for example:
              // add(MyReactNativePackage());
              add(WatermelonDBJSIPackage())
            }

@saurabhspinny
Copy link

will this also work on the 0.76+ new arch and jsi enabled ? because i am getting strange errors like this Caused by: java.lang.NullPointerException at com.facebook.react.runtime.BridgelessCatalystInstance.getReactQueueConfiguration(BridgelessCatalystInstance.kt:116) at com.nozbe.watermelondb.WMDatabaseBridge.invalidate(WMDatabaseBridge.java:251) at com.facebook.react.internal.turbomodule.core.TurboModuleManager.invalidate(TurboModuleManager.java:417)

when i reload the app

@radex can you confirm this please. Using pre-release version 0.28.1

@radex I am also getting the same error. Could you please provide a fix for this?

@KrisLau
Copy link
Contributor

KrisLau commented Jan 5, 2025

@radex any rough idea on when the new arch will be supported? What is left to be done that maybe anyone here could help with?

@clmoreno
Copy link

clmoreno commented Jan 9, 2025

Is anyone having issues with the pre-release? i have been using it on a group of beta testers for a week now and is working fine.

Setup:
expo 51
RN 75

@saurabhsaneja
Copy link

@clmoreno having issue with react Native cli

@limbo56
Copy link
Contributor

limbo56 commented Jan 13, 2025

@CodeWithBlaze
@saurabhspinny

Fix for Bridgeless Architecture Error on App Reload

If you're running into the following error with the new architecture when reloading your app:

Caused by: java.lang.NullPointerException 
  at com.facebook.react.runtime.BridgelessCatalystInstance.getReactQueueConfiguration(BridgelessCatalystInstance.kt:116)
  at com.nozbe.watermelondb.WMDatabaseBridge.invalidate(WMDatabaseBridge.java:251) 
  at com.facebook.react.internal.turbomodule.core.TurboModuleManager.invalidate(TurboModuleManager.java:417)

This issue arises because the new Bridgeless architecture does not support CatalystInstance, as mentioned in the React Native source code.

Solution

The fix involves replacing getCatalystInstance().getReactQueueConfiguration().getJSQueueThread().runOnQueue() with runOnJSQueueThread() to maintain compatibility with the Bridgeless architecture:

diff --git a/native/android/src/main/java/com/nozbe/watermelondb/WMDatabaseBridge.java b/native/android/src/main/java/com/nozbe/watermelondb/WMDatabaseBridge.java
index 117b2bc1f75217c685bc9cfa76b4e55b22e015dc..a678d5d1715d633c4518a04b11fd1198c53157e9 100644
--- a/native/android/src/main/java/com/nozbe/watermelondb/WMDatabaseBridge.java
+++ b/native/android/src/main/java/com/nozbe/watermelondb/WMDatabaseBridge.java
@@ -248,7 +248,7 @@ public class WMDatabaseBridge extends ReactContextBaseJavaModule {
     public void invalidate() {
         // NOTE: See Database::install() for explanation
         super.invalidate();
-        reactContext.getCatalystInstance().getReactQueueConfiguration().getJSQueueThread().runOnQueue(() -> {
+        reactContext.runOnJSQueueThread(() -> {
             try {
                 Class<?> clazz = Class.forName("com.nozbe.watermelondb.jsi.WatermelonJSI");
                 Method method = clazz.getDeclaredMethod("onCatalystInstanceDestroy");

I’ve submitted a pull request with this fix and would appreciate it being merged. Hope this helps!

@dppo
Copy link
Contributor

dppo commented Jan 14, 2025

@CodeWithBlaze @saurabhspinny

Fix for Bridgeless Architecture Error on App Reload

If you're running into the following error with the new architecture when reloading your app:

Caused by: java.lang.NullPointerException 
  at com.facebook.react.runtime.BridgelessCatalystInstance.getReactQueueConfiguration(BridgelessCatalystInstance.kt:116)
  at com.nozbe.watermelondb.WMDatabaseBridge.invalidate(WMDatabaseBridge.java:251) 
  at com.facebook.react.internal.turbomodule.core.TurboModuleManager.invalidate(TurboModuleManager.java:417)

This issue arises because the new Bridgeless architecture does not support CatalystInstance, as mentioned in the React Native source code.

Solution

The fix involves replacing getCatalystInstance().getReactQueueConfiguration().getJSQueueThread() with getJSMessageQueueThread() to maintain compatibility with the Bridgeless architecture:

diff --git a/native/android/src/main/java/com/nozbe/watermelondb/WMDatabaseBridge.java b/native/android/src/main/java/com/nozbe/watermelondb/WMDatabaseBridge.java
index 117b2bc1f75217c685bc9cfa76b4e55b22e015dc..a678d5d1715d633c4518a04b11fd1198c53157e9 100644
--- a/native/android/src/main/java/com/nozbe/watermelondb/WMDatabaseBridge.java
+++ b/native/android/src/main/java/com/nozbe/watermelondb/WMDatabaseBridge.java
@@ -248,7 +248,7 @@ public class WMDatabaseBridge extends ReactContextBaseJavaModule {
     public void invalidate() {
         // NOTE: See Database::install() for explanation
         super.invalidate();
-        reactContext.getCatalystInstance().getReactQueueConfiguration().getJSQueueThread().runOnQueue(() -> {
+        reactContext.getJSMessageQueueThread().runOnQueue(() -> {
             try {
                 Class<?> clazz = Class.forName("com.nozbe.watermelondb.jsi.WatermelonJSI");
                 Method method = clazz.getDeclaredMethod("onCatalystInstanceDestroy");

I’ve submitted a pull request with this fix and would appreciate it being merged. Hope this helps!

Is it cleaner to use reactContext.runOnJSQueueThread?

@limbo56
Copy link
Contributor

limbo56 commented Jan 14, 2025

@dppo You're right, and reactContext.runOnJSQueueThread() ensures the JS MessageQueueThread isn't null. I've updated my comment and PR with this change and credited you. Thanks for the suggestion!

@saurabhsaneja
Copy link

@limbo56 will this change also work with react native 0.76.0 and watermelon db version 0.28.1-1?

@limbo56
Copy link
Contributor

limbo56 commented Jan 14, 2025

@saurabhsaneja Yes, I'm running React Native 0.76.6 and WatermelonDB 0.28.0-1, and that change resolved the issue.

@saurabhsaneja
Copy link

@limbo56 thanks

@Rohit3523
Copy link

@saurabhsaneja Yes, I'm running React Native 0.76.6 and WatermelonDB 0.28.0-1, and that change resolved the issue.

Are you using it with newArch enabled?

@limbo56
Copy link
Contributor

limbo56 commented Jan 16, 2025

@Rohit3523 Yes

@saurabhspinny
Copy link

saurabhspinny commented Jan 17, 2025

@limbo56 are you importing com.nozbe.watermelondb.jsi.WatermelonDBJSIPackage in MainApplication.kt file and adding it like this: packages.add(WatermelonDBJSIPackage())

or are you importing com.nozbe.watermelondb.jsi.JSIInstaller and registering it using registerWatermelonJSI method like @prathameshmm02 mentioned

when i am importing com.nozbe.watermelondb.jsi.WatermelonDBJSIPackage like this, i am getting NativeModules.WMDatabaseBridge is not defined error

also, are you linking watermelon db manually or linking it using JSI installation (Optional, recommended) like mentioned in documention link below:
https://watermelondb.dev/docs/Installation

Post-thought: I think you are not installing watermelon db with jsi

@limbo56
Copy link
Contributor

limbo56 commented Jan 18, 2025

@saurabhspinny Apologies for getting back so late. I am installing WatermelonDB with JSI. Here's what I did:

  • I imported com.nozbe.watermelondb.jsi.WatermelonDBJSIPackage in MainApplication.kt.
  • Then, I registered it using packages.add(WatermelonDBJSIPackage()) inside the getPackages() method of ReactNativeHost.

If you're running into the NativeModules.WMDatabaseBridge is not defined error, there might be an issue with your setup. Double-check that you followed the JSI Installation steps in the documentation.

For reference, I’m using a bare workflow Expo project with SDK 52.

@saurabhsaneja
Copy link

saurabhsaneja commented Jan 19, 2025

@limbo56 Hey, thanks for getting back. I was able to make it work with manual linking and changing just one line of watermelon db like you mentioned. Maybe for react native cli project 0.76 version, jsi installation has issues @Rohit3523

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests