Skip to main content

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.

npx nitrogen@latest init <name>

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 two things:

  1. A class declaration which extends HybridObject and describes its implementation via the @iOS or @Android decorators
  2. The generated JS class bindings via #nitro/...
Math.nitro.ts
import { iOS, Android, type HybridObject } from 'nitro-core'

@iOS({ language: 'swift', allowMultipleImplementations: true })
@Android({ language: 'kotlin', allowMultipleImplementations: true })
export declare class Math extends HybridObject {
// ...
}
export * from '#nitro/Math'

Right now Math is a class that doesn't have a native implementation yet. It cannot be constructed (new Math(..)), nor are there any static methods that are accessible on it.

For Math it makes sense to have an add(...) method that can add two numbers together - it won't make sense to create instances of Math via new Math(...), so we make add(...) a static method.

For this, we need to connect an implementation via the @iOS/@Android decorators - this connects the JS class with the native implementation in Swift/Kotlin or C++:

Math.nitro.ts
import { iOS, Android, type HybridObject } from 'nitro-core'

@iOS({
language: 'swift',
implementation: { className: 'HybridMath' }
})
@Android({
language: 'kotlin',
implementation: { className: 'HybridMath' }
})
export declare class Math extends HybridObject {
static add(a: number, b: number): number
}
export * from '#nitro/Math'

2.2. Run nitrogen​

After writing specs, re-generate the generated code by running nitrogen:

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​

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

HybridMath.swift
import Foundation
import NitroModules

class HybridMath : HybridMathSpec {
static func add(a: Double,
b: Double) -> Double {
return a + b
}
}

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

HybridMath.kt
package com.margelo.nitro.math

class HybridMath : HybridMathSpec() {
companion object {
fun add(a: Double, b: Double): Double {
return a + b
}
}
}

Your class' name has to match the string passed to implementation: ... - here; HybridMath.

3. Use your Hybrid Objects in JS​

Finally, you can use the your Hybrid Object from JS. To call our static add(...) method, simply use Math.add(...):

import { Math } from './Math.nitro'

const result = Math.add(5, 7) // --> 12

4. 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 - run create-expo-app:

npx create-expo-app@latest

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

cd example
npm install ../

5. Dive deeper​

Hybrid Objects can be instances with constructors, properties and instance methods - add a constructor() to be able to create instances of Math:

Math.nitro.ts
import { iOS, Android, type HybridObject } from 'nitro-core'

export interface MathOptions {
decimalPlaces: number
}

@iOS(...)
@Android(...)
export declare class Math extends HybridObject {
constructor(options: MathOptions)
add(a: number, b: number): number
}
export * from '#nitro/Math'

Then, simply create instances via new Math(...):

import { Math } from './Math.nitro'

const math = new Math({ decimalPlaces: 2 })
math.add(1.234, 2.345) // --> 3.58

const preciseMath = new Math({ decimalPlaces: 4 })
preciseMath.add(1.234, 2.345) // --> 3.579