Documentation Index
Fetch the complete documentation index at: https://mintlify.com/facebook/react-native/llms.txt
Use this file to discover all available pages before exploring further.
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:
- A module class extending
ReactContextBaseJavaModule
- Methods annotated with
@ReactMethod
- A
ReactPackage to register the module
Creating Your First Module
Create a new Java or Kotlin file for your module:
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";
}
}
Create a ReactPackage to register your module:
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();
}
}
Add your package to MainApplication.java or MainApplication.kt:
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;
}
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 Type | Java/Kotlin Type |
|---|
boolean | Boolean |
number | Int, Double, Float |
string | String |
array | ReadableArray |
object | ReadableMap |
function | Callback |
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