0Pricing
React Native Academy · 课时

使用 Kotlin 编写旧版原生模块

在 Kotlin 中创建 ReactContextBaseJavaModule,使用 @ReactMethod 标注一个方法,通过 ReactPackage 暴露该模块,并从 JavaScript 调用它。

使用 Kotlin 编写旧版原生模块 是 CoddyKit 上的免费 React Native Academy 课时。 这是第 1 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 React Native Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 React Native Academy 课程共包含 4 节课。

本课时的部分内容尚未翻译,以英文显示。

What Is a Legacy Native Module

A legacy native module allows you to call native Android (Kotlin/Java) code from JavaScript in a React Native app. When the built-in React Native APIs do not cover a device feature you need, you write a native module to bridge the gap. Legacy modules use the Bridge architecture, which means calls go asynchronously across the JS-to-native bridge.

Creating the Kotlin Module Class

Every Android native module extends ReactContextBaseJavaModule. You override getName() to return the name that JavaScript will use to call your module. Place this class inside the android/app/src/main/java/com/yourapp/ folder alongside existing Android source files.

// ToastModule.kt
package com.yourapp

import android.widget.Toast
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod

class ToastModule(reactContext: ReactApplicationContext) :
    ReactContextBaseJavaModule(reactContext) {

    override fun getName(): String = 'ToastModule'
}

Annotating Methods with @ReactMethod

Any method you want to expose to JavaScript must be annotated with @ReactMethod. The method must be public and its return type must be void — results are returned asynchronously via callbacks or promises, not return values. React Native serializes parameters automatically for basic types like String, Int, Boolean, and Double.

@ReactMethod
fun show(message: String, duration: Int) {
    val durationConst = if (duration == Toast.LENGTH_SHORT)
        Toast.LENGTH_SHORT else Toast.LENGTH_LONG
    Toast.makeText(reactApplicationContext, message, durationConst).show()
}

Creating the ReactPackage

Native modules must be registered through a ReactPackage. You implement the ReactPackage interface and return your module from createNativeModules. The package is then added to the list of packages in MainApplication.kt so React Native includes it at startup.

// ToastPackage.kt
package com.yourapp

import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ViewManager

