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.
- nitrogen
- react-native-builder-bob
- create-nitro-module
- Manually
npx nitro-codegen@latest init
npx create-react-native-library@latest
npx create-nitro-module@latest
1.1. Install Nitro and Nitrogen
In your existing react-native library, install nitro and nitrogen as dev dependencies:
npm install react-native-nitro-modules --save-dev
npm install nitro-codegen --save-dev
Then, you need to decide if you want to use Nitro's C++ library directly, or use nitrogen to generate specs:
- I will use Nitrogen later on
- I will not use Nitrogen
1.2. Create a nitro.json
file
Next, create a nitro.json
file. See 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:
npx nitro-codegen
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:
Pod::Spec.new do |s|
// ...
s.source_files = [ ... ]
load 'nitrogen/generated/ios/NitroExample+autolinking.rb'
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:
// ...
apply plugin: 'com.android.library'
apply plugin: 'org.jetbrains.kotlin.android'
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:
add_library($$androidCxxLibName$$ SHARED
...
)
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
):
#include "$$androidCxxLibName$$OnLoad.hpp"
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*) {
return margelo::nitro::$$cxxNamespace$$::initialize(vm);
}
If you don't plan on using Nitrogen at all - and instead write your 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.
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++.
- With Nitrogen ✨
- Manually
2.1. Write the HybridObject specs
A spec is just a *.nitro.ts
file that exports an interface, which extends HybridObject
:
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:
npx nitro-codegen
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
- Swift
- Kotlin
- C++
Create a new file (e.g. ios/HybridMath.swift
), and implement the HybridMathSpec
protocol:
class HybridMath : HybridMathSpec {
public func add(a: Double, b: Double) throws -> Double {
return a + b
}
}
Create a new file (e.g. android/.../HybridMath.kt
), and implement the HybridMathSpec
interface:
class HybridMath : HybridMathSpec() {
override fun add(a: Double, b: Double): Double {
return a + b
}
}
Create a new file (e.g. cpp/HybridMath.hpp
), and implement the virtual HybridMathSpec
class:
class HybridMath: public HybridMathSpec {
public:
double add(double a, double b) override {
return a + b;
}
}
To create new Hybrid Objects manually, you simply create a new C++ class that meets the following requirements:
- It public-inherits from
HybridObject
- It calls the
HybridObject
constructor with it's name - It overrides
loadHybridMethods()
and registers it's JS-callable methods & properties
#pragma once
#include <NitroModules/HybridObject.hpp>
// 1. Public-inherit from HybridObject
class HybridMath : public HybridObject {
public:
// 2. Call the HybridObject constructor with the name "Math"
HybridMath(): HybridObject("Math") { }
double add(double a, double b) {
return a + b;
}
// 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
:
- With Nitrogen ✨
- Manually
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:
- Swift
- Kotlin
- C++
{
...
"autolinking": {
"Math": {
"swift": "HybridMath"
}
}
}
{
...
"autolinking": {
"Math": {
"kotlin": "HybridMath"
}
}
}
{
...
"autolinking": {
"Math": {
"cpp": "HybridMath"
}
}
}
Now, just run Nitrogen again to generate the native bindings:
npx nitro-codegen
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:
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.
- nitrogen
- react-native-builder-bob
- create-nitro-module
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 - run create-expo-app
:
npx create-expo-app@latest
Then, install the library in the example app - e.g. via:
cd example
npm install ../
The Builder Bob template already includes an example app.
Simply run the react-native app inside the example/
folder.
The create-nitro-module template already includes an example app.
Simply run the react-native app inside the example/
folder.