Skip to content

Latest commit

 

History

History
356 lines (277 loc) · 11.5 KB

File metadata and controls

356 lines (277 loc) · 11.5 KB
toc_min_heading_level 2
toc_max_heading_level 2

import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem';

How to build a Nitro Module

A Nitro Module is essentially just a react-native library that depends on react-native-nitro-modules and exposes one or more Hybrid Objects. It can either just use react-native-nitro-modules directly from C++, or use Nitrogen to generate bindings from TypeScript to native - in this case you can even use Swift and Kotlin.

This is a quick guide to build a Nitro Module from start to finish:

1. Create a Nitro Module

First, you need to create a Nitro Module - either by bootstrapping a template using nitrogen, react-native-builder-bob or create-nitro-module - or by manually adding Nitro to your existing library/app.

```sh npx nitrogen@latest init ``` ```sh npx create-react-native-library@latest ``` ```sh npx create-nitro-module@latest ```
### 1.1. Install Nitro and Nitrogen

In your existing react-native library, install nitro and nitrogen as dev dependencies:
```sh
npm install react-native-nitro-modules --save-dev
npm install nitrogen --save-dev
```

Then, you need to decide if you want to use Nitro's C++ library directly, or use [nitrogen](nitrogen) to generate specs:

<Tabs>
  <TabItem value="with-nitrogen" label="I will use Nitrogen later on" default>

    ### 1.2. Create a `nitro.json` file

    Next, create a `nitro.json` file. See [Configuration (`nitro.json`)](configuration-nitro-json) for a full guide.

    ### 1.3. Run nitrogen once

    After creating a `nitro.json` file, run nitrogen once to generate the autolinking setup:

    ```sh
    npx nitrogen
    ```

    ### 1.4. Add nitro's generated autolinking files to your project

    #### iOS

    In your iOS `.podspec`, you need to load the `+autolinking.rb` file that was generated by nitrogen:
    ```ruby
    Pod::Spec.new do |s|
      // ...
      s.source_files = [ ... ]
      // diff-add
      load 'nitrogen/generated/ios/NitroExample+autolinking.rb'
      // diff-add
      add_nitrogen_files(s)
    ```

    #### Android

    In your Android's `build.gradle`, load the `+autolinking.gradle` file at top-level (after any `apply plugin` calls) to set up the Kotlin files for autolinking:

    ```groovy
    // ...
    apply plugin: 'com.android.library'
    apply plugin: 'org.jetbrains.kotlin.android'
    // diff-add
    apply from: '../nitrogen/generated/android/$$androidCxxLibName$$+autolinking.gradle'
    ```

    And also add the `+autolinking.cmake` file to your `CMakeLists.txt` to set up the C++/JNI autolinking:

    ```cmake
    add_library($$androidCxxLibName$$ SHARED
      ...
    )
    // diff-add
    include(${CMAKE_SOURCE_DIR}/../nitrogen/generated/android/$$androidCxxLibName$$+autolinking.cmake)
    ```

    And lastly, call the C++/JNI `initialize` function inside your library's `JNI_OnLoad(...)` entry point (often in `cpp-adapter.cpp`):

    ```cpp
    #include "$$androidCxxLibName$$OnLoad.hpp"

    JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) {
      // diff-add
      return margelo::nitro::$$cxxNamespace$$::initialize(vm);
    }
    ```
  </TabItem>
  <TabItem value="without-nitrogen-at-all" label="I will not use Nitrogen">

    If you don't plan on using Nitrogen at all - and instead write your [Hybrid Objects](hybrid-objects) manually using C++, you do not need to set up any autolinking files since you will be responsible for exposing your Hybrid Objects to JS.

  </TabItem>
</Tabs>

2. Create Hybrid Object specs

To actually use Nitro, you need to create Hybrid Objects - either by using Nitro's code-generator CLI “Nitrogen”, or by just manually extending the HybridObject base class in C++.

### 2.1. Write the HybridObject specs

A spec is just a `*.nitro.ts` file that exports an interface, which extends `HybridObject`:

```ts title="Math.nitro.ts"
export interface Math extends HybridObject {
  add(a: number, b: number): number
}
```

### 2.2. Run nitrogen

After writing specs, re-generate the generated code by running [nitrogen](nitrogen):

```sh
npx nitrogen
```

This then will generate a native specs which you can implement - in C++, that'd be `HybridMathSpec.hpp`.

### 2.3. Implement the generated native specs

