Getting Started with React Native JSI Modules
Tags: reactnative, javascript, android, ios
Published: 2021-06-28 06:38:30

React Native JSI (Javascript Interface) is the new layer that helps in communication between Javascript and Native Platforms easier and faster. It is the core element in re-architecture of React Native with Fabric UI Layer and Turbo Modules.

## How is JSI different?
JSI removes the need for a `bridge` between Native(Java/ObjC) and Javascript code. It also removes the requirement to serialize/deserialize  all the information as JSON for communication between the two worlds. JSI is opening doors to new possibilities by bringing closes the javascript and the native worlds. Based on my understanding I am going to help you understand more about the JSI interface based on my knowledge.

1. Javascript Interface which allows us to register methods with the Javascript runtime. These methods are available via the `global` object in the Javascript world. 
2. The methods can be entirely written in C++ or they can be a way to communicate with Objective C code on iOS and Java code in Android. 
3. Any native module that is currently using the traditional `bridge` for communication between Javascript and the native worlds can be converted to a JSI module by writing a simple layer in C++
4. On iOS writing this layer is simple because C++ can run directly in Objective C hence all the iOS frameworks and code is available to use directly.
5. On android however we have to go an extra mile to do this through JNI.
6. These methods can be fully synchronous which means using `async/await` is not mandatory.

Now we are going to create a simple JSI Module which will help us understand everything even better.

## Setting up our JSI Module
Open terminal in the desired directory where you want to create your library and run the following:
```shell
npx create-react-native-library react-native-simple-jsi
```
It will ask you some questions. 
![Screenshot 2021-06-22 at 7.30.56 PM](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l23458rvftxdqu814wmk.png)
The important part is to choose `C++ for iOS and Android` when it asks for `Which languages you want to use?`

![Screenshot 2021-06-22 at 7.31.49 PM](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nwdmrqo1msny3qimrqj5.png)

This will setup a basic module for us that uses C++ code. However note that this is not a JSI module. We need to change some parts of the code on Android and iOS to make it a JSI module.

Navigate to the `react-native-simple-jsi` folder that was just created and **delete the example folder** then create a new example in its place.

```
npx react-native init example.
```

It will also resolve all the other dependencies.

## Configuring on Android
Now let's configure our library for android.

Prerequisite for android: Have NDK installed. Preferred version is `21.xx`. Install Cmake 3.10.2. You can install both of these from SDK Manager in Android Studio

### `CMakeLists.txt`

```c++
cmake_minimum_required(VERSION 3.9.0)

add_library(cpp
            SHARED
            ../cpp/example.cpp
            ./cpp-adapter.cpp
            ../../react-native/ReactCommon/jsi/jsi/jsi.cpp
)

include_directories(
            ../../react-native/React
            ../../react-native/React/Base
            ../../react-native/ReactCommon/jsi
            ../cpp
)

set_target_properties(
        cpp PROPERTIES
        CXX_STANDARD 17
        CXX_EXTENSIONS OFF
        POSITION_INDEPENDENT_CODE ON
)

target_link_libraries(
        cpp
        android
)

```
Okay, let's make this consumable. We are linking all the different libraries that we need for our jsi module here. We are telling CMake(Compiler for C++) how to compile our code and what directories to look for dependencies.

`cmake_minimum_required`: The minimum version of CMake required to compile our library.

`add_library`: We are telling the compiler, which libraries to add. 

1. `cpp` is the name of our library. 
2. `SHARED` means we are using shared c++ .so instead of compiling one to reduce size of our library.
3. We are including different files that we will need for our code to run. As you see, we have added path for `jsi.cpp` here too.

`include_directories`: Here we are telling the compiler to search for include files. 

The remaining `set_target_properties`, `find_library` and `target_link_libraries` can be used as they are. Remember to change `cpp` to your desirable library name here.

### `build.gradle`
Specify the minimum version of CMake to use while compiling c++ code.
```
  externalNativeBuild {
    cmake {
      path "./CMakeLists.txt"
      version "3.8.0+"
    }
  }
```

### Step 3: Installing JSI Bindings

Run `yarn add ../` inside the example folder to add our library to the example project.

Open `example/android` folder in Android Studio and wait for gradle to complete building your project.

If everything went as planned you should now see this in the Sidebar in Android Studio.
![Screenshot 2021-06-22 at 10.29.56 PM](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e7no4sq063jnk6qtdyie.png)

