React Native Vs. Native App Development: What to Choose in 2019

React Native Map components for iOS + Android

React Native Map components for iOS + Android

React Native Map components for iOS + Android.Contribute to react-native-community/react-native-maps development by creating an account on GitHub

React Native Map components for iOS + Android

Installation

See Installation Instructions.

See Setup Instructions for the Included Example Project.

Compatibility

Due to the rapid changes being made in the React Native ecosystem, we are not officially going to support this module on anything but the latest version of React Native. With that said, we will do our best to stay compatible with older versions as much that is practical, and the peer dependency of this requirement is set to "react-native": "*" explicitly for this reason. If you are using an older version of React Native with this module though, some features may be buggy.

Note about React requires

Since react-native 0.25.0, React should be required from node_modules. React Native versions from 0.18 should be working out of the box, for lower versions you should add react as a dependency in your package.json.

General Usage
source-js
import MapView from 'react-native-maps';

or

source-js
var MapView = require('react-native-maps');

This MapView component is built so that features on the map (such as Markers, Polygons, etc.) are specified as children of the MapView itself. This provides an intuitive and react-like API for declaratively controlling features on the map.

Rendering a Map with an initial region

MapView
source-js-jsx
  <MapView
    initialRegion={{
      latitude: 37.78825,
      longitude: -122.4324,
      latitudeDelta: 0.0922,
      longitudeDelta: 0.0421,
    }}
  />

Using a MapView while controlling the region as state

source-js-jsx
getInitialState() {
  return {
    region: {
      latitude: 37.78825,
      longitude: -122.4324,
      latitudeDelta: 0.0922,
      longitudeDelta: 0.0421,
    },
  };
}

onRegionChange(region) {
  this.setState({ region });
}

render() {
  return (
    <MapView
      region={this.state.region}
      onRegionChange={this.onRegionChange}
    />
  );
}

Rendering a list of markers on a map

source-js-jsx
import { Marker } from 'react-native-maps';

<MapView
  region={this.state.region}
  onRegionChange={this.onRegionChange}
>
  {this.state.markers.map(marker => (
    <Marker
      coordinate={marker.latlng}
      title={marker.title}
      description={marker.description}
    />
  ))}
</MapView>

Rendering a Marker with a custom view

source-js-jsx
<Marker coordinate={marker.latlng}>
  <MyCustomMarkerView {...marker} />
</Marker>

Rendering a Marker with a custom image

source-js-jsx
<Marker
  coordinate={marker.latlng}
  image={require('../assets/pin.png')}
/>

Rendering a custom Marker with a custom Callout

source-js-jsx
import { Callout } from 'react-native-maps';

<Marker coordinate={marker.latlng}>
  <MyCustomMarkerView {...marker} />
  <Callout>
    <MyCustomCalloutView {...marker} />
  </Callout>
</Marker>

Draggable Markers

source-js-jsx
<MapView initialRegion={...}>
  <Marker draggable
    coordinate={this.state.x}
    onDragEnd={(e) => this.setState({ x: e.nativeEvent.coordinate })}
  />
</MapView>

Using a custom Tile Overlay

Tile Overlay using tile server

source-js-jsx
import { UrlTile } from 'react-native-maps';

<MapView
  region={this.state.region}
  onRegionChange={this.onRegionChange}
>
  <UrlTile
    /**
     * The url template of the tile server. The patterns {x} {y} {z} will be replaced at runtime
     * For example, http://c.tile.openstreetmap.org/{z}/{x}/{y}.png
     */
    urlTemplate={this.state.urlTemplate}
    /**
     * The maximum zoom level for this tile overlay. Corresponds to the maximumZ setting in
     * MKTileOverlay. iOS only.
     */
    maximumZ={19}
    /**
     * flipY allows tiles with inverted y coordinates (origin at bottom left of map)
     * to be used. Its default value is false.
     */
    flipY={false}
  />
</MapView>

For Android: add the following line in your AndroidManifest.xml

text-xml
<uses-permission android:name="android.permission.INTERNET" />

For IOS: configure App Transport Security in your app

Tile Overlay using local tiles

Tiles can be stored locally within device using xyz tiling scheme and displayed as tile overlay as well. This is usefull especially for offline map usage when tiles are available for selected map region within device storage.

