How to Integrate Plaid SDK to React Native Using Expo Config Plugins

Previously, if you wanted to integrate Plaid React Native SDK to Expo, you either had to forcefully eject to bare workflow or use a webview solution which could result in some funny unexpected issues such as this recaptcha pop up.

Well, in case you have stumbled upon my previous guide to Expo config plugins, you already know that customizing Expo managed workflow is more than possible and therefore, in this tutorial, we will look at a bit more advanced example how to integrate Plaid, a reliable service to connect financial accounts to our app.

SETUP

To get started you can use this Github repository as a starter or just follow these commands:

Initiate a new Expo project - here with typescript for better DX:

expo init expo-plaid-sdk-integration  -t expo-template-blank-typescript

Add the React Native Plaid SDK library:

yarn add react-native-plaid-link-sdk

Update App.tsx to:

import React from 'react'
import { StyleSheet, Text, View } from 'react-native'
import { PlaidLink, LinkSuccess, LinkExit } from 'react-native-plaid-link-sdk'

export default function App() {
  return (
    <View style={styles.container}>
      <PlaidLink
        tokenConfig={{ token: '#GENERATED_LINK_TOKEN#', noLoadingState: false }}
        onSuccess={(success: LinkSuccess) => console.log(success)}
        onExit={(exit: LinkExit) => console.log(exit)}
      >
        <Text>Add Account</Text>
      </PlaidLink>
    </View>
  )
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center'
  }
})

And include iOS bundleIdentifier and Android package name of your choice to app.json:

"expo": {
    ...otherProps,
    "ios": {
        "supportsTablet": true,
        "bundleIdentifier": "com.expo.plaid"
    },
    "android": {
        "adaptiveIcon": {
            "foregroundImage": "./assets/adaptive-icon.png",
            "backgroundColor": "#FFFFFF"
      },
      "package": "com.expo.plaid"
    }
}

If you now try running the application through Expo Go, you will likely see this error...

TypeError: null is not an object (evaluating '_reactNative.NativeModules.RNLinksdk.continueFromRedirectUriString')

...which should be expected.

PLAID INTEGRATION

To start integrating Plaid we will need to run the app through expo-dev-client which we can achieve either by using expo run commands locally or using EAS build process. By building the dev client we will have a custom Expo Go which will add the Plaid library to its bundle, meaning you can use this client until you decide to add again something new that is not supported by it. For simplicity we will stay with the local run commands. Then we just need to follow the Plaid readme setup guide for both iOS and Android.

iOS setup

This is actually super simple. The readme suggests adding to ios/Podfile following string pod 'Plaid', '~> <insert latest version> to pin the latest version, but, in reality, this is optional and we can easily work with the version currently bundled in the library. As a result, the only step we have to do is to initiate the expo-dev-client by running:

expo run:ios

After the build process finishes, the app launches without the error we experienced previously. You can press Add Account and you should see Plaid error complaining about the token we provided - but about that later.

Unexpected environment string value: (null). Expected one of: production, sandbox, or development.

Android setup

If you run expo run:android and press Add Account at this stage, you will get an unhandled promise rejection..

TypeError: null is not an object (evaluating '_reactNative.NativeModules.PlaidAndroid.startLinkActivityForResult')

..because there are actually bunch of steps to make Android work and as the readme suggests it is due to TurboModules not supporting autolinking. But in general this is nothing we cannot setup ourselves with Expo config plugins.

1) Setup your app id

This change you have to do on Plaid Dashboard at the API page - Allowed Android Package. You should configure there the Android package name from app.json - com.expo.plaid.

2) Update MainApplication.java

Here we have to fullfil 2 requirements:

  • Add import com.plaid.PlaidPackage; to the imports section
  • Add packages.add(new PlaidPackage()); to List<ReactPackage> getPackages();

Which we can do only with an expo config plugin:

Create in the root of your project withAndroidPlaid.ts file and start transpiling it to javascript with this command:

yarn tsc withAndroidPlaid.ts --watch --skipLibCheck

and import it in app.json as a plugin:

{
  "expo": {
    ...otherProps,
    "plugins": ["./withAndroidPlaid"]
  }
}

Finally, change withAndroidPlaid.ts content to following:

import type { ConfigPlugin } from '@expo/config-plugins'
import { withMainApplication } from '@expo/config-plugins'

function applyPackage(mainApplication: string) {
  const plaidPackageImport = `import com.plaid.PlaidPackage;\n`
  const plaidAddPackage = `packages.add(new PlaidPackage());`

  // Make sure the project does not have the settings already
  if (!mainApplication.includes(plaidPackageImport)) {
    mainApplication = mainApplication.replace(
      /package com.expo.plaid;/,
      `package com.expo.plaid;\n${plaidPackageImport}`
    )
  }

  if (!mainApplication.includes(plaidAddPackage)) {
    mainApplication = mainApplication.replace(
      /return packages;/,
      `
    ${plaidAddPackage}
    return packages;
    `
    )
  }

  return mainApplication
}