#### `SimpleJsiModule.java`
From the Sidebar navigate to `react-native-simple-jsi/android/java/com.reactnativesimplejsi/SimpleJsiModule.java` and replace it with the following code:
```java
package com.reactnativesimplejsi;

import android.util.Log;

import androidx.annotation.NonNull;

import com.facebook.react.bridge.JavaScriptContextHolder;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.module.annotations.ReactModule;

@ReactModule(name = SimpleJsiModule.NAME)
public class SimpleJsiModule extends ReactContextBaseJavaModule {
  public static final String NAME = "SimpleJsi";

  static {
    try {
      // Used to load the 'native-lib' library on application startup.
      System.loadLibrary("cpp");
    } catch (Exception ignored) {
    }
  }

  public SimpleJsiModule(ReactApplicationContext reactContext) {
    super(reactContext);
  }

  @Override
  @NonNull
  public String getName() {
    return NAME;
  }

  private native void nativeInstall(long jsi);

  public void installLib(JavaScriptContextHolder reactContext) {

    if (reactContext.get() != 0) {
      this.nativeInstall(
        reactContext.get()
      );
    } else {
      Log.e("SimpleJsiModule", "JSI Runtime is not available in debug mode");
    }

  }

}

```

As you see, there are no `@ReactMethod` etc here. Two things are happening in this class.

1. We are loading our c++ library using `System.loadLibrary`.
2. We have an `installLib` method here which is basically looking for javascript runtime memory reference. The `get` method basically returns a `long` value. This value is passed over to JNI where we will install our bindings.

But we have an error, the `nativeInstall` function is not present in JNI.
![Screenshot 2021-06-22 at 10.49.35 PM](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eaoeln1zk8isgzlp0wa3.png)
Just click on Create JNI function for nativeInstall in the tooltip that shows when you move cursor over the method.

Now if you open `cpp-adapter.cpp` file. You will see a `Java_com_reactnativesimplejsi_SimpleJsiModule_nativeInstall` function added.

#### `SimpleJsiModulePackage.java`
This file does not exist. You have to create this java class.

Create a new java class and name it `SimpleJsiModulePackage`.
![Screenshot 2021-06-22 at 10.58.05 PM](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zyf7621wrjir2f7k0221.png)

Replace with the following code:
```java
package com.reactnativesimplejsi;

import com.facebook.react.bridge.JSIModulePackage;
import com.facebook.react.bridge.JSIModuleSpec;
import com.facebook.react.bridge.JavaScriptContextHolder;
import com.facebook.react.bridge.ReactApplicationContext;
import java.util.Collections;
import java.util.List;



public class SimpleJsiModulePackage implements JSIModulePackage {
  @Override
  public List<JSIModuleSpec> getJSIModules(ReactApplicationContext reactApplicationContext, JavaScriptContextHolder jsContext) {

    reactApplicationContext.getNativeModule(SimpleJsiModule.class).installLib(jsContext);

    return Collections.emptyList();
  }
}


```
In this class we are overriding the `getJSIModules` method and installing our jsi bindings. 

At this point our module is registered and running. So we are getting the module from react context and then calling `installLib` function to install our library.

While we could do this directly in our native module when it loads, it would not be safe because it is possible that the runtime is not loaded when the native module is ready. This package gives us more control and makes sure that runtime is available when we call `installLib`.

To call this method and install library we have to modify our app's `MainApplication.java`. 

```java
....

import com.facebook.react.bridge.JSIModulePackage;
import com.reactnativesimplejsi.SimpleJsiModulePackage;

public class MainApplication extends Application implements ReactApplication {

  private final ReactNativeHost mReactNativeHost =
      new ReactNativeHost(this) {
        @Override
        public boolean getUseDeveloperSupport() {
          return BuildConfig.DEBUG;
        }

        @Override
        protected List<ReactPackage> getPackages() {
          @SuppressWarnings("UnnecessaryLocalVariable")
          List<ReactPackage> packages = new PackageList(this).getPackages();
          // Packages that cannot be autolinked yet can be added manually here, for SimpleJsiExample:
          // packages.add(new MyReactNativePackage());
          return packages;
        }


        @Override
        protected JSIModulePackage getJSIModulePackage() {
          return new SimpleJsiModulePackage();
        }

        @Override
        protected String getJSMainModuleName() {
          return "index";
        }
      };
.....
```
1. We are importing `JSIModulePackage`
2. We are registering our `SimpleJsiModulePackage` as a JSI Module so that when JS Runtime loads, our jsi bindings are also installed. Inside our instance of `ReactNativeHost` we are overriding `getJSIModulePackage` method and returning an new instance of `SimpleJsiModulePackage`.

### `cpp-adapter.cpp`
This is our Java Native Interface (JNI) adapter which allows for two way communication between java and native c++ code. We can call c++ code from java and java code from c++.

