React Native Walkthrough 0.7.0
React Native Captur Events SDK
Integrate Captur’s verification capabilities into your React Native apps. Below you’ll find installation steps, initialization guidance, event handling, and a full example screen.
Prerequisites
- A valid Captur API key (provided by the Captur team).
- React Native 0.77+
- iOS 15.4 or higher
- Android 10 or higher
Installation & Authentication
- Create or open your global npm config:
touch ~/.npmrc # create file if doesn't exist already- Add your Access Token:
# ~/.npmrc
//registry.npmjs.org/:_authToken=ACCESS_TOKEN- Install the Captur Events SDK:
# in your react-native project
npm install @captur-ai/captur-react-native-eventsPlatform setup
iOS Installation
- From the iOS / folder:
cd ios && pod install- In Info.plist, add: ( or use Xcode to add camera permissions )
<key>Privacy - Camera Usage Description</key>
<string>Your camera is used for verification</string>Android Installation
- In
android/app/build.gradle, add the AAR repository:
repositories {
google()
mavenCentral()
flatDir {
dirs '../../node_modules/@captur-ai/captur-react-native-events/android/libs'
}
}- In the same file, include the Captur AAR and its dependencies:
dependencies {
...
implementation(name: 'capturMicroMobility-release', ext: 'aar') {
transitive = true
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1")
implementation("androidx.camera:camera-core:1.3.4")
implementation("androidx.camera:camera-camera2:1.3.4")
implementation("androidx.camera:camera-lifecycle:1.3.4")
implementation("androidx.camera:camera-view:1.3.4")
api("org.tensorflow:tensorflow-lite-task-vision:0.4.0")
implementation("com.squareup.retrofit2:adapter-rxjava3:2.9.0")
implementation("com.squareup.moshi:moshi-kotlin:1.14.0")
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.retrofit2:converter-moshi:2.9.0")
implementation("com.squareup.okhttp3:okhttp:4.11.0")
implementation("com.squareup.okhttp3:logging-interceptor:4.11.0")
}
...
}- In android/app/src/main/AndroidManifest.xml, add:
<uses-permission android:name="android.permission.INTERNET" />
<uses-feature android:name="android.hardware.camera.flash" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
<uses-permission android:name="android.permission.CAMERA" />Initialization
- Set up your configuration and initialize the SDK before rendering any camera views:
const DELAY = 1;
const TIMEOUT = 25;
const LOCATION_NAME = "Toronto";
const ASSET_TYPE = "package"; // other values: eScooter, eBike...etc
const API_KEY = "captur-*******";
async function initializeCaptur() {
await setTimeout(TIMEOUT);
await setDelay(DELAY);
await setApiKey(API_KEY);
await prepareModel(LOCATION_NAME, ASSET_TYPE, 0.0, 0.0);
await getConfig(LOCATION_NAME, ASSET_TYPE, 0.0, 0.0);
}Event Subscription
Use subscribeToEvents to receive both real-time guidance and final decision events from the Captur SDK. You register four callbacks:
- capturDidGenerateGuidance: fires continuously as the camera analyzes frames, providing user instructions (e.g. “move closer”, “rotate left”).
- capturDidRevokeGuidance: fires when the previous guidance no longer applies (e.g. user moved out of frame).
- capturDidGenerateEvent: fires on state changes, including the final "cameraDecided" state which delivers the compliant or non-compliant decision along with a base64 image.
- capturDidGenerateError: fires if any internal error occurs (e.g. network timeout, model load failure).
useEffect(() => {
let unsubscriber: (() => void) | undefined;
(async () => {
await initializeCaptur();
unsubscriber = subscribeToEvents({
capturDidGenerateGuidance: (guidanceMeta) => {
// guidanceMeta.guidanceTitle is a human-readable tip for the user
guidanceInputRef.current?.setNativeProps({
text: guidanceMeta.guidanceTitle,
});
},
capturDidRevokeGuidance: () => {
// clear or hide guidance when it’s no longer valid
guidanceInputRef.current?.setNativeProps({ text: "" });
},
capturDidGenerateEvent: (state, metadata) => {
// state === "cameraDecided" indicates the final result
if (state === "cameraDecided") {
// metadata.imageDataBase64 contains the decision image
setThumbnail("data:image/png;base64," + metadata?.imageDataBase64);
}
},
capturDidGenerateError: (error) => {
// handle errors (e.g. show a message or retry)
console.error("Captur error:", error);
},
});
})();
return () => {
unsubscriber?.();
};
}, []);Camera Component
Render the camera preview and control flash/zoom:
<CapturCameraView
key={`${referenceId}-${startVerification}`} // This key is required to deal with surface attachment quirks.
style={styles.camera}
referenceId={referenceId ?? ''}
startVerification={startVerification}
isFlashOn={isFlashOn}
isZoomedIn={isZoomedIn}
/>- referenceId: unique per session (e.g. rideId).
- startVerification: on/off toggle for preview & analysis.
- isFlashOn / isZoomedIn: control flash & zoom.
- key: The key helps with mounting and unmounting the camera preview and managing its life cycle.
Attempts & Sessions
Start an Attempt
Starting an attempt tells the Captur SDK to begin capturing camera frames and running its compliance model in real time. If you already have a thumbnail from a prior attempt in the same session, call retake() first to clear previous state—otherwise you can simply set startVerification to true.
const startCamera = async () => {
if (!capturInitialized) return;
if (!referenceId) return;
if (startVerification) return;
if (thumbnail) {
setThumbnail(null);
guidanceInputRef.current?.setNativeProps({
text: '',
});
retake();
}
setStartVerification(true);
};End an Attempt
Ending an attempt stops the camera analysis, resets any toggles (flash, zoom), and emits a final cameraDecided event containing the compliance decision and a base64-encoded image.
const stopCamera = () => {
if (!capturInitialized) return;
if (!startVerification) return;
return endAttempt();
};
When you call endAttempt an event called capturDidGenerateEvent will be emitted from the SDK with a state with the value cameraDecided. You should always perform the end attempt or capture photo logic inside the callback of that event. for example, you should reset the camera state and set the thumbnail/photo taken in the event listener like so:
subscribeToEvents({
capturDidGenerateEvent: (state, metadata) => {
if (state === 'cameraDecided') {
resetCameraState();
setThumbnail('data:image/png;base64,' + metadata?.imageDataBase64);
}
}
)
New Session
If you need to start a completely new session (new referenceId), clear all UI state, then generate a new referenceId and begin verification again.
const newSession = async () => {
setThumbnail(null);
resetCameraState();
guidanceInputRef.current?.setNativeProps({
text: '',
});
setReferenceId(Date.now().toString());
setStartVerification(true);
};Full example
import { useState, useEffect, useRef } from 'react';
import { View, StyleSheet, Button, Image, TextInput } from 'react-native';
import {
setDelay,
setTimeout as cptrSetTimeout,
setApiKey,
prepareModel,
getConfig,
CapturCameraView,
subscribeToEvents,
endAttempt,
retake,
} from '@captur-ai/captur-react-native-events';
const DELAY = 1;
const TIMEOUT = 25;
const LOCATION_NAME = "Toronto";
const ASSET_TYPE = "package";
const API_KEY = "captur-***-****-**-**-**-****";
async function initializeCaptur() {
await cptrSetTimeout(TIMEOUT);
await setDelay(DELAY);
await setApiKey(API_KEY);
await prepareModel(LOCATION_NAME, ASSET_TYPE, 0.0, 0.0);
await getConfig(LOCATION_NAME, ASSET_TYPE, 0.0, 0.0);
}
export default function App() {
const [capturInitialized, setCapturInitialized] = useState(false);
const [startVerification, setStartVerification] = useState(false);
const [isFlashOn, setIsFlashOn] = useState(false);
const [isZoomedIn, setIsZoomedIn] = useState(false);
const [thumbnail, setThumbnail] = useState<string | null>(null);
const [referenceId, setReferenceId] = useState<string>(Date.now().toString());
const guidanceInputRef = useRef<TextInput>(null);
useEffect(() => {
let unsubscriber: (() => void) | undefined;
(async () => {
await initializeCaptur();
unsubscriber = subscribeToEvents({
capturDidGenerateEvent: (state, metadata) => {
if (state === 'cameraDecided') {
resetCameraState();
setThumbnail('data:image/png;base64,' + metadata?.imageDataBase64);
}
},
capturDidGenerateError: (err) => {
console.log('capturDidGenerateError', err);
},
capturDidGenerateGuidance: (meta) => {
guidanceInputRef.current?.setNativeProps({
text: meta.guidanceTitle,
});
},
capturDidRevokeGuidance: () => {
console.log('capturDidRevokeGuidance');
},
});
setCapturInitialized(true);
})();
return () => {
unsubscriber?.();
};
}, []);
const resetCameraState = () => {
setStartVerification(false);
setIsFlashOn(false);
setIsZoomedIn(false);
};
const startCamera = async () => {
if (!capturInitialized) return;
if (!referenceId) return;
if (startVerification) return;
if (thumbnail) {
setThumbnail(null);
guidanceInputRef.current?.setNativeProps({
text: '',
});
retake();
}
setStartVerification(true);
};
const stopCamera = () => {
if (!capturInitialized) return;
if (!startVerification) return;
return endAttempt();
};
const handleFlash = () => {
setIsFlashOn((prev) => !prev);
};
const handleZoom = () => {
setIsZoomedIn((prev) => !prev);
};
const newSession = async () => {
setThumbnail(null);
resetCameraState();
guidanceInputRef.current?.setNativeProps({
text: '',
});
setReferenceId(Date.now().toString());
setStartVerification(true);
};
return (
<View style={styles.container}>
<View style={styles.cameraContainer}>
<CapturCameraView
key={`${referenceId}-${startVerification}`}
style={styles.camera}
referenceId={referenceId ?? ''}
startVerification={startVerification}
isFlashOn={isFlashOn}
isZoomedIn={isZoomedIn}
/>
</View>
{thumbnail && (
<View style={styles.thumbnailContainer}>
<Image style={styles.thumbnail} source={{ uri: thumbnail }} />
</View>
)}
<View style={styles.actionButtons}>
<Button
title={startVerification ? 'Stop' : 'Start'}
onPress={startVerification ? stopCamera : startCamera}
/>
<Button
title={isFlashOn ? 'Turn flash off' : 'Turn flash on'}
onPress={handleFlash}
/>
<Button
title={isZoomedIn ? 'Zoom out' : 'Zoom in'}
onPress={handleZoom}
/>
<Button title={'New session'} onPress={newSession} />
</View>
<View style={styles.guidance}>
<TextInput
style={styles.guidanceText}
ref={guidanceInputRef}
editable={false}
/>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'flex-end',
},
cameraContainer: {
flex: 1,
position: 'relative',
justifyContent: 'flex-end',
},
camera: {
flex: 1,
},
thumbnailContainer: {
position: 'absolute',
top: 0,
left: 0,
width: '100%',
height: '100%',
backgroundColor: 'black',
},
thumbnail: {
width: '100%',
height: '100%',
resizeMode: 'cover',
},
guidance: {
position: 'absolute',
top: '10%',
width: '100%',
zIndex: 14,
alignItems: 'center',
backgroundColor: 'rgba(0,0,0,0.4)',
paddingVertical: 12,
},
guidanceText: {
color: 'white',
fontSize: 16,
},
actionButtons: {
position: 'absolute',
bottom: 0,
left: 0,
width: '100%',
zIndex: 20,
gap: 10,
},
});
Updated 15 days ago