<Tabs groupId="native-language">
  <TabItem value="swift" label="Swift" default>

    Create a new file (e.g. `ios/HybridMath.swift`), and implement the `HybridMathSpec` protocol:

    ```swift title="HybridMath.swift"
    class HybridMath : HybridMathSpec {
      public func add(a: Double, b: Double) throws -> Double {
        return a + b
      }
    }
    ```
  </TabItem>
  <TabItem value="kotlin" label="Kotlin">

    Create a new file (e.g. `android/.../HybridMath.kt`), and implement the `HybridMathSpec` interface:

    ```kotlin title="HybridMath.kt"
    class HybridMath : HybridMathSpec() {
      override fun add(a: Double, b: Double): Double {
        return a + b
      }
    }
    ```
  </TabItem>
  <TabItem value="cpp" label="C++">

    Create a new file (e.g. `cpp/HybridMath.hpp`), and implement the virtual `HybridMathSpec` class:

    ```cpp title="HybridMath.hpp"
    class HybridMath: public HybridMathSpec {
    public:
      double add(double a, double b) override {
        return a + b;
      }
    }
    ```
  </TabItem>
</Tabs>
To create new [Hybrid Objects](hybrid-objects) manually, you simply create a new C++ class that meets the following requirements:

1. It **public**-inherits from `HybridObject`
2. It calls the `HybridObject` constructor with it's name
3. It overrides `loadHybridMethods()` and registers it's JS-callable methods & properties

```cpp title="HybridMath.hpp"
#pragma once
#include <NitroModules/HybridObject.hpp>

// diff-add
// 1. Public-inherit from HybridObject
class HybridMath : public HybridObject {
public:
  // diff-add
  // 2. Call the HybridObject constructor with the name "Math"
  HybridMath(): HybridObject("Math") { }

  double add(double a, double b) {
    return a + b;
  }

  // diff-add
  // 3. Override loadHybridMethods()
  void loadHybridMethods() override {
    // register base methods (toString, ...)
    HybridObject::loadHybridMethods();
    // register custom methods (add)
    registerHybrids(this, [](Prototype& proto) {
      proto.registerHybridMethod("add", &HybridMath::add);
    });
  }
};
```

3. (Optional) Register Hybrid Objects

Each Hybrid Object you want to be able to construct from JS has to be registered in Nitro's HybridObjectRegistry. If you don't want to register this Hybrid Object, you can skip this part - you will still be able to create it from another Hybrid Object's function (e.g. using the Factory-pattern).

You can either use Nitrogen to automatically generate bindings for your Hybrid Object's constructor, or manually register them using the C++ API for HybridObjectRegistry:

In your nitro.json config, you can connect the name of the Hybrid Object ("Math") with the name of the native C++/Swift/Kotlin class that you used to implement the spec (HybridMath) using the autolinking section:

<Tabs groupId="native-language">
  <TabItem value="swift" label="Swift" default>
    ```json title="nitro.json"
    {
      ...
      "autolinking": {
        // diff-add
        "Math": {
          // diff-add
          "swift": "HybridMath"
        // diff-add
        }
      }
    }
    ```
  </TabItem>
  <TabItem value="kotlin" label="Kotlin">
    ```json title="nitro.json"
    {
      ...
      "autolinking": {
        // diff-add
        "Math": {
          // diff-add
          "kotlin": "HybridMath"
        // diff-add
        }
      }
    }
    ```
  </TabItem>
  <TabItem value="cpp" label="C++">
    ```json title="nitro.json"
    {
      ...
      "autolinking": {
        // diff-add
        "Math": {
          // diff-add
          "cpp": "HybridMath"
        // diff-add
        }
      }
    }
    ```
  </TabItem>
</Tabs>

Now, just run [Nitrogen](nitrogen) again to generate the native bindings:

```sh
npx nitrogen
```
To manually register a C++ class inside the `HybridObjectRegistry`, you need to call `HybridObjectRegistry::registerHybridObjectConstructor(...)` at some point before your JS code runs - e.g. at app startup:

```cpp
HybridObjectRegistry::registerHybridObjectConstructor(
  "Math",
  []() -> std::shared_ptr<HybridObject> {
    return std::make_shared<HybridMath>();
  }
);
```

4. Use your Hybrid Objects in JS

Lastly, you can initialize and use the registered Hybrid Objects from JS. This is what this will ultimately look like:

interface Math extends HybridObject {
  add(a: number, b: number): number
}

const math = NitroModules.createHybridObject<Math>("Math")
const result = math.add(5, 7) // --> 12

5. Run it

To test the library you just created, you now need to set up an example app for it.

Nitro's template does not include an example app by default, which makes it easier to be used in monorepos.
To create an example app yourself - for example with [Expo](https://expo.dev) - run `create-expo-app`:

```sh
npx create-expo-app@latest
```

Then, install the library in the example app - e.g. via:

```
cd example
npm install ../
```
The [Builder Bob](https://github.com/callstack/react-native-builder-bob) template already includes an example app. Simply run the react-native app inside the `example/` folder. The [create-nitro-module](https://github.com/patrickkabwe/create-nitro-module) template already includes an example app. Simply run the react-native app inside the `example/` folder.