class ToastPackage : ReactPackage {
    override fun createNativeModules(
        reactContext: ReactApplicationContext
    ): List<NativeModule> = listOf(ToastModule(reactContext))

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

Registering the Package in MainApplication

Open MainApplication.kt and find the getPackages() method. Add an instance of your ToastPackage to the list. React Native calls this during startup to discover all available native modules and register their JavaScript interfaces.

// MainApplication.kt (inside getPackages)
override fun getPackages(): List<ReactPackage> =
    PackageList(this).packages.apply {
        add(ToastPackage()) // <-- register your package here
    }

Calling the Module from JavaScript

On the JavaScript side, import NativeModules from react-native and access your module by the name returned from getName(). Wrap the native module in a helper file to provide a clean TypeScript API and hide the raw NativeModules reference from your components.

// ToastModule.js
import { NativeModules } from 'react-native';

const { ToastModule } = NativeModules;

export function showToast(message, duration = 'short') {
  const dur = duration === 'short' ? 0 : 1;
  ToastModule.show(message, dur);
}

// Usage in a component:
// import { showToast } from './ToastModule';
// showToast('Hello from native!', 'long');

Returning Values with Promises

Because @ReactMethod methods must return void, you use Promise parameters to send data back to JavaScript. Add promise: Promise as the last parameter, call promise.resolve(value) on success, or promise.reject(code, message) on error. On the JS side you await the call like any async function.

// Kotlin
@ReactMethod
fun getDeviceName(promise: Promise) {
    try {
        val name = android.os.Build.MODEL
        promise.resolve(name)
    } catch (e: Exception) {
        promise.reject('ERROR', e.message)
    }
}

// JavaScript
const name = await NativeModules.MyModule.getDeviceName();
console.log('Device:', name);

Using Callbacks as an Alternative

Before Promises became standard, native modules used Callback parameters (Callback type in the SDK). You receive one or two callbacks — typically successCallback and errorCallback — and invoke them from native code. Callbacks can only be called once; use Promises or events for repeated responses.

@ReactMethod
fun getBatteryLevel(
    successCallback: Callback,
    errorCallback: Callback
) {
    try {
        val intent = reactApplicationContext.registerReceiver(
            null,
            android.content.IntentFilter(
                android.content.Intent.ACTION_BATTERY_CHANGED
            )
        )
        val level = intent?.getIntExtra(
            android.os.BatteryManager.EXTRA_LEVEL, -1
        ) ?: -1
        successCallback.invoke(level)
    } catch (e: Exception) {
        errorCallback.invoke(e.message)
    }
}

Passing Complex Data with WritableMap

To return an object (JSON map) from native to JavaScript, use WritableMap and Arguments.createMap(). You add key-value pairs to the map using typed putters like putString, putInt, and putBoolean. The map is automatically serialized into a JavaScript object when it reaches the JS layer.

@ReactMethod
fun getDeviceInfo(promise: Promise) {
    val map = Arguments.createMap()
    map.putString('model', android.os.Build.MODEL)
    map.putString('brand', android.os.Build.BRAND)
    map.putInt('sdkVersion', android.os.Build.VERSION.SDK_INT)
    map.putBoolean('isTablet', reactApplicationContext
        .resources.configuration.smallestScreenWidthDp >= 600)
    promise.resolve(map)
}

The isBlockingSynchronousMethod Annotation

By default all @ReactMethod calls are asynchronous. Adding isBlockingSynchronousMethod = true makes the call synchronous — the JS thread blocks until native returns. This is strongly discouraged in production because it freezes the UI, but it can be useful for debugging or reading a tiny cached value that must be available immediately.

@ReactMethod(isBlockingSynchronousMethod = true)
fun getAppVersionSync(): String {
    return reactApplicationContext
        .packageManager
        .getPackageInfo(reactApplicationContext.packageName, 0)
        .versionName ?: 'unknown'
}

Module Constants via getConstants

You can expose compile-time constants to JavaScript by overriding getConstants() in your module. Constants are transferred once at startup and are synchronously accessible as NativeModules.MyModule.CONSTANT_NAME with no async call needed. Use constants for fixed values like error codes, config flags, or platform identifiers.

override fun getConstants(): Map<String, Any> {
    return mapOf(
        'LONG_TOAST' to Toast.LENGTH_LONG,
        'SHORT_TOAST' to Toast.LENGTH_SHORT,
        'PLATFORM' to 'android'
    )
}

// In JavaScript:
// const { LONG_TOAST } = NativeModules.ToastModule;
// showToast('Hello!', LONG_TOAST);

Quick Check

Test your understanding of React Native Mobile Development concepts from this lesson.

Lesson Recap

In this lesson you learned: how to create a ReactContextBaseJavaModule in Kotlin, annotate methods with @ReactMethod to expose them to JavaScript, and register the module through a ReactPackage in MainApplication. You also saw how to return data using Promises and WritableMap. Next up we explore writing a legacy native module in Swift for iOS.

常见问题解答

「使用 Kotlin 编写旧版原生模块」课时是免费的吗?

是的 — 「使用 Kotlin 编写旧版原生模块」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 React Native Academy 课程的其余内容,请升级到 CoddyKit PRO。 React Native Academy 课程共包含 4 节课。

「使用 Kotlin 编写旧版原生模块」这节课中我会学到什么?

在 Kotlin 中创建 ReactContextBaseJavaModule,使用 @ReactMethod 标注一个方法,通过 ReactPackage 暴露该模块,并从 JavaScript 调用它。 你通过在浏览器中直接运行的动手代码来练习 React Native Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 React Native Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 React Native Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 1 节课,共 4 节。

「使用 Kotlin 编写旧版原生模块」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 React Native Academy 课中编写并运行代码吗?

能。每节 React Native Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 使用 Kotlin 编写旧版原生模块
  2. 使用 Swift 编写旧版原生模块
  3. 使用 JSI 编写 Turbo 原生模块
  4. 来自原生代码的异步回调、Promise 与事件
← 返回 React Native Academy