## Create react native app without expo
```
npx @react-native-community/cli@latest init KExampleProject
```
## Install `react-native-status-keycard` library
```
npm install https://github.com/keycard-tech/react-native-status-keycard
```
### Android
1. Add the following lines inside the buildscript block in `android/build.gradle`:
```
subprojects { subproject ->
afterEvaluate{
if((subproject.plugins.hasPlugin('android') || subproject.plugins.hasPlugin('android-library'))) {
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
}
}
}
}
```
This will apply compileSdkVersion and buildToolsVersion to any android modules you have.
2. Update `android/settings.gradle` to add `react-native-status-keycard` library:
```
include ':react-native-status-keycard'
project(':react-native-status-keycard').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-status-keycard/android')
```
3. Provide NFC & Camera permissions by updating `android/app/src/main/AndroidManifest.xml` to :
```
<uses-permission android:name="android.permission.NFC"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-feature android:name="android.hardware.nfc.hce" android:required="true" />
```
4. Insert the following lines inside the dependencies block in `android/app/build.gradle`:
```
implementation project(':react-native-status-keycard')
```
### iOS
1. Add pods to `target` block inside `ios/Podfile` :
```
pod 'SSZipArchive'
pod "react-native-status-keycard", path: "../node_modules/react-native-status-keycard"
pod "Keycard", git: "https://github.com/status-im/Keycard.swift.git", tag: '3.1.2'
pod 'secp256k1', git: "https://github.com/status-im/secp256k1.swift.git", submodules: true
```
In terminal:
```
cd ios
pod install
```
2. Insert the following lines inside `dict` block of `ios/<ProjectDir>/Info.plist` to configure NFC and camera permissions:
```
<key>NFCReaderUsageDescription</key>
<string>$(PRODUCT_NAME) needs access to NFC to interact with Keycard.</string>
<key>NSCameraUsageDescription</key>
<string>$(PRODUCT_NAME) needs access to your Camera in order scan the QR codes.</string>
...
<key>com.apple.developer.nfc.readersession.iso7816.select-identifiers</key>
<array>
<string>A00000080400010101</string>
<string>A00000080400010301</string>
<string>A000000151000000</string>
</array>
```
3. Open your project in Xcode, go to `Signing & Capabilities` and add `Near Field Communication Tag Reading` capability.
### Compiling issues
#### Android
~~The app will not compile on android unless you change ` jcenter()` to `mavenCentral()` in `android/build.gradle` of `react-native-status-keycard` library, as JCenter is no longer available.~~
The issue has been solved in [this commit](https://github.com/keycard-tech/react-native-status-keycard/commit/86775cd5419e1373a290b88f1d0942fabc8314bc).
#### iOS
~~The app will not compile on ios unless you convert `result.responseData.bytes` of `Sources/Keycard/CoreNFCCardChannel.swift` from `RawSpan` to `[UInt8]` in `Keycard.swift` library.~~
The issue has been solved in [this commit](https://github.com/keycard-tech/Keycard.swift/commit/120052c614d5cc2e839feac5fc1ff01824a4d50e).
##### Example solution
Write `result.responseData` to [UInt8] in `send` function instead of returnig `result.responseData.bytes`
```
public func send(_ cmd: APDUCommand) throws -> APDUResponse
...
var array = [UInt8](repeating: 0, count: result.responseData.count)
for i in 0..<array.count {
array[i] = result.responseData[i]
}
return APDUResponse(sw1: result.sw1, sw2: result.sw2, data: array)
...
```
## Usage
In App.tsx
```
import { NativeEventEmitter } from 'react-native';
...
//@ts-ignore
import Keycard from "react-native-status-keycard";
const App() => {
...
useEffect(() => {
const eventEmitter = new NativeEventEmitter(Keycard);
let onConnectedListener = eventEmitter.addListener('keyCardOnConnected', () => console.log("keycard connected"));
let onDisconnectedListener = eventEmitter.addListener('keyCardOnDisconnected', () => console.log("keycard disconnected"));
let onNFCEnabledListener = eventEmitter.addListener('keyCardOnNFCEnabled', () => console.log("nfc enabled"));
let onNFCDisabledListener = eventEmitter.addListener('keyCardOnNFCDisabled', () => console.log("nfc disabled"));
})
...
const connectCard = async () => {
if (await Keycard.nfcIsSupported() && !await Keycard.nfcIsEnabled()) {
await Keycard.openNfcSettings();
}
await Keycard.startNFC("Tap your Keycard");
#do something
}
}
```
## Create an expo app
A development build should be created to be able to use `react-native-status-keycard` library in expo app.
More about switching from Expo Go to development build [here](https://docs.expo.dev/develop/development-builds/expo-go-to-dev-build/).
There are 2 options:
* build on a local machine
* build on EAS
Check [Expo Docs](https://docs.expo.dev/develop/development-builds/create-a-build/) for more details about development build.
### Install `react-native-status-keycard`
```
npm install https://github.com/keycard-tech/react-native-status-keycard
```
### Configure your project to use `react-native-status-keycard`
#### 1. Update `app.json` file to to configure NFC permissions
Android
```
"android": {
...
"permissions": ["android.permission.NFC"],
...
```
iOS
```
...
"ios": {
....
"entitlements": {
"com.apple.developer.nfc.readersession.formats": ["TAG"]
},
"infoPlist": {
"NFCReaderUsageDescription": "This app needs access to NFC to interact with Keycard.",
"com.apple.developer.nfc.readersession.iso7816.select-identifiers": [
"A00000080400010101",
"A00000080400010301",
"A000000151000000"
]
}
},
```
#### 2. Write a custom config plugin for `react-native-status-keycard` to make custom native changes
In project directory, create a `plugins` directory and add `custom-config.ts`
Example `custom-config.ts`
```
import { ExpoConfig } from '@expo/config-types';
import {
ConfigPlugin,
withAndroidManifest,
withAppBuildGradle,
withDangerousMod,
withProjectBuildGradle,
withSettingsGradle,
} from 'expo/config-plugins';
import fs from "fs";
import path from 'path';
// Set the android.hardware.nfc.hce feature required to true in AndroidManifest.xml
const withAndroidFeatures: ConfigPlugin = config => {
return withAndroidManifest(config, async (config: any) => {
const existingFeature = config.modResults.manifest['uses-feature']?.find((p: any) => p['$'] && p['$']['android:name'] === 'android.hardware.nfc.hce');
if (!existingFeature) {
config.modResults.manifest['uses-feature'] =
config.modResults.manifest['uses-feature'] ?? [];
config.modResults.manifest['uses-feature'].push({
$: {
'android:name': 'android.hardware.nfc.hce',
'android:required': 'true'
},
});
}
return config;
});
}
// Add ext and subprojects configuration blocks to buildscript block in project build.gradle
const withBuildGradle: ConfigPlugin = config => {
return withProjectBuildGradle(config, async config => {
const buildScriptBlock = 'buildscript {';
const subprojectsBlock =
`ext {
buildToolsVersion = "36.0.0"
minSdkVersion = 24
compileSdkVersion = 36
targetSdkVersion = 36
ndkVersion = "27.1.12297006"
kotlinVersion = "2.2.20"
}
subprojects { subproject ->
afterEvaluate{
if((subproject.plugins.hasPlugin('android') || subproject.plugins.hasPlugin('android-library'))) {
android {
compileSdkVersion rootProject.ext.compileSdkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
}
}
}
}`;
if (config.modResults.contents.includes(buildScriptBlock)) {
config.modResults.contents = config.modResults.contents.replace(
buildScriptBlock,
`${buildScriptBlock}\n${subprojectsBlock}`
);
} else {
config.modResults.contents += `${buildScriptBlock}\n${subprojectsBlock}`
}
return config;
});
};
// Add the 'implementation' configuration to the dependencies in app/build.gradle
const withApplicationBuildGradle: ConfigPlugin = config => {
return withAppBuildGradle(config, async config => {
const dependenciesBlock = 'dependencies {';
const implementationLine =
" implementation project(':react-native-status-keycard')";
if (config.modResults.contents.includes(dependenciesBlock)) {
config.modResults.contents = config.modResults.contents.replace(
dependenciesBlock,
`${dependenciesBlock}\n${implementationLine}`
);
}
return config;
});
};
// Add the 'include' and 'project' configuration for react-native-status-keycard in settings.gradle
const withGradleSettings: ConfigPlugin = config => {
return withSettingsGradle(config, async config => {
const includeKeycardLine = `include ':react-native-status-keycard'`;
const projectKeycardLine = `project(':react-native-status-keycard').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-status-keycard/android')`;
config.modResults.contents += `${includeKeycardLine}\n${projectKeycardLine}`;
return config;
});
}
// Add configuration for keycard pods in Podfile
const withPodKeycardPodfile: ConfigPlugin = config => {
return withDangerousMod(config, [
'ios',
async config => {
const podfilePath = path.join(
config.modRequest.platformProjectRoot,
"Podfile",
);
let podfileContent = fs.readFileSync(podfilePath, "utf8");
// Keycard and its dependencies
const keycardPods =
`pod "SSZipArchive"
pod "react-native-status-keycard", path: "../node_modules/react-native-status-keycard"
pod "Keycard", git: "https://github.com/status-im/Keycard.swift.git", tag: '3.1.2'
pod "secp256k1", git: "https://github.com/status-im/secp256k1.swift.git", submodules: true`;
if (!podfileContent.includes("pod 'Keycard'")) {
podfileContent = podfileContent.replace(
/use_expo_modules!/,
`use_expo_modules!\n${keycardPods}`,
);
fs.writeFileSync(podfilePath, podfileContent);
}
return config;
},
]);
}
//configuration function
const withKeycardSDK = (config: ExpoConfig) => {
config = withBuildGradle(config);
config = withApplicationBuildGradle(config);
config = withAndroidFeatures(config);
config = withGradleSettings(config);
config = withPodKeycardPodfile(config);
return config;
};
export default withKeycardSDK;
```
#### 3. Create `app.config.ts` for dynamic plugins configuration in `app.json`
Example code to dynamically update plugins block adding `custom-config` plugin:
```
import "tsx/cjs";
import withKeycardSDK from './plugins/custom-config';
export default ({ config }) => {
if (!config.plugins) config.plugins = [];
config.plugins.push(
withKeycardSDK,
);
return config;
};
```
#### 4. Prebuild app with `npx expo prebuild --clean`