Skip to main content
Android native modules allow you to write Java or Kotlin code that can be called from your React Native JavaScript code.

Basic Module Structure

An Android native module consists of:
  1. A module class extending ReactContextBaseJavaModule
  2. Methods annotated with @ReactMethod
  3. A ReactPackage to register the module

Creating Your First Module

1
Create the Module Class
2
Create a new Java or Kotlin file for your module:
3
Kotlin
package com.yourapp.modules

import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
import com.facebook.react.bridge.Promise

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

    override fun getName(): String {
        return "CalendarModule"
    }

    @ReactMethod
    fun createEvent(name: String, location: String, promise: Promise) {
        try {
            // Native implementation here
            val eventId = createCalendarEvent(name, location)
            promise.resolve(eventId)
        } catch (e: Exception) {
            promise.reject("CREATE_EVENT_ERROR", e)
        }
    }

    private fun createCalendarEvent(name: String, location: String): String {
        // Your native implementation
        return "event-123"
    }
}
Java
package com.yourapp.modules;

import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.Promise;

public class CalendarModule extends ReactContextBaseJavaModule {
    
    public CalendarModule(ReactApplicationContext reactContext) {
        super(reactContext);
    }

    @Override
    public String getName() {
        return "CalendarModule";
    }

    @ReactMethod
    public void createEvent(String name, String location, Promise promise) {
        try {
            // Native implementation here
            String eventId = createCalendarEvent(name, location);
            promise.resolve(eventId);
        } catch (Exception e) {
            promise.reject("CREATE_EVENT_ERROR", e);
        }
    }

    private String createCalendarEvent(String name, String location) {
        // Your native implementation
        return "event-123";
    }
}
4
Create a Package
5
Create a ReactPackage to register your module:
6
Kotlin
package com.yourapp.modules

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

class CalendarPackage : ReactPackage {
    
    override fun createNativeModules(
        reactContext: ReactApplicationContext
    ): List<NativeModule> {
        return listOf(CalendarModule(reactContext))
    }

    override fun createViewManagers(
        reactContext: ReactApplicationContext
    ): List<ViewManager<*, *>> {
        return emptyList()
    }
}
Java
package com.yourapp.modules;

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

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class CalendarPackage implements ReactPackage {

    @Override
    public List<NativeModule> createNativeModules(
            ReactApplicationContext reactContext) {
        List<NativeModule> modules = new ArrayList<>();
        modules.add(new CalendarModule(reactContext));
        return modules;
    }

    @Override
    public List<ViewManager> createViewManagers(
            ReactApplicationContext reactContext) {
        return Collections.emptyList();
    }
}
7
Register the Package
8
Add your package to MainApplication.java or MainApplication.kt:
9
Kotlin
override fun getPackages(): List<ReactPackage> {
    return PackageList(this).packages.apply {
        // Add your custom package
        add(CalendarPackage())
    }
}
Java
@Override
protected List<ReactPackage> getPackages() {
    List<ReactPackage> packages = new PackageList(this).getPackages();
    // Add your custom package
    packages.add(new CalendarPackage());
    return packages;
}
10
Use from JavaScript
11
import { NativeModules } from 'react-native';

const { CalendarModule } = NativeModules;

// Call the native method
try {
  const eventId = await CalendarModule.createEvent(
    'Team Meeting',
    'Conference Room A'
  );
  console.log('Created event:', eventId);
} catch (error) {
  console.error('Error creating event:', error);
}

Module Methods

Method Annotations

All methods exposed to JavaScript must use the @ReactMethod annotation:
@ReactMethod
fun methodName(param: String) {
    // Implementation
}

Supported Parameter Types

Native modules automatically convert between JavaScript and Java/Kotlin types:
JavaScript TypeJava/Kotlin Type
booleanBoolean
numberInt, Double, Float
stringString
arrayReadableArray
objectReadableMap
functionCallback

Using Callbacks

@ReactMethod
fun fetchData(callback: Callback) {
    try {
        val data = "result"
        callback.invoke(null, data) // success
    } catch (e: Exception) {
        callback.invoke(e.toString(), null) // error
    }
}
From JavaScript:
CalendarModule.fetchData((error, result) => {
  if (error) {
    console.error(error);
  } else {
    console.log(result);
  }
});

Using Promises

Promises provide a cleaner API for async operations:
@ReactMethod
fun getEventById(eventId: String, promise: Promise) {
    try {
        val event = findEvent(eventId)
        if (event != null) {
            val eventMap = Arguments.createMap().apply {
                putString("id", event.id)
                putString("name", event.name)
            }
            promise.resolve(eventMap)
        } else {
            promise.reject("EVENT_NOT_FOUND", "Event not found")
        }
    } catch (e: Exception) {
        promise.reject("GET_EVENT_ERROR", e)
    }
}
From JavaScript:
try {
  const event = await CalendarModule.getEventById('123');
  console.log(event);
} catch (error) {
  console.error(error);
}

