Welcome to Native Modules for Android. Please start by reading the Native Modules Intro for an intro to what native modules are.
Create a Calendar Native Module
In the following guide you will create a native module,
To get started, open up the Android project within your React Native application in Android Studio. You can find your Android project here within a React Native app:
We recommend using Android Studio to write your native code. Android studio is an IDE built for Android development and using it will help you resolve minor issues like code syntax errors quickly.
We also recommend enabling Gradle Daemon to speed up builds as you iterate on Java code.
Create A Custom Native Module File
The first step is to create the CalendarModule.java Java file inside
android/app/src/main/java/com/your-app-name/ folder. This Java file will contain your native module Java class.
Then add the following content:
As you can see, your
CalendarModule class extends the
ReactContextBaseJavaModule class. For Android, Java native modules are written as classes that extend
It is worth noting that technically Java classes only need to extend the
BaseJavaModuleclass or implement the
NativeModuleinterface to be considered a Native Module by React Native.
However we recommend that you use
ReactContextBaseJavaModule, as shown above.
ReactContextBaseJavaModulegives access to the
ReactApplicationContext(RAC), which is useful for Native Modules that need to hook into activity lifecycle methods. Using
All Java native modules in Android need to implement the
The native module can then be accessed in JS like this:
Set up a method
CalendarModule that can be invoked in JS through
CalendarModule.createCalendarEvent(). For now, the method will take in a name and location as strings. Argument type options will be covered shortly.
Add a debug log in the method to confirm it has been invoked when you call it from your application. Below is an example of how you can import and use the Log class from the Android util package:
You can pass
isBlockingSynchronousMethod = true to a native method to mark it as a synchronous method.
At the moment, we do not recommend this, since calling methods synchronously can have strong performance penalties and introduce threading-related bugs to your native modules. Additionally, please note that if you choose to enable
isBlockingSynchronousMethod, your app can no longer use the Google Chrome debugger. This is because synchronous methods require the JS VM to share memory with the app. For the Google Chrome debugger, React Native runs inside the JS VM in Google Chrome, and communicates asynchronously with the mobile devices via WebSockets.
Register the Module (Android Specific)
Once a native module is written, it needs to be registered with React Native. In order to do so, you need to add your native module to a
ReactPackage and register the
ReactPackage with React Native. During initialization, React Native will loop over all packages, and for each
ReactPackage, register each native module within.
React Native invokes the method
createNativeModules() on a
To add your Native Module to
ReactPackage, first create a new Java Class named
MyAppPackage.java that implements
ReactPackage inside the
Then add the following content:
This file imports the native module you created,
CalendarModule. It then instantiates
CalendarModule within the
createNativeModules() function and returns it as a list of
NativeModules to register. If you add more native modules down the line, you can also instantiate them and add them to the list returned here.
It is worth noting that this way of registering native modules eagerly initializes all native modules when the application starts, which adds to the startup time of an application. You can use TurboReactPackage as an alternative. Instead of
createNativeModules, which return a list of instantiated native module objects, TurboReactPackage implements a
getModule(String name, ReactApplicationContext rac)method that creates the native module object, when required. TurboReactPackage is a bit more complicated to implement at the moment. In addition to implementing a
getModule()method, you have to implement a
getReactModuleInfoProvider()method, which returns a list of all the native modules the package can instantiate along with a function that instantiates them, example here. Again, using TurboReactPackage will allow your application to have a faster startup time, but it is currently a bit cumbersome to write. So proceed with caution if you choose to use TurboReactPackages.
To register the
CalendarModule package, you must add
MyAppPackage to the list of packages returned in ReactNativeHost's
getPackages() method. Open up your
MainApplication.java file, which can be found in the following path:
getPackages() method and add your package to the packages list
You have now successfully registered your native module for Android!
Test What You Have Built
Find a place in your application where you would like to add a call to the native module’s
createCalendarEvent() method. Below is an example of a component,
NewModuleButton you can add in your app. You can invoke the native module inside
NativeModules from React Native:
You can then access the
CalendarModule native module off of
Now that you have the CalendarModule native module available, you can invoke your native method
createCalendarEvent(). Below it is added to the
onPress() method in
The final step is to rebuild the React Native app so that you can have the latest native code (with your new native module!) available. In your command line, where the react native application is located, run the following:
Building as You Iterate
npx react-native run-android command.
You should now be able to invoke your
createCalendarEvent() method on your native module in the app. In our example this occurs by pressing the
NewModuleButton. You can confirm this by viewing the log you set up in your
createCalendarEvent() method. You can follow these steps to view ADB logs in your app. You should then be able to search for your
Log.d message (in our example “Create event called with name: testName and location: testLocation”) and see your message logged each time you invoke your native module method.
Beyond a Calendar Native Module
Better Native Module Export
Importing your native module by pulling it off of
NativeModules like above is a bit clunky.
CalendarModule.js with the following content:
This assumes that the place you are importing
CalendarModuleis in the same hierarchy as
CalendarModule.js. Please update the relative import as necessary.
The following types are currently supported but will not be supported in TurboModules. Please avoid using them:
- Integer -> ?number
- int -> number
- Float -> ?number
- float -> number
For argument types not listed above, you will need to handle the conversion yourself. For example, in Android,
Date conversion is not supported out of the box. You can handle the conversion to the
Date type within the native method yourself like so:
A native module can export constants by implementing the native method
getConstants(), which is available in JS. Below you will implement
getConstants() and return a Map that contains a
The constant can then be accessed by invoking
getConstants on the native module in JS:
Technically it is possible to access constants exported in
getConstants() directly off the native module object. This will no longer be supported with TurboModules, so we encourage the community to switch to the above approach to avoid necessary migration down the line.
getConstants()will become a regular native module method, and each invocation will hit the native side.
In order to create a native module method with a callback, first import the
Callback interface, and then add a new parameter to your native module method of type
Callback. There are a couple of nuances with callback arguments that will soon be lifted with TurboModules. First off, you can only have two callbacks in your function arguments- a successCallback and a failureCallback. In addition, the last argument to a native module method call, if it's a function, is treated as the successCallback, and the second to last argument to a native module method call, if it's a function, is treated as the failure callback.
WriteableMaps, if you need to use a collection use
WritableArrays. It is also important to highlight that the callback is not invoked immediately after the native function completes. Below the ID of an event created in an earlier call is passed to the callback.
Another important detail to note is that a native module method can only invoke one callback, one time. This means that you can either call a success callback or a failure callback, but not both, and each callback can only be invoked at most one time. A native module can, however, store the callback and invoke it later.
There are two approaches to error handling with callbacks. The first is to follow Node’s convention and treat the first argument passed to the callback as an error object.
Another option is to use an onSuccess and onFailure callback:
Refactoring the above code to use a promise instead of callbacks looks like this:
Similar to callbacks, a native module method can either reject or resolve a promise (but not both) and can do so at most once. This means that you can either call a success callback or a failure callback, but not both, and each callback can only be invoked at most one time. A native module can, however, store the callback and invoke it later.
await keyword within an async function to call it and wait for its result:
The reject method takes different combinations of the following arguments:
For more detail, you can find the
Promise.java interface here. If
userInfo is not provided, ReactNative will set it to null. For the rest of the parameters React Native will use a default value. The
message argument provides the error
Java reject call:
Error message in React Native App when promise is rejected:
RCTDeviceEventEmitter which can be obtained from the
ReactContext as in the code snippet below.
addListener on the NativeEventEmitter class.
Getting Activity Result from startActivityForResult
You'll need to listen to
onActivityResult if you want to get results from an activity you started with
startActivityForResult. To do this, you must extend
BaseActivityEventListener or implement
ActivityEventListener. The former is preferred as it is more resilient to API changes. Then, you need to register the listener in the module's constructor like so:
Now you can listen to
onActivityResult by implementing the following method:
Let's implement a basic image picker to demonstrate this. The image picker will expose the method
Listening to Lifecycle Events
Listening to the activity's LifeCycle events such as
onPause etc. is very similar to how
ActivityEventListener was implemented. The module must implement
LifecycleEventListener. Then, you need to register a listener in the module's constructor like so:
Now you can listen to the activity's LifeCycle events by implementing the following methods:
To date, on Android, all native module async methods execute on one thread. Native modules should not have any assumptions about what thread they are being called on, as the current assignment is subject to change in the future. If a blocking call is required, the heavy work should be dispatched to an internally managed worker thread, and any callbacks distributed from there.