60
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.
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.
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.
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.
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.
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
.
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.
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
}
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
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:
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