Working with Complex Types

Reading Arrays

@ReactMethod
fun processItems(items: ReadableArray) {
    for (i in 0 until items.size()) {
        val item = items.getString(i)
        // Process item
    }
}

Reading Maps

@ReactMethod
fun updateSettings(settings: ReadableMap) {
    if (settings.hasKey("theme")) {
        val theme = settings.getString("theme")
    }
    if (settings.hasKey("notifications")) {
        val enabled = settings.getBoolean("notifications")
    }
}

Creating Return Values

import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.WritableMap
import com.facebook.react.bridge.WritableArray

@ReactMethod
fun getEventList(promise: Promise) {
    val events = Arguments.createArray()
    
    val event1 = Arguments.createMap().apply {
        putString("id", "1")
        putString("name", "Meeting")
        putDouble("timestamp", System.currentTimeMillis().toDouble())
    }
    
    events.pushMap(event1)
    promise.resolve(events)
}

Accessing Android Context

The ReactApplicationContext provides access to Android system services:
class DeviceInfoModule(reactContext: ReactApplicationContext) : 
    ReactContextBaseJavaModule(reactContext) {

    override fun getName() = "DeviceInfo"

    @ReactMethod
    fun getBatteryLevel(promise: Promise) {
        val batteryManager = reactApplicationContext
            .getSystemService(Context.BATTERY_SERVICE) as BatteryManager
        
        val level = batteryManager
            .getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
        
        promise.resolve(level)
    }
}

Constants Export

Expose constants to JavaScript that are available immediately without async calls:
override fun getConstants(): Map<String, Any> {
    return mapOf(
        "PLATFORM" to "android",
        "VERSION" to Build.VERSION.SDK_INT,
        "MODEL" to Build.MODEL
    )
}
From JavaScript:
const { PLATFORM, VERSION, MODEL } = CalendarModule.getConstants();
console.log(`Running on ${PLATFORM}, SDK ${VERSION}, ${MODEL}`);

Lifecycle Methods

Module Initialization

override fun initialize() {
    super.initialize()
    // Initialize resources
}

Module Cleanup

override fun invalidate() {
    super.invalidate()
    // Clean up resources, cancel ongoing operations
}

Threading

Running on UI Thread

import com.facebook.react.bridge.UiThreadUtil

@ReactMethod
fun updateUI() {
    UiThreadUtil.runOnUiThread {
        // UI updates here
        val activity = currentActivity
        activity?.runOnUiThread {
            // Update views
        }
    }
}

Example: Image Loader Module

Here’s a real-world example based on React Native’s source code (packages/react-native/ReactAndroid/src/main/java/com/facebook/react/modules/image/ImageLoaderModule.kt:78-118):
@ReactMethod
override fun getSize(uriString: String?, promise: Promise) {
    if (uriString.isNullOrEmpty()) {
        promise.reject("INVALID_URI", "Cannot get size for empty URI")
        return
    }
    
    val source = ImageSource(reactApplicationContext, uriString)
    val request = ImageRequestBuilder.newBuilderWithSource(source.uri).build()
    val dataSource = imagePipeline.fetchDecodedImage(request, callerContext)
    
    val dataSubscriber = object : BaseDataSubscriber<CloseableReference<CloseableImage>>() {
        override fun onNewResultImpl(
            dataSource: DataSource<CloseableReference<CloseableImage>>
        ) {
            if (!dataSource.isFinished) return
            
            val ref = dataSource.result
            if (ref != null) {
                try {
                    val image = ref.get()
                    val sizes = Arguments.createMap().apply {
                        putInt("width", image.width)
                        putInt("height", image.height)
                    }
                    promise.resolve(sizes)
                } finally {
                    CloseableReference.closeSafely(ref)
                }
            } else {
                promise.reject("GET_SIZE_FAILURE", "Failed to get image size")
            }
        }

        override fun onFailureImpl(
            dataSource: DataSource<CloseableReference<CloseableImage>>
        ) {
            promise.reject("GET_SIZE_FAILURE", dataSource.failureCause)
        }
    }
    
    dataSource.subscribe(dataSubscriber, CallerThreadExecutor.getInstance())
}

Best Practices

  • Always handle errors and reject promises appropriately
  • Use meaningful error codes for promise rejections
  • Validate input parameters before processing
  • Clean up resources in invalidate()
  • Use promises for single async operations
  • Document your module’s API
  • Consider thread safety when accessing shared resources

Build docs developers (and LLMs) love