skip to content
Andrei Calazans

Migrating a React Native's Native Module to Turbo Module on Android

/ 2 min read

In the previous post on bridging the Trace API on Android I noticed how ChatGPT generated the native module using the legacy Native Module. So I had to manually migrate it to Turbo Module. This post is how to accomplish such migration.

๐Ÿงพ Before: Legacy Native Module

The original TracingModule looked like this in Kotlin:

// TracingModule.kt
class TracingModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
    override fun getName(): String = "TracingModule"

    @ReactMethod
    fun beginSection(sectionName: String) {
        Trace.beginSection(sectionName)
    }

    @ReactMethod
    fun endSection() {
        Trace.endSection()
    }
}

It was registered using the old ReactPackage interface:

// TracingPackage.kt
class TracingPackage : ReactPackage {
    override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
        return listOf(TracingModule(reactContext))
    }

    override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
        return emptyList()
    }
}

And used like this from JavaScript:

import { NativeModules } from 'react-native';

NativeModules.TracingModule.beginSection('MySection');

๐Ÿงฌ After: Turbo Module

โœ… 1. Replace Legacy Module with TurboModule Implementation

// TracingModuleSpec.kt
@ReactModule(name = TracingModule.NAME)
class TracingModule(reactContext: ReactApplicationContext)
  : ReactContextBaseJavaModule(reactContext), TurboModule {

    companion object {
        const val NAME = "TracingModule"
    }

    override fun getName(): String = NAME

    @ReactMethod
    fun beginSection(sectionName: String) {
        android.os.Trace.beginSection(sectionName)
    }

    @ReactMethod
    fun endSection() {
        android.os.Trace.endSection()
    }
}

Key additions:

  • Annotated with @ReactModule
  • Implements TurboModule interface

โœ… 2. Update Package to Use TurboReactPackage

class TracingPackage : TurboReactPackage() {

    override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
        return if (name == TracingModule.NAME) TracingModule(reactContext) else null
    }

    override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
        return ReactModuleInfoProvider {
            mapOf(
                TracingModule.NAME to ReactModuleInfo(
                    TracingModule.NAME,
                    TracingModule.NAME,
                    false, // canOverrideExistingModule
                    false, // needsEagerInit
                    true,  // hasConstants
                    true,  // isCxxModule
                    true   // isTurboModule
                )
            )
        }
    }
}

โœ… 3. Define the TypeScript Spec for the Module

// specs/NativeTracingModule.ts
import { TurboModule, TurboModuleRegistry } from 'react-native';

export interface Spec extends TurboModule {
  beginSection(name: string): void;
  endSection(): void;
}

export default TurboModuleRegistry.getEnforcing<Spec>('TracingModule');

This gives TypeScript type safety and integrates with the TurboModule codegen system.

โœ… 4. Add codegenConfig to package.json

"codegenConfig": {
  "name": "NativeTracingModuleSpec",
  "type": "modules",
  "jsSrcsDir": "specs",
  "android": {
    "javaPackageName": "com.myrnapp"
  }
}

This tells the Metro bundler and codegen how to build the binding between your JS spec and native module.

โœ… 5. Run Codegen

To generate the necessary artifacts for the TurboModule, run the following command in the android folder:

./gradlew generateCodegenArtifactsFromSchema

โœ… 6. Update JS Usage

Instead of accessing the module via NativeModules, you now import the strongly-typed TurboModule:

import TracingModule from './specs/NativeTracingModule';

TracingModule.beginSection('MySection');

โš ๏ธ Gotchas

  • Make sure the module name in the spec matches getName() exactly.
  • This currently an Android only module.

Iโ€™ll soon review how we can trace similarly on iOS.