Native modules bind native code to JavaScript, extending the functionality of your React Native application. For You.i Platform, native modules are usually written in C++.
In general, a You.i React Native native module is simply a C++ class that exposes a name, methods, and constants to the React instance within JavaScript.
There are two simple steps to create a You.i React Native native module:
You can then follow the rest of the topics here for adding constant methods, adding callbacks, dealing with events, etc.
If your native module includes platform-specific code in Swift or Java, see the sections below for additional steps to take.
Create your class starting with header using this template, replacing MyFirstModule
with the name of your module:
//MyFirstModule.h
#ifndef _PLATFORM_NAME_MODULE_H_
#define _PLATFORM_NAME_MODULE_H_
#include <youireact/NativeModule.h>
class YI_RN_MODULE(MyFirstModule)
{
public:
YI_RN_EXPORT_NAME(MyFirstModule);
};
#endif // _PLATFORM_NAME_MODULE_H_
And the matching implementation file:
//MyFirstModule.cpp
#include "MyFirstModule.h"
#include <youireact/NativeModuleRegistry.h>
#include <platform/YiDeviceBridgeLocator.h>
#include <platform/YiDeviceInformationBridge.h>
YI_RN_INSTANTIATE_MODULE(MyFirstModule);
YI_RN_REGISTER_MODULE(MyFirstModule);
Implicitly, the getName()
method is added to your module and returns the name of your module (which is specified with the macro YI_RN_EXPORT_NAME
shown in the header file above).
Now you can use MyFirstModule
in your application like this:
import { NativeModules } from 'react-native';
const SampleModule = NativeModules.MyFirstModule;
You may have different native module files for each platform (for example, you may have a module that works on iOS and another that works on Android), so the build system doesn’t automatically find and build all new .cpp files when building your project.
To tell the build system to add these new files to your project, add the .cpp and .h file names to youi/SourceList.cmake
as shown below, then generate the project again with youi-tv generate -p <platform>
.
# =============================================================================
set (YI_PROJECT_SOURCE
src/App.cpp
src/AppFactory.cpp
src/MyFirstModule.cpp
)
set (YI_PROJECT_HEADERS
src/App.h
src/MyFirstModule.h
)
If needed, you can use cmake conditions or YI_PROJECT_SOURCE_*
and YI_PLATFORM_UPPER
variables to define exactly which files to include with each platform’s build.
In the example below, myiosModule.cpp
is included for iOS, mytvOSModule.cpp
is included for tvOS, and nothing extra is included for any other platform.
set(YI_PROJECT_SOURCE_IOS
src/myiOSModule.cpp
)
set(YI_PROJECT_SOURCE_TVOS
src/mytvOSModule.cpp
)
set(YI_PROJECT_SOURCE
src/App.cpp
src/AppFactory.cpp
${YI_PROJECT_SOURCE_${YI_PLATFORM_UPPER}}
)
The native module is instantiated and the class constructor is implicitly called when your application starts.
You probably want your native module to expose some methods to JavaScript.
To do this, use YI_RN_EXPORT_METHOD
and YI_RN_DEFINE_EXPORT_METHOD
in the header and class files, respectively.
The basic framework from create includes an example with a method called doSomething
:
// MyFirstModule.h
#include <youireact/NativeModule.h>
class YI_RN_MODULE(MyFirstModule)
{
public:
YI_RN_EXPORT_NAME(MyFirstModule);
YI_RN_EXPORT_METHOD(doSomething)();
};
// MyFirstModule.cpp
#include "MyFirstModule.h"
YI_RN_INSTANTIATE_MODULE(MyFirstModule); // this is required
YI_RN_DEFINE_EXPORT_METHOD(MyFirstModule, doSomething)()
{
YI_LOGD(TAG, "Native Module Called");
}
From JavaScript, you can now access to the doSomething
method as follows:
// native-module-sample.js
import { NativeModules } from 'react-native';
const SampleModule = NativeModules.MyFirstModule;
// Call into C++
SampleModule.doSomething();
The sample method is declared without parameters, but your native module methods can include C++ parameters. See Method Parameters for more details.
Note: Exported native module methods can’t be overloaded.
To export data from native or C++ code, use constant methods.
Use the YI_RN_EXPORT_CONSTANT
and the YI_RN_DEFINE_EXPORT_CONSTANT
macros to define your method:
// MyFirstModule.h
public:
YI_RN_EXPORT_CONSTANT(someConstants);
// MyFirstModule.cpp
YI_RN_DEFINE_EXPORT_CONSTANT(MyFirstModule, someConstants)
{
return folly::dynamic::object
("size", folly::dynamic::object("width", 100)("height", 100))
("name", "Name");
}
// native-module-sample.js
import { NativeModules } from 'react-native';
const SampleModule = NativeModules.MyFirstModule;
// Call into C++
const size = SampleModule.someConstants.size;
Your native module can accept a JavaScript function as a callback. Imagine you want to pass in a String from JavaScript and specify a callback function to handle a returned value. Start by updating the C++ method definition:
// myNativeModule.h
#include <youireact/NativeModule.h>
class YI_RN_MODULE(NativeModuleSample)
{
public:
...
YI_RN_EXPORT_METHOD(doSomethingWithCallback)(Callback successCallback, Callback failedCallback, CYIString str);
};
And add the corresponding C++ method.
Note that Callback
parameters must always come first in the definition.
// myNativeModule.cpp
...
YI_RN_DEFINE_EXPORT_METHOD(NativeModuleSample, doSomethingWithCallback)(Callback successCallback, Callback failedCallback, CYIString str))
{
YI_LOGD(TAG, "%s", str.GetData());
// callback with ({response} after 3 seconds)
std::shared_ptr<CYIHTTPRequest> pRequest(new CYIHTTPRequest(CYIUrl("http://httpstat.us/200?sleep=3000"),
CYIHTTPRequest::Method::GET));
// always assume success callback for this example
pRequest->NotifyResponse.Connect([successCallback, str]() {
successCallback({ ToDynamic(str) });
});
CYIHTTPService::GetInstance()->EnqueueRequest(pRequest);
}
In JavaScript, use this method like this:
// native-module-sample.js
import { NativeModules } from 'react-native';
const SampleModule = NativeModules.NativeModuleSample;
// Call into C++
SampleModule.doSomethingWithCallback('Thanks for all the fish')
.then(response => {
console.log(response); // Success callback
}).catch( err => {
console.log(err); // Failure callback
});
To send events from C++ and listen for them in JavaScript, have your native module C++ class inherit from EventEmitterModule
and use the EmitEvent()
function.
From JavaScript, receive events from your module by wrapping it in or extending it from NativeEventEmitter
and then adding a listener for your event by calling the addListener()
function.
The interface between the two is
Note Failure to extend your C++ class from EventEmitterModule or wrap your JavaScript module will result in signals not being sent across the bridge. For more information, see Facebook’s documentation.
The following example sends the string I/JavaScript: Event received: Test parameter.
every two seconds.
Note that in this case the YI_RN_MODULE
and YI_RN_INSTANTIATE_MODULE
macros added by the create command are updated to expose the fact that this module emits events.
// MyEvent.h
#include <youireact/modules/EventEmitter.h>
#include <utility/YiTimer.h>
class YI_RN_MODULE(MyEvent, yi::react::EventEmitterModule), public CYISignalHandler
{
public:
MyEvent();
YI_RN_EXPORT_NAME(MyEvent)
void SendEvent();
private:
CYITimer m_eventTimer;
};
// MyEvent.cpp
#include "MyEvent.h"
// The name specified here is the listener you connect to from JavaScript
static const std::string MY_NAMED_EVENT = "MyNamedEvent";
YI_RN_INSTANTIATE_MODULE(MyEvent, yi::react::EventEmitterModule);
MyEvent::MyEvent()
{
SetSupportedEvents
({
MY_NAMED_EVENT
});
m_eventTimer.SetSingleShot(false);
m_eventTimer.TimedOut.Connect(*this, &MyEvent::SendEvent);
m_eventTimer.Start(2000);
}
void MyEvent::SendEvent()
{
EmitEvent(MY_NAMED_EVENT, folly::dynamic::object("param", "Test parameter."));
}
Listen for the events in index.youi.js
:
import { Component } from 'react';
import { AppRegistry, NativeEventEmitter, NativeModules } from 'react-native';
const myEventEmitter = new NativeEventEmitter(NativeModules.MyEvent);
export default class YiReactApp extends Component {
componentDidMount() {
this.myEventSubscription = myEventEmitter.addListener("MyNamedEvent", payload =>
console.log("Event received: " + payload.param)
);
}
componentWillUnmount() {
this.myEventSubscription && this.myEventSubscription.remove();
}
render = () => null;
}
AppRegistry.registerComponent('YiReactApp', () => YiReactApp);
Running this application results in the following in the console log:
Event received: I/JavaScript: Event received: Test parameter.
Event received: I/JavaScript: Event received: Test parameter.
Event received: I/JavaScript: Event received: Test parameter.
Event received: I/JavaScript: Event received: Test parameter.
...
If you need to inline a method rather than specify it in your implementation (.cpp) file, you must use the YI_RN_REGISTER_EXPORT_METHOD
macro in the associated .cpp file.
// MyModule.h
// Implementing a method inline
class YI_RN_MODULE(MyModule)
{
public:
YI_RN_EXPORT_METHOD(myInlineMethod)()
{
// method body goes here
}
};
// MyModule.cpp
#include "MyModule.h"
YI_RN_REGISTER_EXPORT_METHOD(MyModule, myInlineMethod);
Parameters passed from JavaScript are converted to a folly::dynamic
array where each element is the left-to-right parameter from the JavaScript call.
The array is made up of other folly::dynamic
s that may be strings, numbers, objects, arrays, and so on.
We suggest you define and document your method’s parameter expectations, as you may receive anything from JavaScript.
When defining your native module method, it can look like any of the following, depending on the type of parameters it expects:
// method with no parameters
YI_RN_EXPORT_METHOD(doSomething)();
// method with n parameters
YI_RN_EXPORT_METHOD(doSomething)(folly::dynamic);
// method with n parameters and one callback
YI_RN_EXPORT_METHOD(doSomething)(folly::dynamic, Callback);
// method with n parameters and two callbacks
YI_RN_EXPORT_METHOD(doSomething)(folly::dynamic, Callback, Callback);
If you’d prefer not to use folly::dynamic
, you can specify your expected parameters in the function signature.
The NativeModule class tries to convert the array of folly::dynamic
s from JavaScript into the expected arguments of your C++ method.
For example, if using the folly::dynamic
mechanism, the following JavaScript reference to a native module method results in arguments that your C++ class needs to extract and convert to the right types.
MyModule.makeNamed3dPoint("Earth", 1.0, 2.5, 3.77);
However, you could alternately define your method as shown below to have You.i Platform attempt to expand the args dynamic array into the specified types, in the expected order.
// alternate definition
YI_RN_EXPORT_METHOD(doSomething)(CYIString name, float x, float y, float z);
// if any parameter is optional, indicate
YI_RN_EXPORT_METHOD(doSomething)(CYIString name, float x, float y, folly::Optional<float> z);
When using this alternate definition method, the OnBadArgumentsPassedToMethod
virtual function can be overridden to manage cases where your method doesn’t get the parameters it expects.
For example, your application might respond by re-attempting a call, implementing an error handler, or alerting the user of a problem.
void NativeModuleBase::OnBadArgumentsPassedToMethod(
std::string methodName, // The name of the method that was attempted
folly::dynamic originalArgs, // The original folly::dynamic array containing the args
std::vector<FollyDynamicArgsConversionReport> reports // Failure reports for each argument conversion attempt
);
The reports
vector contains the same number of elements as the number of expected parameters.
Each report has information about the conversion attempted, some of which may have been successful.
Note that by default, NativeModuleBase
logs a diagnostic warning message.
Suppose you want to subclass a module to inherit all of the base’s methods and override some as follows:
// Declare a subclass native module called MySubclassModule
// derived from MyModule.
class YI_RN_MODULE(MySubclassModule, MyModule)
{
};
The first parameter to the YI_RN_MODULE
macro is the name of your new module.
Subsequent parameters are the base class(es).
Multiple inheritance is possible, but each base class must also be defined with a YI_RN_MODULE*()
macro.
The inheritance access level of base classes is public.
The same set of parameters is also needed for the YI_RN_INSTANTIATE_MODULE
macro in the class implementation:
// MyModule.cpp
YI_RN_INSTANTIATE_MODULE(MySubclassModule, MyModule);
Override a method by re-declaring it in the subclass, as normal. Exported methods are always marked virtual, so you can even use the override specifier to mark the method as an override.
// Overriding a method in a subclass
class YI_RN_MODULE(MySubclassModule, MyModule)
{
public:
YI_RN_EXPORT_METHOD(doSomething)() override;
};
To bridge Swift code to You.i React Native, you need to make various CMake changes. You can then bridge using Objective-C++ with You.i React Native native module macros. Do the following steps:
By default, build system CMake file is configured to compile only C, C++, and Objective C. You need to manually add Swift to the CMake file’s project language settings:
CMakelists.txt
located at <MyApp>/youi
.project(${YI_PROJECT_NAME} LANGUAGES C CXX)
Replace it with the following:
project(${YI_PROJECT_NAME} LANGUAGES C CXX)
if(IOS OR TVOS OR OSX)
enable_language(Swift)
endif()
Note You can also use the CheckLanguage CMake module to check and see if Swift is supported rather than checking for iOS, tvOS, or macOS.
By default, the CMakeLists.txt
file uses target_compile_options()
and check_cxx_compile_flags()
to send C++ flags to the compiler.
These flags are invalid for Swift compiler, swiftc
.
You need modify CMakeLists.txt
to allow for Swift flags.
Do the following steps:
Locate the following lines in the CMakeLists.txt
file:
if(MSVC)
target_compile_options(${PROJECT_NAME}
PRIVATE $<$<BOOL:${YI_TREAT_WARNINGS_AS_ERRORS}>:/WX>
)
else()
target_compile_options(${PROJECT_NAME}
PRIVATE -Wall
-Wextra
-Wno-float-equal
-Wpointer-arith
-Wwrite-strings
-Wunused-variable
-Wno-unused-parameter # for JSExecutor header
PRIVATE -Wno-unused-result -Wno-unused-function
PRIVATE $<$<BOOL:${YI_TREAT_WARNINGS_AS_ERRORS}>:-Werror>
)
endif()
Replace them with the following:
include(Modules/YiConfigureWarningsAsErrors)
yi_configure_warnings_as_errors(TARGET ${PROJECT_NAME})
include(Modules/YiConfigureCompileOptions)
yi_configure_compile_options(PROJECT_TARGET ${PROJECT_NAME} EXTRA_FLAGS -Wno-unused-parameter)
Do the following steps to set more properties in CMake required by Swift code:
Create a new file called BridgingHeader.h
in your src
directory:
touch BridgingHeader.h
.
Tip While empty for this example, you can use BridgingHeader.h
to expose Objective-C files to Swift.
The name and location aren’t important; just adjust the following steps accordingly.
Add the following in CMakelists.txt
:
if(IOS OR TVOS OR OSX)
set_target_properties(${PROJECT_NAME} PROPERTIES
XCODE_ATTRIBUTE_SWIFT_OBJC_BRIDGING_HEADER "${CMAKE_CURRENT_SOURCE_DIR}/src/BridgingHeader.h"
XCODE_ATTRIBUTE_DEFINES_MODULE "YES"
XCODE_ATTRIBUTE_SWIFT_VERSION "4.2")
endif()
The previous code does the following:
BridgingHeader.h
as the bridging header file in Xcode."4.2"
is appropriate for Xcode version 10.1.To add the Swift and Objective-C++ files to the list of compiled files, modify SourceList.cmake
.
Update the following text in the file:
file(GLOB_RECURSE YI_PROJECT_SOURCE "src/*.cpp")
To the following:
file(GLOB_RECURSE _COMMON_SOURCE "src/*.cpp")
file(GLOB_RECURSE _SWIFT_SOURCE "src/.swift" "src/.mm")
set(_PLATFORM_OSX_SOURCE
... # OSX specific source
${_SWIFT_SOURCE}
)
set(YI_PLATFORM_SOURCE
${_COMMON_SOURCE}
${_PLATFORM_${YI_PLATFORM_UPPER}_SOURCE}
)
The previous steps dealt with setting up. This step deals with writing files for execution. The following procedure shows you how to test your setup by writing the files of simple example where the Swift code does a print out.
Add the following basic Swift code in the src
folder in a new file called Test.swift
:
import Foundation
class TestSwift : NSObject {
@objc class func printHello() {
print("Hello from Swift");
}
}
The @objc
attribute marks the function to expose to Objective-C.
Write the following Objective-C++ code in the src
folder.
This Objective-C++ code exposes the Swift code as a JavaScript module:
Write the TestBridge.h
file.
#ifndef _TEST_BRIDGE_H_
#define _TEST_BRIDGE_H_
#include "youireact/NativeModule.h"
class YI_RN_MODULE(TestBridgeModule)
{
public:
YI_RN_EXPORT_NAME(TestBridge);
YI_RN_EXPORT_METHOD(printHello)();
};
#endif // _TEST_BRIDGE_H_
Write the TestBridge.mm
file.
#include "TestBridge.h"
#import <SwiftTest-Swift.h>
YI_RN_INSTANTIATE_MODULE(TestBridgeModule);
YI_RN_DEFINE_EXPORT_METHOD(TestBridgeModule, printHello)()
{
[TestSwift printHello];
}
Note that SwiftTest-Swift.h
is the name of the automatically generated file, and its name follows the pattern of <CMake Target Name>-Swift.h
.
This is typically set with the variable YI_PROJECT_NAME
, which in this example is set to SwiftTest
.
Register the new module in the app’s UserInit
function.
Replace the last line in the app’s UserInit
function with the following:
bool bSuccess = PlatformApp::UserInit();
GetBridge().AddModule<TestBridgeModule>();
return bSuccess;
Run the test by invoking the function from the JavaScript as following:
import { NativeModules } from 'react-native';
...
componentDidMount() {
NativeModules.TestBridge.printHello();
}
Your build process can bring in dependencies from gradle
and maven
, as well as local .jar
or .aar
libraries, if you use the module system to bring in your own module definitions with your own build.gradle.in
template (instead of the default build.gradle.in
template).
With the module system, you have a file structure as follows:
<project>/
<project>/youi
<project>/youi/android/modules
<project>/youi/android/modules/<project_name>/
The
AndroidManifest.xml.in
build.gradle.in
YiModuleDefinition.cmake
res/
This content is copied into your Android project during project generation. If you do not have the previous structure and content, boilerplate versions of it are copied over from the You.i C++.
In the build.gradle.in
for your app, you should see a dependency section such as the following:
dependencies {
// Include local JAR files
implementation fileTree(dir: "libs", include: ["*.jar"])
@YI_MODULE_DEPENDENCIES@
@YI_YOUIENGINE_DEPENDENCY@
implementation 'com.google.android.exoplayer:exoplayer:2.9.6'
implementation 'com.google.android.exoplayer:extension-mediasession:2.9.6'
implementation 'com.google.android.gms:play-services-ads:15.0.1'
// Load unit test support libraries
testImplementation "junit:junit:4.12"
}
You can alter this entry to specify a new directory that contains libraries specific to your project or app; for example:
implementation fileTree(dir: "@THIRDPARTY_LIBS@", include: ["*.jar"])
Define the variable in your app’s module definition file (YiModuleDefinition.cmake
):
yi_define_module(${YI_PROJECT_NAME}
TYPE APPLICATION
PROJECT_DIR ${_PROJECT_DIR}
VARIABLES
...
"THIRDPARTY_LIBS=${CMAKE_CURRENT_SOURCE_DIR}/thirdparty/android"
...
)
Bring in a new .jar
artifact by placing it in the directory defined in your YiModuleDefinition.cmake
file.
Once you make your changes, the generate and build processes for our app do the following:
You may want to include a third party static library (such as an analytics library) in your native module. Start by creating a place in your project to hold the library file and associated headers.
youi
folder, create a sub-folder called thirdparty
(or another name, if you prefer).Inside the library folder, create an include
folder to hold the header files for the library.
Your folder tree looks like this: *appname* > youi > thirdparty > *libname* >include
.
*appname* > youi > thirdparty > *libname*
.Copy the associated header files into *appname* > youi > thirdparty > *libname* > include
.
Since libraries are specific to a particular target platform, you’ll need platform-specific instructions for each platform that uses the library.
youi
folder create the folder cmake/Platform/
.YiAndroid.cmake
, YiIos.cmake
, YiLinux.cmake
, YiWin64.cmake
, YiOsx.cmake
, YiPs4.cmake
, YiTizenNacl.cmake
, YiTvos.cmake
, YiUwp.cmake
, WebosCommon.cmake
, YiWebos3.cmake
, or YiWebos4.cmake
.Put the following code in that file, updating the first line to correctly identify the file by the same name in You.i Platform and the target_include
and target_link_libraries
line to specify the location of header and library files.
include(${YouiEngine_DIR}/cmake/Platform/YiOsx.cmake)
macro(yi_configure_platform)
cmake_parse_arguments(_ARGS "" "PROJECT_TARGET" "" ${ARGN})
if(NOT _ARGS_PROJECT_TARGET)
message(FATAL_ERROR "'yi_configure_platform' requires the PROJECT_TARGET argument be set")
endif()
_yi_configure_platform(PROJECT_TARGET ${_ARGS_PROJECT_TARGET})
target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_LIST_DIR}/thirdparty/hello/include)
target_link_libraries(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_LIST_DIR}/thirdparty/hello/libHello.a)
endmacro(yi_configure_platform)
All that’s left is to either wrap your library in a native module, or to initialize it in the App.cpp
file.
Which one you’ll use depends on whether or not the library exposes methods you want accessible from JSX.
If you wrap it in a native module, be sure to include those source and header files in the SourceList.cmake
file (as described above) and rebuild your application.
For example, consider a library called Hello
that is specific to macOS (OSX).
A simplified youi
folder for including this library is as follows:
The header file for our example library, Hello.h
is defined as:
#ifndef Hello_h
#define Hello_h
#include <string>
std::string getHello();
#endif
It’s likely anyone using this library would want to wrap getHello()
in a native module to make it accessible from JSX.
Since this library is specific to OSX, it’s important to only import it and use it when running on the OSX platform.
It’s equally important to ensure that the code has an alternate path when run on another platform where the library isn’t available.
#include "myLibraryWrapperModule.h"
#include <youireact/NativeModuleRegistry.h>
#if defined(YI_OSX)
#include <Hello.h>
#endif
YI_RN_INSTANTIATE_MODULE(MyModuleWithLibrary);
YI_RN_REGISTER_MODULE(MyModuleWithLibrary);
YI_RN_DEFINE_EXPORT_CONSTANT(MyModuleWithLibrary, name)
{
#if defined(YI_OSX)
return getHello();
#endif
return "MyModuleWithLibrary no supported platform defined.";
}
Now, JSX files in this project can import NativeModules
from react-native
to access the name
method from MyModuleWithLibrary
.
const { MyModuleWithLibrary } = NativeModules;
export default class YiReactApp extends Component {
render() {
return (
<View style={styles.mainContainer}>
<View style={styles.bodyContainer}>
<Text style={styles.headlineText}>{NativeModules.MyModuleWithLibrary.name}</Text>
</View>
</View>
);
}
}
This section describes the macros you can use while creating your You.i React Native native module.
.h
fileMacro | Description |
---|---|
YI_RN_MODULE(MyModule) | Identifies this class as a native module. |
YI_RN_EXPORT_NAME(ModuleName) | Exposes the native module name. |
YI_RN_EXPORT_METHOD(Method) | Exposes native module method to JavaScript. |
YI_RN_EXPORT_CONSTANT(ConstantName) | Exposes native module constant method to JavaScript. |
.cpp
fileMacro | Description |
---|---|
YI_RN_INSTANTIATE_MODULE(MyModule) | Instantiates your native module. Be sure to use the exact parameters as passed to the YI_RN_MODULE macro. |
YI_RN_REGISTER_MODULE(MyModule); | Registers the native module with You.i Platform. |
YI_RN_DEFINE_EXPORT_METHOD(Class, Method) | Implements an exposed method. |
YI_RN_REGISTER_EXPORT_METHOD(Class, Method) | Implements an exposed inline method |
YI_RN_DEFINE_EXPORT_CONSTANT(Class, Constant) | Implements an exposed constant method. |
YI_RN_REGISTER_EXPORT_CONSTANT(Class, Constant) | Implements an exposed inline constant method. |