How to create React Native Wrappers for Native SDKs

January 21, 2019
9 min Read

Why do we need this?

React native is an open-source framework for cross-platform mobile development that lets us build mobile apps using Javascript. It works by providing a bridge that facilitates the communication between Javascript and native modules.

With a community that grows day by day, more and more libraries are created, so chances are you'll find pretty much everything you need during your development process. But what if you need to use some native SDKs in your React Native app? This was one of our challenges here at MCRO. We developed a crypto trading app (DSTOQ Mobile, see https://mcro.tech/our-work) and we were constrained to use Onfido for the Know Your Customer process, a very well known process in the crypto community.

While Onfido already had their Android and iOS SDKs, there was no React Native wrapper counterpart. They do have an example on how to use the SDKs with React Native, but that means getting into native code. Even though that's not a hard task for a developer with native development background, it would be much easier to have a wrapper for these SDKs and handle everything on the Javascript side. Plus, if anyone else needs to integrate Onfido into their React Native app this wrapper would greatly ease up the work for them.

Onfido SDKs offer a lot of customization options for tailoring the entire KYC process to everyone's needs, but it lacks support for opting out different verification mechanisms. In our case, we only needed 3 out of 4 document types. There are 4 document type checks (Passport, Driver's Licence, National Identity Card and Residence Permit Card) that the service provides. With their current implementation you could either have all 4 options in your app, or you had to start the SDK using a specific document type. We'll cover later in the article our solution for this.

Create the library

The easiest way to get started on writing a new library is to use react-native-create-library. It creates a boilerplate with everything you need to get you started on writing a RN library. Install it globally using:

npm install -g react-native-create-library

react-native-create-library --package-identifier com.mcro.rnonfido --platforms android,ios onfido-sdk

We opt in only for android and ios since not only there's no windows phone SDK, but it is also pointless since Microsoft announced it's ending support for it. Next, we need to do a bit of renaming. Running the above command produces the following:

react native wrapper create library

The reason we named the library onfido-sdk and not react-native-onfido-sdk is because react-native-create-library generates all files using RN prefix and it would've been redundant to have files named such as RNReactNativeOnfidoSdk. To rename our directory, we just need to manually run:

mv onfido-sdk react-native-onfido-sdk

We're all set now, time to hook up the SDKs. Before we go into details, let's recap the challenges that we're trying to tackle:

1. Integrate native SDKs in out wrapper library

2. Expose methods from Native to Javascript side

3. Extend the functionality of these SDKs. Because we need a separate screen for having custom document types, we need to implement native screens for each platform. We could've implemented this screen in Javascript, but in that case the developer would be responsible to render it from React Native depending on the navigation library used and manually handle all selections.

4. Combine the native screens (newly added ones and those coming from Onfido SDK) with React Native

iOS

There are two ways of including a library in a project:

  1. manually, by copying the sdk in the project
  2. using a dependency manager

Advantages of the second option are obvious, but we won't go into details in this article. Onfido SDK is available through Cocoapods, so we'll leverage the capabilities of .podspec file, which react-native-create-library created for us, to specify the Onfido dependency. We can update this file to load the details in a nicer fashion, reading them from our package.json:

require 'json'

package = JSON.parse(File.read(File.join(__dir__, 'package.json')))

Pod::Spec.new do |s|
  s.name           = 'react-native-onfido-sdk'
  s.version        = package['version']
  s.summary        = package['description']
  s.description    = package['description']
  s.license        = package['license']
  s.author         = package['author']
  s.homepage       = package['homepage']
  s.source         = ...

  s.requires_arc   = true
  s.platform       = :ios, '11.0'

  s.preserve_paths = 'LICENSE', 'README.md', 'package.json', 'index.js'
  s.source_files   = './*.{h,m}'

  s.dependency 'React'
  s.dependency 'Onfido'
end

At this point, we're pretty much set with our dependencies, we can start writing native modules for exposing what we need to Javascript. The header and the implementation are already created for us (RNOnfidoSdk.h and RNOnfidoSdk.m). React Native documentation covers this topic in detail (https://facebook.github.io/react-native/docs/native-modules-ios). In our case, we needed to expose a method that would present the Onfido flow, so we implemented the following RCTEXPORTMETHOD:

RCT_EXPORT_METHOD(startSDK:(id)json successCallback:(RCTResponseSenderBlock)successCallback errorCallback:(RCTResponseErrorBlock)errorCallback) {
    RNOnfidoSdk *sdk = [[RNOnfidoSdk alloc] initWithParams:json successCallback:successCallback errorCallback:errorCallback];
    [sdk run];
}

After we're done with the implementation of the exposed methods and our custom document types view controller, let's see what options we have for presenting native view controllers in the React Native app. On iOS there are two ways for navigating to a new screen: modally or by pushing it in a UINavigationController. If we look into AppDelegate.m of any React Native app, we'll see that we have a UIViewController set as root for the window object. By default, all view controllers have the ability to present some other view controller, but we can push a view controller only in the context of a UINavigationController. With that being said, we're kind of constrained to present the entire navigation stack to the Onfido flow, unless we make some changes to the app that uses our library. We can update AppDelegate.m and change the rootViewController to be a UINavigationController:

UIViewController *rootViewController = [UIViewController new];
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:rootViewController];
self.window.rootViewController = navigationController;

Of course this will add a navigation bar in the app, but we can easily hide it.

