iOS Native Modules
Native Module and Native Components are our stable technologies used by the legacy architecture. They will be deprecated in the future when the New Architecture will be stable. The New Architecture uses Turbo Native Module and Fabric Native Components to achieve similar results.
Welcome to Native Modules for iOS. 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, CalendarModule
, that will allow you to access Apple's calendar APIs from JavaScript. By the end you will be able to call CalendarModule.createCalendarEvent('Dinner Party', 'My House');
from JavaScript, invoking a native method that creates a calendar event.
Setup
To get started, open up the iOS project within your React Native application in Xcode. You can find your iOS project here within a React Native app:
We recommend using Xcode to write your native code. Xcode is built for iOS development, and using it will help you to quickly resolve smaller errors like code syntax.
Create Custom Native Module Files
The first step is to create our main custom native module header and implementation files. Create a new file called RCTCalendarModule.h
and add the following to it:
// RCTCalendarModule.h
#import <React/RCTBridgeModule.h>
@interface RCTCalendarModule : NSObject <RCTBridgeModule>
@end
You can use any name that fits the native module you are building. Name the class RCTCalendarModule
since you are creating a calendar native module. Since ObjC does not have language-level support for namespaces like Java or C++, convention is to prepend the class name with a substring. This could be an abbreviation of your application name or your infra name. RCT, in this example, refers to React.
As you can see below, the CalendarModule class implements the RCTBridgeModule
protocol. A native module is an Objective-C class that implements the RCTBridgeModule
protocol.
Next up, let’s start implementing the native module. Create the corresponding implementation file using cocoa touch class in xcode, RCTCalendarModule.m
, in the same folder and include the following content:
// RCTCalendarModule.m
#import "RCTCalendarModule.h"
@implementation RCTCalendarModule
// To export a module named RCTCalendarModule
RCT_EXPORT_MODULE();
@end
Module Name
For now, your RCTCalendarModule.m
native module only includes a RCT_EXPORT_MODULE
macro, which exports and registers the native module class with React Native. The RCT_EXPORT_MODULE
macro also takes an optional argument that specifies the name that the module will be accessible as in your JavaScript code.
This argument is not a string literal. In the example below RCT_EXPORT_MODULE(CalendarModuleFoo)
is passed, not RCT_EXPORT_MODULE("CalendarModuleFoo")
.
// To export a module named CalendarModuleFoo
RCT_EXPORT_MODULE(CalendarModuleFoo);
The native module can then be accessed in JS like this:
const {CalendarModuleFoo} = ReactNative.NativeModules;
If you do not specify a name, the JavaScript module name will match the Objective-C class name, with any "RCT" or "RK" prefixes removed.
Let's follow the example below and call RCT_EXPORT_MODULE
without any arguments. As a result, the module will be exposed to React Native using the name CalendarModule
, since that is the Objective-C class name, with RCT removed.
// Without passing in a name this will export the native module name as the Objective-C class name with “RCT” removed
RCT_EXPORT_MODULE();
The native module can then be accessed in JS like this:
const {CalendarModule} = ReactNative.NativeModules;
Export a Native Method to JavaScript
React Native will not expose any methods in a native module to JavaScript unless explicitly told to. This can be done using the RCT_EXPORT_METHOD
macro. Methods written in the RCT_EXPORT_METHOD
macro are asynchronous and the return type is therefore always void. In order to pass a result from a RCT_EXPORT_METHOD
method to JavaScript you can use callbacks or emit events (covered below). Let’s go ahead and set up a native method for our CalendarModule
native module using the RCT_EXPORT_METHOD
macro. Call it createCalendarEvent()
and for now have it take in name and location arguments as strings. Argument type options will be covered shortly.
RCT_EXPORT_METHOD(createCalendarEvent:(NSString *)name location:(NSString *)location)
{
}
Please note that the
RCT_EXPORT_METHOD
macro will not be necessary with TurboModules unless your method relies on RCT argument conversion (see argument types below). Ultimately, React Native will removeRCT_EXPORT_MACRO,
so we discourage people from usingRCTConvert
. Instead, you can do the argument conversion within the method body.
Before you build out the createCalendarEvent()
method’s functionality, add a console log in the method so you can confirm it has been invoked from JavaScript in your React Native application. Use the RCTLog
APIs from React. Let’s import that header at the top of your file and then add the log call.
#import <React/RCTLog.h>
RCT_EXPORT_METHOD(createCalendarEvent:(NSString *)name location:(NSString *)location)
{
RCTLogInfo(@"Pretending to create an event %@ at %@", name, location);
}
Synchronous Methods
You can use the RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD
to create a synchronous native method.
RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD(getName)
{
return [[UIDevice currentDevice] name];
}
The return type of this method must be of object type (id) and should be serializable to JSON. This means that the hook can only return nil or JSON values (e.g. NSNumber, NSString, NSArray, NSDictionary).
At the moment, we do not recommend using synchronous methods, 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 use RCT_EXPORT_BLOCKING_SYNCHRONOUS_METHOD
, 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.
Test What You Have Built
At this point you have set up the basic scaffolding for your native module in iOS. Test that out by accessing the native module and invoking it’s exported method in JavaScript.
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 NewModuleButton
's onPress()
function.
import React from 'react';
import {NativeModules, Button} from 'react-native';
const NewModuleButton = () => {
const onPress = () => {
console.log('We will invoke the native module here!');
};
return (
<Button
title="Click to invoke your native module!"
color="#841584"
onPress={onPress}
/>
);
};
export default NewModuleButton;
In order to access your native module from JavaScript you need to first import NativeModules
from React Native:
import {NativeModules} from 'react-native';
You can then access the CalendarModule
native module off of NativeModules
.
const {CalendarModule} = NativeModules;
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 NewModuleButton
:
const onPress = () => {
CalendarModule.createCalendarEvent('testName', 'testLocation');
};
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 :
- npm
- Yarn
npm run ios
yarn ios
Building as You Iterate
As you work through these guides and iterate on your native module, you will need to do a native rebuild of your application to access your most recent changes from JavaScript. This is because the code that you are writing sits within the native part of your application. While React Native’s metro bundler can watch for changes in JavaScript and rebuild JS bundle on the fly for you, it will not do so for native code. So if you want to test your latest native changes you need to rebuild by using the above command.
Recap✨
You should now be able to invoke your createCalendarEvent()
method on your native module in JavaScript. Since you are using RCTLog
in the function, you can confirm your native method is being invoked by enabling debug mode in your app and looking at the JS console in Chrome or the mobile app debugger Flipper. You should see your RCTLogInfo(@"Pretending to create an event %@ at %@", name, location);
message each time you invoke the native module method.
At this point you have created an iOS native module and invoked a method on it from JavaScript in your React Native application. You can read on to learn more about things like what argument types your native module method takes and how to setup callbacks and promises within your native module.