Here is how our adapter looks like.
```c++
#include <jni.h>
#include "example.h"

extern "C"
JNIEXPORT void JNICALL
Java_com_reactnativesimplejsi_SimpleJsiModule_nativeInstall(JNIEnv *env, jobject thiz, jlong jsi) {
    // TODO: implement nativeInstall()
}
```
Let's add JSI Bindings now assuming that `example` includes our `install` function which I will explain later.

```c++
#include <jni.h>
#include "example.h"

extern "C"
JNIEXPORT void JNICALL
Java_com_reactnativesimplejsi_SimpleJsiModule_nativeInstall(JNIEnv *env, jobject thiz, jlong jsi) {

    auto runtime = reinterpret_cast<facebook::jsi::Runtime *>(jsi);


    if (runtime) {
        example::install(*runtime);
    }
}
```
We are calling `example::install` from our `nativeInstall` function which is called from java code.
```c++
Java_com_reactnativesimplejsi_SimpleJsiModule_nativeInstall(JNIEnv *env, jobject thiz, jlong jsi)
```
1. `JNIEnv`: A JNI interface pointer
2. `jobject`: The java class from which the function is called.
3. `long` value of our runtime memory reference.

We are reinterpreting the runtime class with `auto runtime = reinterpret_cast<jsi::Runtime *>(jsi);` and then calling `install(*runtime);` to install our bindings.

## Configuring on iOS
Configuration on iOS is easier than android and includes a few simple step.

Run `pod install` in `example/ios` and open `example.xcworkspace` in xcode.

### `SimpleJsi.mm`

Navigate to `Pods > Development Pods > react-native-simple-jsi > ios` and open `SimpleJsi.mm`. 
![Screenshot 2021-06-23 at 11.09.23 AM](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/c2m3vfv56ze9uc9nde5d.png)

Replace it with following code:

```objc
#import "SimpleJsi.h"
#import <React/RCTBridge+Private.h>
#import <React/RCTUtils.h>
#import <jsi/jsi.h>
#import "example.h"

@implementation SimpleJsi

@synthesize bridge = _bridge;
@synthesize methodQueue = _methodQueue;

RCT_EXPORT_MODULE()

+ (BOOL)requiresMainQueueSetup {
    
    return YES;
}
 
- (void)setBridge:(RCTBridge *)bridge {
    _bridge = bridge;
    _setBridgeOnMainQueue = RCTIsMainQueue();
    [self installLibrary];
}

- (void)installLibrary {
    
    RCTCxxBridge *cxxBridge = (RCTCxxBridge *)self.bridge;
    
    if (!cxxBridge.runtime) {
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.001 * NSEC_PER_SEC),
                       dispatch_get_main_queue(), ^{
            /**
             When refreshing the app while debugging, the setBridge
             method is called too soon. The runtime is not ready yet
             quite often. We need to install library as soon as runtime
             becomes available.
             */
            [self installLibrary];
        });
        return;
    }
    
    example::install(*(facebook::jsi::Runtime *)cxxBridge.runtime);
}

@end
```
1. At the top we are synthesising the `bridge` and `methodQueue`.
2. We are telling React that our module requires setup on Main Queue.
3. We are getting an instance of `bridge` which we will use to get the runtime and install our jsi bindings. Inside it we are checking if `bridge.runtime` exists or not. If it does not, we are waiting for sometime and then trying again until the `bridge.runtime` becomes available.

### `SimpleJsi.h`

```objc
#import <React/RCTBridgeModule.h>

@interface SimpleJsi : NSObject <RCTBridgeModule>

@property (nonatomic, assign) BOOL setBridgeOnMainQueue;

@end

We are adding a property here, `setBridgeOnMainQueue` which tells React to set the bridge on main queue. This results in `setBridge` being called in our module with the `bridge`.
```

So this is how we configure JSI for both android and iOS. Now let's see what is happening in `example.cpp` where our `install`function is present.

```c++
#include "example.h"
#include <jsi/jsi.h>

using namespace facebook::jsi;
using namespace std;

namespace example {

void install(Runtime &jsiRuntime) {
    
    auto helloWorld = Function::createFromHostFunction(jsiRuntime,
                                                       PropNameID::forAscii(jsiRuntime,
                                                                            "helloWorld"),
                                                       0,
                                                       [](Runtime &runtime,
                                                          const Value &thisValue,
                                                          const Value *arguments,
                                                          size_t count) -> Value {
        string helloworld = "helloworld";
        
        
        return Value(runtime,
                     String::createFromUtf8(
                                            runtime,
                                            helloworld));
        
    });
    
    jsiRuntime.global().setProperty(jsiRuntime, "helloWorld", move(helloWorld));
}

}
```
Okay let's make this consumable.