source-js-jsx
import { LocalTile } from 'react-native-maps';

<MapView
  region={this.state.region}
  onRegionChange={this.onRegionChange}
>
  <LocalTile
   /**
    * The path template of the locally stored tiles. The patterns {x} {y} {z} will be replaced at runtime
    * For example, /storage/emulated/0/mytiles/{z}/{x}/{y}.png
    */
   pathTemplate={this.state.pathTemplate}
   /**
    * The size of provided local tiles (usually 256 or 512).
    */
   tileSize={256}
  />
</MapView>

For Android: LocalTile is still just overlay over original map tiles. It means that if device is online, underlying tiles will be still downloaded. If original tiles download/display is not desirable set mapType to 'none'. For example:

<MapView
  mapType={Platform.OS == "android" ? "none" : "standard"}
>

See OSM Wiki for how to download tiles for offline usage.

Overlaying other components on the map

Place components you that wish to overlay MapView underneath the MapView closing tag. Absolutely position these elements.

source-js-jsx
render() {
  return (
    <MapView
      region={this.state.region}
    />
    <OverlayComponent
      style={{position: “absolute”, bottom: 50}}
    />
  );
}

Customizing the map style

Create the json object, or download a generated one from the google style generator.

source-js-jsx
// The generated json object
mapStyle = [ ... ]

render() {
  return (
    <MapView
      region={this.state.region}
      onRegionChange={this.onRegionChange}
      customMapStyle={mapStyle}
    />
  );
}

For iOS, in addition to providing the mapStyle you will need to do the following

source-js-jsx
import MapView, { PROVIDER_GOOGLE } from 'react-native-maps'

// ...

<MapView
  provider={PROVIDER_GOOGLE}
  customMapStyle={MapStyle}
>

Then add the AirGoogleMaps directory:

https://github.com/react-native-community/react-native-maps/blob/1e71a21f39e7b88554852951f773c731c94680c9/docs/installation.md#ios

An unofficial step-by-step guide is also available at https://gist.github.com/heron2014/e60fa003e9b117ce80d56bb1d5bfe9e0

Examples

To run examples:

source-shell
npm i
npm start

#Android
npm run run:android

#iOS
npm run build:ios
npm run run:ios

MapView Events

The <MapView /> component and its child components have several events that you can subscribe to. This example displays some of them in a log as a demonstration.

Tracking Region / Location

Programmatically Changing Region

One can change the mapview's position using refs and component methods, or by passing in an updated region prop. The component methods will allow one to animate to a given position like the native API could.

Changing the style of the map

Arbitrary React Views as Markers

Using the MapView with the Animated API

The <MapView /> component can be made to work with the Animated API, having the entire region prop be declared as an animated value. This allows one to animate the zoom and position of the MapView along with other gestures, giving a nice feel.

Further, Marker views can use the animated API to enhance the effect.

Issue: Since android needs to render its marker views as a bitmap, the animations APIs may not be compatible with the Marker views. Not sure if this can be worked around yet or not.

Markers' coordinates can also be animated, as shown in this example:

Polygon Creator

Other Overlays

So far, <Circle />, <Polygon />, and <Polyline /> are available to pass in as children to the <MapView /> component.

Gradient Polylines (iOS MapKit only)

Gradient polylines can be created using the strokeColors prop of the <Polyline> component.

Default Markers

Default markers will be rendered unless a custom marker is specified. One can optionally adjust the color of the default marker by using the pinColor prop.

Custom Callouts

Callouts to markers can be completely arbitrary react views, similar to markers. As a result, they can be interacted with like any other view.

Additionally, you can fall back to the standard behavior of just having a title/description through the <Marker />'s title and description props.

Custom callout views can be the entire tooltip bubble, or just the content inside of the system default bubble.

To handle press on specific subview of callout use <CalloutSubview /> with onPress. See Callouts.js example.

Image-based Markers

Markers can be customized by just using images, and specified using the image prop.

Draggable Markers

Markers are draggable, and emit continuous drag events to update other UI during drags.

Lite Mode ( Android )

Enable lite mode on Android with liteMode prop. Ideal when having multiple maps in a View or ScrollView.

On Poi Click (Google Maps Only)