const withAndroidPlaid: ConfigPlugin = (expoConfig) => {
  expoConfig = withMainApplication(expoConfig, (config) => {
    config.modResults.contents = applyPackage(config.modResults.contents)
    return config
  })

  return expoConfig
}

export default withAndroidPlaid

If you look closely, our plugin is utilizing withMainApplication, a mod provided by Expo allowing us to read and modify the content of MainApplication.java. We provide the content to our function applyPackage where we execute 2 string replacements to insert plaidPackageImport and plaidAddPackage constants into the file - the changes Plaid readme wanted from us.

Our strategy is to simply find a stable part of the file where we can append our changes. Due to the string replacement nature, it is quite dangerous thing to rely on, because they could change with consequent React Native and Expo updates - so be sure these replacements still work when you upgrade.

If you run expo prebuild -p android you should now see updated MainApplication.java. If you did something incorrectly, you may want to discard changes, check your plugin code, and try prebuild again.

3) Update app/build.gradle

We need to update dependencies to following:

dependencies {
    // ...
    implementation project(':react-native-plaid-link-sdk')
}

Which we can do utilizing withAppBuildGradle modifying the file within withAndroidPlaid function.

// ...
expoConfig = withAppBuildGradle(expoConfig, (config) => {
  config.modResults.contents = applyImplementation(config.modResults.contents)
  return config
})
// ...

And applyImplementation is our custom function following the previous strategy of appending plaidImplementation string to the right place of the file.

function applyImplementation(appBuildGradle: string) {
  const plaidImplementation = `implementation project(':react-native-plaid-link-sdk')`

  // Make sure the project does not have the dependency already
  if (!appBuildGradle.includes(plaidImplementation)) {
    return appBuildGradle.replace(
      /dependencies\s?{/,
      `dependencies {
    ${plaidImplementation}`
    )
  }

  return appBuildGradle
}

4) Update settings.gradle

Finally, we have to append following string to settings.gradle:

include ':react-native-plaid-link-sdk'
project(':react-native-plaid-link-sdk').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-plaid-link-sdk/android')

Which can be done using withSettingsGradle Expo mod:

expoConfig = withSettingsGradle(expoConfig, (config) => {
  config.modResults.contents = applySettings(config.modResults.contents)
  return config
})

And making the changes with our custom applySettings function. Notice that we are just concatenating the strings with plus symbol given that we don't really care about the exact placement of plaidSettings constant.

function applySettings(gradleSettings: string) {
  const plaidSettings = `include ':react-native-plaid-link-sdk'
  project(':react-native-plaid-link-sdk').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-plaid-link-sdk/android')`

  // Make sure the project does not have the settings already
  if (!gradleSettings.includes(`include ':react-native-plaid-link-sdk'`)) {
    return gradleSettings + plaidSettings
  }

  return gradleSettings
}

If you lost track of the changes or applied them incorrectly, you can always go to the solution branch of this github repository for my final implementation.

Troubleshooting: I have discovered 2 potential issues when implementing Plaid on Android, so if you are facing build errors or your app is crashing, you might find an answer in these github issues: Kotlin version and OkHttp version.

After applying all necessary changes you should just run expo run:android to build the app with all modifications.

Once the build process finishes and the app launches, you can press Add Account and you should see a different Plaid error complaining about configuration - but it is actually about the fake token we have provided.

null - unable to open link, please check that your configuration is valid

Getting the link token

At this point, you just need to provide a correct link token in App.tsx. Normally you would get it from your backend, but for testing purposes we can actually use a very handy Postman collection provided by Plaid.

After going through the setup and utilizing your client_id and secret from Plaid dashboard, you can hit https://sandbox.plaid.com/link/token/create API endpoint and use the returned link_token.

{
  "expiration": "2021-12-25T19:49:22Z",
  "link_token": "link-sandbox-965dbc89-14fc-4122-b900-27a562de6db0",
  "request_id": "AEBgG6EbWGsQ9aR"
}

Now pressing Add Account should finally open Plaid interface:

SUMMARY

This integration might feel scary at first, but in the end we are just doing the same thing over and over - inserting a string into a native file during the build time (or prebuild time more specifically). Note that the Expo mods we used are marked as dangerous given that they rely on our custom string replacement rules and you should expect them to break in the future. But for now, they are the best way how to combine the Expo managed workflow and Plaid React Native SDK.

60