Hallo 😊

Kamsi Oleka, a software engineer, a gym rat, and a stand-up comedian amongst friends.

I have worked with interesting companies like Thepeer, Klarna, Piggy LLC, and Abeg.

A blog of my thoughts, experiences, and cool stuff I've built.

Back

Building a React Native SDK

Just like with React, a React Native library could be anything, e.g. a component, multiple components that abstracts some repetitive code and makes it easier to use.

We're going to be focused on building a React Native library for the pay-script we had deployed. There're two ways to go build this:

  • Building the component(s) from scratch and publishing the component(s) or;
  • Injecting our pay-script into a WebView component in React Native and publishing it

We're doing the latter for this piece.

Prerequisite

  • Understanding JavaScript basics, JavaScript functions, Switch statements
  • Knowledge of React Native

Step 1

Open up your terminal and make a new directory called pay-app-rn

One simple way of creating a React Native library is by using create-react-native-library cli.

To create a new library, simply run:

npx create-react-native-library pay-app-rn

Some configuration options would follow after running the command in your terminal. For template, select typescript and for package manager, use npm. Every other thing can be altered from the package.json file.

Screenshot of terminal window!

After configuring your library, our module would be created. Run the following

cd pay-app-rn && npm install

Next let us install a package to build our code for multiple targets

npx react-native-builder-bob init

Afterwards, we will install our dependency

npm install react-native-webview

After installation, add it as a peerDependency in your package.json file. Adding dependencies to peerDependency ensures there would not be a conflict between your specified version and whatever version your library's users are running at the time of installation.

Screenshot of peerDependencies!

Step 2

Let's create our directories and components in our src folder. In your terminal, run the code below to make our directories:

cd src && mkdir components types utils

Now we will create the files we need in each of these directories. Run the code below to create these files

cd components && touch Error.tsx Loader.tsx && cd .. &&
cd utils && touch index.ts && cd .. &&
cd types && touch index.ts

Step 3

Open the Error.tsx and paste the code below:

// Error.tsx

import React from "react";
import { View, StyleSheet, Text, Platform, Pressable } from "react-native";

const ErrorFallback = ({ onClose }: { onClose: () => void }) => {
  return (
    <View style={styles.container}>
      <Pressable
        android_ripple={{ color: "rgba(0,0,0,0.15)" }}
        style={({ pressed }: { pressed: boolean }) => [
          {
            opacity: Platform.OS === "ios" && pressed ? 0.2 : 1,
            padding: 20,
            marginRight: -20,
            position: "absolute",
            alignItems: "flex-end",
            alignSelf: "flex-end",
          },
        ]}
        onPress={onClose}
      >
        Close
      </Pressable>

      <Text style={styles.text}>Something went wrong. Try again</Text>
    </View>
  );
};

const styles = StyleSheet.create({
  text: {
    fontSize: 16,
    color: "#727EA3",
    fontWeight: "400",
    marginTop: 10,
  },
  container: {
    flex: 1,
    backgroundColor: "#ffffff",
    height: "100%",
    width: "100%",
    alignItems: "center",
    position: "absolute",
  },
});

export default ErrorFallback;

The component above is a fallback for when the WebView doesn't load up successfully, it gives the users a chance to close the SDK and reboot it

Open the Loader.tsx and paste the code below:

// Loader.tsx

import React from "react";
import { ActivityIndicator, View, StyleSheet } from "react-native";

const Loader = () => {
  return (
    <View style={styles.wrapper}>
      <ActivityIndicator size="small" color="#0066FF" />
    </View>
  );
};

const styles = StyleSheet.create({
  wrapper: {
    display: "flex",
    justifyContent: "center",
    alignItems: "center",
    backgroundColor: "white",
    height: "100%",
  },
});

export default Loader;

This is a simple loader we will show when the WebView is still loading.

Step 4

Open the index file and paste the code below:

// index.ts

import React from "react";
import { View, Modal, StyleSheet, Platform } from "react-native";
import type { ConfigProps } from "../types";
import { createUrl } from "../utils";