Poi are clickable, you can catch the event to get its information (usually to get the full detail from Google Place using the placeId).

Animated Region

The MapView can accept an AnimatedRegion value as its region prop. This allows you to utilize the Animated API to control the map's center and zoom.

source-js-jsx
import MapView, { AnimatedRegion, Animated } from 'react-native-maps';

getInitialState() {
  return {
    region: new AnimatedRegion({
      latitude: LATITUDE,
      longitude: LONGITUDE,
      latitudeDelta: LATITUDE_DELTA,
      longitudeDelta: LONGITUDE_DELTA,
    }),
  };
}

onRegionChange(region) {
  this.state.region.setValue(region);
}

render() {
  return (
    <Animated
      region={this.state.region}
      onRegionChange={this.onRegionChange}
    />
  );
}

Animated Marker Position

Markers can also accept an AnimatedRegion value as a coordinate.

source-js-jsx
import Mapview, { AnimatedRegion, Marker } from 'react-native-maps';

getInitialState() {
  return {
    coordinate: new AnimatedRegion({
      latitude: LATITUDE,
      longitude: LONGITUDE,
    }),
  };
}

componentWillReceiveProps(nextProps) {
  const duration = 500

  if (this.props.coordinate !== nextProps.coordinate) {
    if (Platform.OS === 'android') {
      if (this.marker) {
        this.marker._component.animateMarkerToCoordinate(
          nextProps.coordinate,
          duration
        );
      }
    } else {
      this.state.coordinate.timing({
        ...nextProps.coordinate,
        duration
      }).start();
    }
  }
}

render() {
  return (
    <MapView initialRegion={...}>
      <MapView.Marker.Animated
        ref={marker => { this.marker = marker }}
        coordinate={this.state.coordinate}
      />
    </MapView>
  );
}

If you need a smoother animation to move the marker on Android, you can modify the previous example:

source-js-jsx
// ...

componentWillReceiveProps(nextProps) {
  const duration = 500

  if (this.props.coordinate !== nextProps.coordinate) {
    if (Platform.OS === 'android') {
      if (this.marker) {
        this.marker._component.animateMarkerToCoordinate(
          nextProps.coordinate,
          duration
        );
      }
    } else {
      this.state.coordinate.timing({
        ...nextProps.coordinate,
        duration
      }).start();
    }
  }
}

render() {
  return (
    <MapView initialRegion={...}>
      <Marker.Animated
        ref={marker => { this.marker = marker }}
        coordinate={this.state.coordinate}
      />
    </MapView>
  );
}

Take Snapshot of map

source-js-jsx
import MapView, { Marker } from 'react-native-maps';

getInitialState() {
  return {
    coordinate: {
      latitude: LATITUDE,
      longitude: LONGITUDE,
    },
  };
}

takeSnapshot () {
  // 'takeSnapshot' takes a config object with the
  // following options
  const snapshot = this.map.takeSnapshot({
    width: 300,      // optional, when omitted the view-width is used
    height: 300,     // optional, when omitted the view-height is used
    region: {..},    // iOS only, optional region to render
    format: 'png',   // image formats: 'png', 'jpg' (default: 'png')
    quality: 0.8,    // image quality: 0..1 (only relevant for jpg, default: 1)
    result: 'file'   // result types: 'file', 'base64' (default: 'file')
  });
  snapshot.then((uri) => {
    this.setState({ mapSnapshot: uri });
  });
}

render() {
  return (
    <View>
      <MapView initialRegion={...} ref={map => { this.map = map }}>
        <Marker coordinate={this.state.coordinate} />
      </MapView>
      <Image source={{ uri: this.state.mapSnapshot.uri }} />
      <TouchableOpacity onPress={this.takeSnapshot}>
        Take Snapshot
      </TouchableOpacity>
    </View>
  );
}

Zoom to Specified Markers

Pass an array of marker identifiers to have the map re-focus.

Zoom to Specified Coordinates

Pass an array of coordinates to focus a map region on said coordinates.

Troubleshooting

My map is blank

  • Make sure that you have properly installed react-native-maps.
  • Check in the logs if there is more informations about the issue.
  • Try setting the style of the MapView to an absolute position with top, left, right and bottom values set.
  • Make sure you have enabled Google Maps API in Google developer console