1. At the top, you see that we have included `jsi` include files.
2. The `using namespace facebook` etc helps us not write `facebook::` over and over.
3. `install` function takes one parameter and that is our JS runtime. Inside this function we are registering a method by name `helloWorld` which will return a `hello world` string when we call it from javascript code.
4. `Function::createFromHostFunction` is a method creates a function which, when invoked, calls C++ code.
5. `jsiRuntime.global().setProperty` is where we bind our function with the javascript runtime global object.

```c++
Function::createFromHostFunction(Runtime, PropNameID, paramCount, function)
```
1. `Runtime`: Represents a JS runtime where our javascript code is running
2. `PropNameID`: An identifier to find our function. It is a simple string.
3. `paramCount`: Number of parameters this function will have. In our case it's `0`.
4. `function`: A function that will be invoked when we call `global.helloWorld()` from javascript.
 
Our `function` has also 4 parameters. 
1. `Runtime`: Represents a JS runtime where our javascript code is running
2. `Value &thisValue`: It is a reference to `Value` class instance which is used to pass JS values to and from javascript code.
3. `Value *arguments`: The arguments for this function coming from Javascript.
4. `size_t count`: Total number of arguments.

Inside the function we are creating a simple string `hello world`.

Then we are returning `Value`. The `String::createFromUtf8` function helps us convert c++ string(`std::string`) to a Javascript String (`jsi::String`) value. 

## Calling our function in Javascript
Now we can call our function `helloWorld` in javascript code. This should show helloworld at the center of screen.

```tsx
export default function App() {
  const [result, setResult] = React.useState<number | undefined>();

  React.useEffect(() => {
    setResult(global.helloWorld())
  }, []);

  return (
    <View style={styles.container}>
      <Text>Result: {result}</Text>
    </View>
  );
}
```
![Simulator Screen Shot - iPhone 8 - 2021-06-23 at 12.55.00](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4gbm571f219wm4z1hjqa.png)

From here onwards, there are unlimited possibilities to what you can do.

## Calling function with multiple arguments
In `example.cpp` add this new function. It's a simple function that does multiplication of two numbers
```c++
   auto multiply = Function::createFromHostFunction(jsiRuntime,
                                                       PropNameID::forAscii(jsiRuntime,
                                                                            "multiply"),
                                                       2,
                                                       [](Runtime &runtime,
                                                          const Value &thisValue,
                                                          const Value *arguments,
                                                          size_t count) -> Value {
        int x = arguments[0].getNumber();
        int y = arguments[1].getNumber();
        
        return Value(x * y);
    
    });

    jsiRuntime.global().setProperty(jsiRuntime, "multiply", move(multiply));
```
Notice now that we have set `paramCount` to 2 because we have two arguments.

In Javascript we can call 

```js
global.multiply(2,4) // 8
```

## Calling a JS Callback from C++
Here we are doing the same multiplication but not returning its value. Instead we are calling a JS function.
```c++
    auto multiplyWithCallback = Function::createFromHostFunction(jsiRuntime,
                                                       PropNameID::forAscii(jsiRuntime,
                                                                            "multiplyWithCallback"),
                                                       3,
                                                       [](Runtime &runtime,
                                                          const Value &thisValue, 
                                                          const Value *arguments,
                                                          size_t count) -> Value {
        int x = arguments[0].getNumber();
        int y = arguments[1].getNumber();
        
        arguments[2].getObject(runtime).getFunction(runtime).call(runtime, x * y);
        
        return Value();
    
    });

    jsiRuntime.global().setProperty(jsiRuntime, "multiplyWithCallback", move(multiplyWithCallback));
```

While in javascript, we can call the function like this:

```js
  global.multiplyWithCallback(2,4,(a) => {
    console.log(a); // 8
  })
```

## `Value`
A Value can be `undefined`, `null`, `boolean`, `number`, `symbol`, `string`, or `object`.

## Conclusion

JSI is a game changer for React Native and and it is transforming the way React Native works. Today we have learnt how to build a simple JSI module. In the next blog, I will explain how we can convert any native module  to a JSI module using some simple steps.

The complete code of the library and example app can be found on [Github](https://github.com/ammarahm-ed/react-native-simple-jsi).

If you use Async Storage in your React Native App, you should give [react-native-mmkv-storage](https://github.com/ammarahm-ed/react-native-mmkv-storage) a try. The fastest storage library for react native built with JSI.

{% github ammarahm-ed/react-native-mmkv-storage %}