ReactNativeExport
To export kmp into react native we need this annotation to be added to the Client Manager,
The annotation will wrap the sdk native output (AAR/Framework) and create a ReactNative Module by generating the following files
- Android - Client Module
- iOS - Swift Module
- iOS - Objective-C Bridge
- JS - Index
Usage
Add the annotation to the Client Manger
@JsExport
@ReactNativeExport
class MySdkClientManager private constructor(
databaseDriverFactory: MySdkClientDatabaseDriverFactory? = null,
private val builder: Builder,
config: Config? = null
) {
fun getUsers() : List<Users>{
return Task.execute {
repo.getUsers()
}
}
}
This will interpolate the manger functions into android, ios and JS react native module
Android - Client Module
For the above example a file will be generated and updated under the ReactNative Folder of the kmp structure,
It will look something like this
{root}/react-native-my-sdk/android/src/main/java/com/example/my_sdk/MySdkModule.g.kt
@OptIn(ExperimentalJsExport::class)
class MySdkClientModule(reactContext: ReactApplicationContext) :
ReactContextBaseJavaModule(reactContext) {
private val manager = MySdkClientManager.getInstance()
override fun getName(): String {
return NAME
}
@ReactMethod
fun getUsers(reason: String, promise: Promise) {
try {
promise.resolve(User.toJson(manager.getUsers()))
} catch (e:Exception){
promise.reject(e)
}
}
companion object {
const val NAME = "MySdkClient"
}
}
.g.kt
Notice the file had
.g.kt
extension which indicate it was generated, it is ignored by default but you can committed anyway to keep track of the changes,
Also you cant to eject from the code generation and add your own logic to the module.
The generated code utilise the @ReactMethod
annotation to expose the logic to react native
iOS - Swift Module
For the above example a file will be generated and updated under the ReactNative Folder of the kmp structure,
It will look something like this
{root}/react-native-my-sdk/ios/MySdkClient.swift
import MySdkClient
extension String: Error {
}
public class MySdkClientInstance {
public static var shared : MySdkClientManager? = nil
}
@objc(MySdkClient)
class MySdkClient: RCTEventEmitter {
private var hasListeners = false;
override func supportedEvents() -> [String]! {
return []
}
override func startObserving() {
hasListeners = true
}
override func stopObserving() {
hasListeners = false
}
override func sendEvent(withName name: String!, body: Any!) {
if (hasListeners) {
super.sendEvent(withName: name, body: body)
}
}
@objc(getUsers:withRejecter:)
func getUsers(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
if (MySdkClientInstance.shared == nil) {
reject("getUsers error", "MySdkClientManager was not initialized", "MySdkClientManager was not initialized")
} else {
resolve(Users.Companian().fromJson(array: MySdkClientInstance.shared!.getUsers()))
}
}
}
This is the swift wrapper of the sdk, but we still need to provide the object-c headers
iOS - Objective-C Header
We use RCT_EXTERN_METHOD
to expose the logic to react native
{root}/react-native-my-sdk/ios/MySdkClient.m
#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>
@interface RCT_EXTERN_MODULE(MySdkClient, RCTEventEmitter)
RCT_EXTERN_METHOD(supportedEvents)
RCT_EXTERN_METHOD(getUsers:
(RCTPromiseResolveBlock)resolve
withRejecter:(RCTPromiseRejectBlock)reject)
+ (BOOL)requiresMainQueueSetup
{
return NO;
}
@end
JS - Index
To be able to use the ReactNative Module we need the JS wrapper that will invoke the Android,iOS native modules
For the above example a file will be generated and updated under the ReactNative Folder of the kmp structure,
It will look something like this
{root}/react-native-my-sdk/src/index.tsx
import { NativeModules, Platform, NativeEventEmitter, EmitterSubscription } from 'react-native';
const LINKING_ERROR =
`The package 'react-native-mysdk-client' doesn't seem to be linked. Make sure: \n\n` +
Platform.select({ ios: "- You have run 'pod install'\n", default: '' }) +
'- You rebuilt the app after installing the package\n' +
'- You are not using Expo Go\n';
const MySdkModels = require('@example/my-sdk').com.example.my_sdk.models;
const MySdkClient = NativeModules.MySdkClient
? NativeModules.MySdkClient
: new Proxy(
{},
{
get() {
throw new Error(LINKING_ERROR);
},
}
);
// @ts-ignore
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const UsersFromJsonArray = MysdkModels.UsersFromJsonArray;
export function getUsers(): Promise<typeof Array<Users>> {
return new Promise<typeof Array<Users>>((resolve, reject) => {
MySdkClient.getUsers()
.then((data: string) => {
resolve(Users.Companian.FromJsonArray(Users.Companian,data));
})
.catch((e: any) => {
reject(e);
});
})
}
Disable ReactNative Export
if you don’t want the annotation to update the files while keeping the client manager class annotated, add the following in the module’s build.gradle.kts
teleresoKmp {
disableReactExport = true
}