source-js
const styles = StyleSheet.create({
  map: {
    ...StyleSheet.absoluteFillObject,
  },
});
source-js-jsx
<MapView
  style={styles.map}
  // other props
/>

Inputs don't focus

  • When inputs don't focus or elements don't respond to tap, look at the order of the view hierarchy, sometimes the issue could be due to ordering of rendered components, prefer putting MapView as the first component.

Bad:

source-js-jsx
<View>
  <TextInput/>
  <MapView/>
</View>

Good:

source-js-jsx
<View>
  <MapView/>
  <TextInput/>
</View>

How to build a news app with JavaScript and React Native

How to build a news app with JavaScript and React Native

How to build a news app with JavaScript and React Native

Requirements for building the app:

  • A basic understanding of the JavaScript language.
  • Node.js, and react native.
  • Libraries used: moment, react-native, react-native-elements.

If you’re not familiar with these resources, don’t worry — they are quite easy to use.

The topics we will cover in the post are:

  • A basic understanding of the JavaScript language.
  • Node.js, and react native.
  • Libraries used: moment, react-native, react-native-elements.

And more…so let’s get started!

You can find the full project repo HERE.

News API

A simple and easy-to-use API that returns JSON metadata for headlines and articles live all over the web right now. — NewsAPI.org
First, you should go ahead and sign up for News Api to get your free apiKey (your authentication key).

Create a new React Native project, and call it news_app (or whatever you want). In the project directory, make a new folder and call it src . In srccreate a folder an name it components . So your project directory should look something like this:

In the src folder, create a new file called news.js . In this file we are going to fetch the JSON that contains the headlines from the News API.

news.js
const url =
  "https://newsapi.org/v2/top-headlines?country=us&apiKey=YOUR_API_KEY_HERE";

export async function getNews() {
  let result = await fetch(url).then(response => response.json());
  return result.articles;
}

Make sure you replace YOUR_API_KEY_HERE with your own API key. For more information about the News API, go to newsapi docs.

Now we declare the getNews function, which is going to fetch the articles for us. Export the function so we can use it in our App.js file.

App.js
import React from 'react';
import { FlatList } from 'react-native';

// Import getNews function from news.js
import { getNews } from './src/news';
// We'll get to this one later
import Article from './src/components/Article';

export default class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = { articles: [], refreshing: true };
    this.fetchNews = this.fetchNews.bind(this);
  }
  // Called after a component is mounted
  componentDidMount() {
    this.fetchNews();
   }

  fetchNews() {
    getNews()
      .then(articles => this.setState({ articles, refreshing: false }))
      .catch(() => this.setState({ refreshing: false }));
  }

  handleRefresh() {
    this.setState(
      {
        refreshing: true
    },
      () => this.fetchNews()
    );
  }

  render() {
    return (
      <FlatList
        data={this.state.articles}
        renderItem={({ item }) => <Article article={item} />}
        keyExtractor={item => item.url}
        refreshing={this.state.refreshing}
        onRefresh={this.handleRefresh.bind(this)}
      />
  );
  }
}

In the constructor, we define the initial state. articles will store our articles after we fetch them, and refreshing will help us in refresh animation. Notice that I setrefreshing to true, because when we start the app, we want the animation to start while we load the articles.

componentDidMount is invoked immediately after a component is mounted. Inside it we call the fetchNews method.

componentDidMount() {
  this.fetchNews();
}

In fetchNews we call getNews() which returns a promise. So we use the .then() method which takes a callback function, and the callback function takes an argument (the articles).

Now assign the articles in the state to the articles argument. I only typed articles because it’s a new ES6 syntax that means { articles: articles } , and we set refreshing to false to stop the spinner animation.

fetchNews() {
  getNews().then(
      articles => this.setState({ articles, refreshing: false })
  ).catch(() => this.setState({ refreshing: false }));
}

.catch() is called in rejected cases.

handleRefresh starts the spinner animation and call fetchNews(). We pass () => this.fetchNews() , so it’s called immediately after we assign the state.

handleRefresh() {
  this.setState({ refreshing: true },() => this.fetchNews());
}

