This is an example of using Native iOS (Swift) and Android (Java) code together with a React Native Application using Native Modules.
More info about Native Modules: iOS Android
Here we have a simple screen that has a counter. It starts with an initial value, and it is possible to increment/decrement the value by using buttons, or set it directly using the input.
React Native side only makes calls to the Native side for making changes to the value, it is Native (i.e. SDK) side which actually does the operations.
For instance, in order to increment the value
SDK.increaseValue()
method is used. This call corresponds to following in iOS platform (SDK/Counter.swift):
open func increaseCount() {
self.countValue += 1
}
and following in Android platform (sdk/Counter.java):
public void increaseCount() {
this.countValue++;
}
Note: React Native works cross-platform. This means that when developing platform specific SDK, Android and iOS should follow the same rules, such as method names and parameters should be exactly the same, otherwise maintenance in RN side will be cumbersome.
Feting initial count value is a good example for this. Check the following iOS example:
// SDK/Counter.swift
open func getCountValue() -> Int {
return self.countValue
}
// Bridges/SDKModule.swift
var counterInstance = Counter()
...
@objc
open func getCountValue(_ resolve: @escaping RCTPromiseResolveBlock, rejecter reject: @escaping RCTPromiseRejectBlock) {
resolve(self.counterInstance.getCountValue());
}
...
Here bridge file calls SDK's getCountValue
method and returns to RN asynchronously.
The same applies for Android as well:
// sdk/Counter.java
public int getCountValue() {
return this.countValue;
}
// Bridge/SDKBridgeModule.java
private Counter counterInstance = new Counter();
...
@ReactMethod
public void getCountValue(final Promise promise) {
promise.resolve(counterInstance.getCountValue());
}
...
In the end, RN side only does the following:
import {NativeModules} from 'react-native';
const {SDKModule} = NativeModules;
class SDK{
public static getCountValue(): Promise<number> {
return SDKModule.getCountValue();
}
...
}
Annotations @objc
and @ReactMethod
are needed for bridging, but SDK side
does not care about how data is handled by bridges. SDK side focuses only
on operating on its own work, which is in this case managing the count value.
Incrementing, decrementing and setting the count value are all examples for this but let's examine setting the count value since we will have chance to see sending parameters as well.
iOS example:
// SDK/Counter.swift
open func setCountValue(_ value: Int) {
self.countValue = value
}
// Bridges/SDKModule.swift
var counterInstance = Counter()
...
@objc
open func setCountValue(_ value: Int) {
self.counterInstance.setCountValue(value)
sendCountValue()
}
...
Android example:
// sdk/Counter.java
public void setCountValue(int countValue) {
this.countValue = countValue;
}
// Bridge/SDKBridgeModule.java
private Counter counterInstance = new Counter();
...
@ReactMethod
public void setCountValue(int value) {
this.counterInstance.setCountValue(value);
this.sendCountValue();
}
...
And RN side calls as following:
import {NativeModules} from 'react-native';
const {SDKModule} = NativeModules;
class SDK{
public static setCountValue(countValue: number) {
SDKModule.setCountValue(countValue);
}
...
}
React Native side listens for events, This way, whenever something happens native side and RN needs to know, events are fired and since RN listens to them, it will receive necessary data.
Count value is always (except first run) changed in result of event called onNewCountValue
being fired.
Let's see iOS side:
// Bridges/SDKModule.swift
var counterInstance = Counter()
@objc
open override func supportedEvents() -> [String] {
return ["onNewCountValue"] // This is only needed for iOS
}
...
open func sendCountValue() {
let body = ["countValue" : self.counterInstance.countValue]
self.sendEvent(withName: "onNewCountValue", body: body)
}
...
Android side:
// Bridge/SDKBridgeModule.java
private Counter counterInstance = new Counter();
...
private void sendCountValue() {
WritableMap body = Arguments.createMap();
body.putInt("countValue", counterInstance.getCountValue());
this.getEventEmitter().emit("onNewCountValue", body);
...
Finally, RN side:
...
const nativeEventListener = Platform.select({
ios: () => new NativeEventEmitter(SDKModule),
android: () => DeviceEventEmitter,
},
)();
const sub = nativeEventListener.addListener('onNewCountValue', body => {
dispatch(setCountAction(body.countValue));
});
...
Examples above also shows parameter passing.
Native components are easily used in RN apps. Although there is no example in this repo, you can check out official guidelines.
React Native are native apps too. They compile and run the same way Native apps do. Only difference is there will be a compiled js bundle added to the app's files.
In Android, one can use Android Studio's green run button in the below image:
In iOS case, one can use Xcode's run button in the below image:
Only thing to keep in mind is to make sure RN code is bundled.