const Pay = (props: ConfigProps) => {
  const { onClose, onSuccess, onError, openSDK } = props;
  const handleMessage = ({ nativeEvent: { data } }: any) => {
    const response = JSON.parse(data);
    switch (response.event) {
      // again, using the events we used at Thepeer, you should have cases that match your own emitted events
      // case "pay.close":
      case "send.close":
        onClose();
        break;
      // case "pay.success":
      case "send.success":
        onSuccess(response);
        break;
      default:
        onError(response);
        break;
    }
  };

  const sourceUrl = createUrl(props);

  return (
    <Modal
      animationType="slide"
      transparent={true}
      visible={openSDK}
      onRequestClose={onClose}
    >
      <View style={styles.container}>
        <View style={styles.mainView}>
          <WebView
            source={{ uri: sourceUrl }}
            onMessage={handleMessage}
            startInLoadingState={true}
            renderLoading={() => <Loader />}
            renderError={(error) => <ErrorFallback {...{ onClose, error }} />}
          />
        </View>
      </View>
    </Modal>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "rgba(0,0,0,0.6)",
    paddingTop: Platform.OS === "ios" ? 48 : 36,
    position: "relative",
  },
  mainView: {
    position: "relative",
    borderTopLeftRadius: 12,
    borderTopRightRadius: 12,
    backgroundColor: "#ffffff",
    overflow: "hidden",
    height: "100%",
  },
});

export default Pay;

This function basically exposes a modal that is housing our WebView. We generate a url with our config and pass it in as the source. The component would automatically load up our SDK.

handleMessage

This is a function to listen and react to the events emitted from our app (pay-app-react).

Step 5

You're probably getting errors in the code because of missing imports. Let's fix those 🏃🏻‍ Now we need to add our types to the type file.

1. Open types/index.ts and add the following types

// types/index.ts

type EventResponse = {
  type: string;
  data: undefined | Object;
};

export interface ConfigProps {
  openSDK: boolean;
  publicKey: string;
  amount: string | number;
  currency: string;
  onSuccess: (response: EventResponse) => void;
  onError: (response: EventResponse) => void;
  onClose: (response?: EventResponse) => void;
}

2. Open utils/index.ts and add the following types

// utils/index.ts

const isRequired = (prop: string, isValid: boolean) => {
  if (isValid) return;
  throw new Error(`${prop} is required`);
};

const validateAmount = ({
  amount,
  currency,
}: {
  amount: string | number;
  currency: string;
}) => {
  if (!amount) return isRequired("amount", false);

  const amt = +amount;
  if (!isNaN(amt) && currency === "NGN" && amt < 10000) {
    throw new Error("amount cannot be less than ₦100");
  } else {
    throw new Error("amount must be a number");
  }
};

const validateConfig = (config: any) => {
  const { amount, publicKey, onClose, onSuccess, onError, currency } = config;

  isRequired("publicKey", !!publicKey);
  isRequired("currency", !!currency);
  isRequired("onClose callback", onClose !== undefined);
  isRequired("onError callback", onError !== undefined);
  isRequired("onSuccess callback", onSuccess !== undefined);
  validateAmount({ amount, currency });

  return true;
};

const createUrl = (config: any) => {
  const configValid = validateConfig(config);
  let base = "https://chain.thepeer.co?";
  if (!configValid) return base;

  Object.keys(config).map((k) => {
    if (config[k]) {
      const val = k === "meta" ? JSON.stringify(config[k]) : config[k];
      base = base.concat(`${k}=${val}&`);
    }
  });
  return base.slice(0, -1);
};

export { createUrl };

1. createUrl

This function appends our props as a query to our pay-app-react's url. It calls our validateConfig to validate our configs first and returns a url depending on the validation result.

2. validateAmount

Validate the amount and currency passed and errors out if there's a problem.

Step 6: Testing locally

We're pretty much done with the SDK. Now we can bundle it and test it out. To bundle and create an installable file, run:

npm run prepare && npm pack

You should see a pay-app-rn-v0.1.0.tgz in the root of your project

Screenshot of terminal window!

You can install it in your example folder using the absolute path to the file.

To publish to NPM

  1. Create an account if you don't already have one
  2. After creating the account, open your terminal and run
npm login
  1. After logging in, run
  npm run prepare && npm publish
  1. Fill out the questions asked and viola!