In the render method, we return a FlatList element. Then we pass some props. data is the array of articles from this.state. The renderItem takes a function to render each item in the array, but in our case it just returns theArticle component we imported earlier (we’ll get there). And we pass the article item as a prop to use later in that component.

Article.js

In src/components create a new JavaScript file and call it Article.js.

Let’s start by installing two simple libraries using npm: react-native-elements, which gives us some premade components we could use, and moment that will handle our time.

run using terminal/cmd:

npm install --save react-native-elements moment

In Article.js:

import React from 'react';
import { View, Linking, TouchableNativeFeedback } from 'react-native';
import { Text, Button, Card, Divider } from 'react-native-elements';
import moment from 'moment';

export default class Article extends React.Component {
  render() {
    const {
      title,
      description,
      publishedAt,
      source,
      urlToImage,
      url
    } = this.props.article;
    const { noteStyle, featuredTitleStyle } = styles;
    const time = moment(publishedAt || moment.now()).fromNow();
    const defaultImg =
      'https://wallpaper.wiki/wp-content/uploads/2017/04/wallpaper.wiki-Images-HD-Diamond-Pattern-PIC-WPB009691.jpg';

    return (
      <TouchableNativeFeedback
        useForeground
        onPress={() => Linking.openURL(url)}
      >
        <Card
          featuredTitle={title}
          featuredTitleStyle={featuredTitleStyle}
          image={{
            uri: urlToImage || defaultImg
          }}
        >
          <Text style={{ marginBottom: 10 }}>
            {description || 'Read More..'}
          </Text>
          <Divider style={{ backgroundColor: '#dfe6e9' }} />
          <View
            style={{ flexDirection: 'row', justifyContent: 'space-between' }}
          >
            <Text style={noteStyle}>{source.name.toUpperCase()}</Text>
            <Text style={noteStyle}>{time}</Text>
          </View>
        </Card>
      </TouchableNativeFeedback>
    );
  }
}

const styles = {
  noteStyle: {
    margin: 5,
    fontStyle: 'italic',
    color: '#b2bec3',
    fontSize: 10
  },
  featuredTitleStyle: {
    marginHorizontal: 5,
    textShadowColor: '#00000f',
    textShadowOffset: { width: 3, height: 3 },
    textShadowRadius: 3
  }
};

There is a lot going on here. First, we start by destructuring the articleprop and the styles object defined below the class.

In render we define time to store the time for when the article was published. We use the moment library to convert the date to the time passed since then, and we pass publishedAt or time from now if publishedAt is null.

defaultImg is assigned an image URL in case the URL of the article image is null.

The render method returns TouchableNativeFeedback (use TouchableOpacityinstead if it does not work on your platform) to handle when the user presses the card. We pass it some props: useForground which tells the element to use the foreground when displaying the ripple effect on the card, and onPress , which takes a function and executes it when the user presses the card. We passed () => Linking.openUrl(url) which simply opens the URL to the full article when we press the card.

The card takes three props: featuredTitle which is just a fancy title placed over the image you could use title instead if you want, featuredTitleStyle to style it, and image which is the article image from the article prop. Otherwise, if its null , it’s going to be the defaultImg.

..
  featuredTitle={title}
  featuredTitleStyle={featuredTitleStyle}
  image={{ uri: urlToImage || defaultImg }}
..

As for the text element, it will hold the description for the article.

<Text style={{ marginBottom: 10 }}>{description}

We added a divider to separate the description from time and source name.

<Divider style={{ backgroundColor: '#dfe6e9' }} />

Below the Divider , we have a View that contains the source name and the time the article was published.

..
<View 
  style={{ flexDirection: ‘row’, justifyContent: ‘space-between’ }} > 
  <Text style={noteStyle}>{source.name.toUpperCase()}</Text>
  <Text style={noteStyle}>{time}</Text>
</View>
..

After the class, we defined the styles for these components.

Now if we run the app:

There you go! The source code for the app is available on GitHub HERE you can improve upon it and make a pull request😄.

How to Build React Native Wrappers for Native SDKs

How to Build React Native Wrappers for Native SDKs

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. In this post, we discuss How to Build React Native Wrappers for Native SDKs

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:

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). In our case, we needed to expose a method that would present the Onfido flow, so we implemented the following RCT_EXPORT_METHOD:

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:

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

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

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.

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

Thank you!