This way, we can update our exposed method that launches the SDK to have a new parameter, that will be iOS only, for letting the users of the library choose whether they want to present the flow as a modal stack, or push it. We can add a helper method in our library for launching the flow like this:

- (void) displayOnfidoFlow: (UIViewController *) viewController {
    // Get view controller on which to present the flow
    UIWindow *window = [[UIApplication sharedApplication] keyWindow];
    self->_rootViewController = (UINavigationController *)window.rootViewController;
    
    NSDictionary *dictionary = [RCTConvert NSDictionary:self->_params];
    BOOL presentModally = dictionary[@"presentModally"];
    if (presentModally) {
        [self->_rootViewController presentViewController:viewController animated:true completion:nil];
    } else {
        [(UINavigationController *) self->_rootViewController pushViewController:viewController animated:true];
    }
}

Another challenge that we had was to bundle the assets in the library. In order to do this you need to create a new resource bundle by adding a new bundle. We named it Resources, you can name it anyway you like:

react native wrapper resources

Next, set the Base SDK iOS for this bundle, since we'll build our library for iOS:

base sdk ios

Next step is to add the new bundle as a target dependency for your initial target:

target dependency bundle

Now if you want to add some .xcassets or .xib files you just need to make sure they have the bundle as target dependency.

The last step in bundling the assets is to update the same .podspec file with the path for this bundle:

require 'json'

package = JSON.parse(File.read(File.join(__dir__, 'package.json')))

Pod::Spec.new do |s|
  s.name           = 'react-native-onfido-sdk'
  s.version        = package['version']
  s.summary        = package['description']
  s.description    = package['description']
  s.license        = package['license']
  s.author         = package['author']
  s.homepage       = package['homepage']
  s.source         = ...

  s.requires_arc   = true
  s.platform       = :ios, '11.0'

  s.preserve_paths = 'LICENSE', 'README.md', 'package.json', 'index.js'
  s.source_files   = 'ios/*.{h,m}'

  # ADD THIS
  s.resource_bundles = {
    'Resources' => ['./*.xcassets']
  }

  s.dependency 'React'
  s.dependency 'Onfido'
end

Now these assets can be accesed both inside and ourside of the library like this:

- (UIImage *) getImageNamed: (NSString *)imageName {
    NSString *resourcePath = [NSBundle.mainBundle pathForResource:@"Resources" ofType:@"bundle"];
    NSBundle *resourcesBundle = [NSBundle bundleWithPath:resourcePath];
    return [UIImage imageNamed:imageName inBundle:resourcesBundle compatibleWithTraitCollection:nil];
}

In this case imageName can be any image that we have in our assets catalog.

Android

While for iOS we used Cocoapods for getting the Onfido SDK in our library, we can do the same for Android using Gradle Dependency Manager. Open android/build.gradle and add the following in dependencies:

dependencies {
    implementation 'com.facebook.react:react-native:+'

    # ADD THIS
    implementation 'com.onfido.sdk.capture:onfido-capture-sdk:+'
}

If we're running gradle sync now, it will throw an error and that is because com.onfido.sdk.capture:onfido-capture-sdk has not been approved yet to be included in JCenter; we need to instruct Gradle to search for the package on Bintray. In the same file, update repositories configuration:

repositories {
  ...
  maven {
    url  "https://dl.bintray.com/onfido/maven"
  }
}

We've managed to integrate the SDK in our library, next step is to write the modules that will be accessed over the bridge. More info about this in React Native documentation (https://facebook.github.io/react-native/docs/native-modules-android)

When it comes to extend the SDK functionality with our custom document types screen, things are less complicated on Android than on iOS. For our particular case, we just need to create a new activity and depending on the parameters that are passed in from Javascript side, we'll know which activity to start.

Let's take a look over our exposed method:

@ReactMethod
  public void startSDK(ReadableMap params, Callback successCallback, Callback errorCallback) {
    Activity currentActivity = getCurrentActivity();
    mSuccessCallback = successCallback;
    mErrorCallback = errorCallback;
    mParams = params;

    if (currentActivity == null) {
      mErrorCallback.invoke(E_ACTIVITY_DOES_NOT_EXIST);
      return;
    }
    HashMap paramsMap = params.toHashMap();

    if (this.isCustomFlow(paramsMap)) { // if user wants custom document type checks
      Intent intent = new Intent(currentActivity, OnfidoCustomDocumentTypesActivity.class);
      int[] documentTypes = this.getDocumentTypes(paramsMap);
      intent.putExtra("documentTypes", documentTypes);
      currentActivity.startActivityForResult(intent, REQUEST_CODE_DOCUMENT_TYPE); // start Onfido SDK in onActivityResult
    } else {
      OnfidoConfig onfidoConfig = this.getOnfidoConfig(paramsMap);
      client.startActivityForResult(currentActivity, REQUEST_CODE_ONFIDO, onfidoConfig);
    }
  }

Final thoughts

Hopefully, this will give you a starting point on how to create a React Native wrapper for existing native SDKs. We haven't covered how to actually write and expose methods over the bridge, since that is very well covered in React Native documentation, but instead we focused on integrating the SDKs in our library and extending the functionality to our needs.

You can see this example fully implemented on our Github, as well as an example that you can check as a demo: https://github.com/MCROEngineering/react-native-onfido-sdk

Featured Articles.