One Logo Pool Ball
wwwwwwwwwwwwwwwwwww

Camera preview

A live camera view that takes pictures and scans QR codes and barcodes on iOS and Android

CameraView shows a live camera preview inside your layout, takes pictures from it, and reports the QR codes and barcodes it sees. On iOS it is an AVCaptureSession with photo and metadata outputs; on Android it is CameraX with ML Kit barcode scanning. The same component is reachable as One.iOS.CameraView and One.Android.CameraView, and picks the platform’s camera itself.

Use it when the camera belongs inside your screen: a custom shutter, an overlay, scanning tickets or product codes. To let people take a photo in the system camera instead, use Image Picker.

Usage

Declare why you use the camera in your app config before prebuild. This writes the iOS usage text and adds Android’s CAMERA permission:

import { one } from 'one/vite'
export default {
plugins: [
one({
native: {
app: {
name: 'MyApp',
imagePicker: { camera: 'Scan tickets at the door.' },
},
},
}),
],
}

Then ask for permission and turn the view on:

import { useState } from 'react'
import { Button, Text, View } from 'react-native'
import { One } from 'one'
function TicketScanner({ onTicket }: { onTicket: (code: string) => void }) {
const [active, setActive] = useState(false)
return (
<View style={{ flex: 1 }}>
<One.iOS.CameraView
style={{ flex: 1 }}
active={active}
codeTypes={['qr']}
onCodeScanned={({ data }) => {
setActive(false)
onTicket(data)
}}
/>
<Button
title="Scan"
onPress={async () => {
const permission = await One.ImagePicker.requestCameraPermissions()
if (permission.granted) setActive(true)
}}
/>
</View>
)
}

The view never asks for permission itself, so the button asks first and turns scanning on only after a yes.

To take a picture, hold a ref and call takePicture once the view reports ready:

import { useRef, useState } from 'react'
import { Button, Image, View } from 'react-native'
import { One, type CameraViewRef } from 'one'
function ProfilePhoto() {
const camera = useRef<CameraViewRef>(null)
const [ready, setReady] = useState(false)
const [photo, setPhoto] = useState<string>()
return (
<View style={{ flex: 1 }}>
<One.iOS.CameraView
ref={camera}
style={{ flex: 1 }}
facing="front"
codeTypes={[]}
onStateChange={(state) => setReady(state === 'ready')}
/>
<Button
title="Take photo"
disabled={!ready}
onPress={async () => {
const picture = await camera.current?.takePicture({ quality: 0.8 })
if (picture) setPhoto(picture.uri)
}}
/>
{photo ? <Image source={{ uri: photo }} style={{ width: 96, height: 96 }} /> : null}
</View>
)
}

How it works

Starting. The camera runs only while active is true, the view is on screen, and the app is in the foreground. When all three hold and permission is granted, the view starts a session on the wide-angle camera facing the way facing says, fills its bounds with the preview, cropping to keep the aspect ratio, and reports ready.

Scanning. The requested codeTypes are matched against what the camera can detect. On iOS the metadata output reports each code in every frame it appears in. On Android, ML Kit analyzes the newest frame at a time, around 1280 by 720, and skips a code identical to the last one delivered. Turning the view off and on again starts fresh, so the same code is delivered again.

Taking pictures. takePicture captures a JPEG from the running session, writes it to the app’s cache directory under Camera/, and resolves with its file URI and upright size. The image is turned upright for the way the phone is held. flash applies to each picture; quality runs from 0 to 1 and sets JPEG compression. Ask for base64 only when you need the bytes in JavaScript, since it copies the whole image across.

Torch and zoom. enableTorch keeps the light on while the camera runs. zoom runs from 0, no zoom, to 1, the camera’s maximum. Changing flash, enableTorch, or zoom adjusts the running camera without restarting it.

Changing props. A change to active, facing, or codeTypes restarts the session with the new settings. Duplicate code types are removed before they reach native code, and an unknown one throws.

Stopping. Setting active to false, unmounting, or leaving the window releases the camera. On iOS, going to the background releases it too, and the view starts again when the app becomes active, which is also when it notices a permission granted in the system prompt or Settings. On Android, CameraX follows the activity’s lifecycle, pausing in the background and resuming in the foreground.

Interruptions on iOS. When another app or a call takes the camera, the view reports inactive and restarts once the interruption ends. If the media services reset, it restarts; any other runtime error reports failed.

API reference

CameraViewPropsTypeDescription
activeboolean = trueRun the camera and scanning.
boolean = trueRun the camera and scanning.
facingCameraFacing = "back"back or front.
CameraFacing = "back"back or front.
codeTypesreadonly CameraCodeType[] = ["qr"]The codes to scan. Empty shows the preview only.
readonly CameraCodeType[] = ["qr"]The codes to scan. Empty shows the preview only.
flashCameraFlashMode = "off"Flash for each picture: off, on, or auto.
CameraFlashMode = "off"Flash for each picture: off, on, or auto.
enableTorchboolean = falseKeep the light on while the camera runs.
boolean = falseKeep the light on while the camera runs.
zoomnumber = 0From 0, no zoom, to 1, the maximum.
number = 0From 0, no zoom, to 1, the maximum.
onStateChange(state: CameraState) => voidCalled when the camera state changes.
(state: CameraState) => voidCalled when the camera state changes.
onCodeScanned(code: CameraCode) => voidCalled with each code found.
(code: CameraCode) => voidCalled with each code found.
CameraCodeTypeDescription
typeCameraCodeTypeWhich kind of code it is.
CameraCodeTypeWhich kind of code it is.
datastringThe decoded text.
stringThe decoded text.
CameraViewRefTypeDescription
takePicture(options?: CameraPictureOptions) => Promise<CameraCapturedPicture>Take a picture from the running camera.
(options?: CameraPictureOptions) => Promise<CameraCapturedPicture>Take a picture from the running camera.
CameraPictureOptionsTypeDescription
qualitynumber = 1JPEG quality from 0 to 1.
number = 1JPEG quality from 0 to 1.
base64boolean = falseAlso return the image as base64.
boolean = falseAlso return the image as base64.
CameraCapturedPictureTypeDescription
uristringA file URI in the cache directory.
stringA file URI in the cache directory.
widthnumberUpright width in pixels.
numberUpright width in pixels.
heightnumberUpright height in pixels.
numberUpright height in pixels.
format'jpg'The image format.
'jpg'The image format.
base64stringThe JPEG as base64, when asked for.
stringThe JPEG as base64, when asked for.

takePicture rejects with a code:

CodeMeans
E_CAMERA_NOT_READYThe view is not ready: off, no permission, or no camera
E_CAMERA_INACTIVEThe camera stopped or restarted before the picture finished
E_CAMERA_CAPTUREThe camera failed to produce an image
E_CAMERA_UNMOUNTEDThe view unmounted first

CameraCodeType is qr, ean13, ean8, upce, code128, code39, code93, pdf417, aztec, or dataMatrix. The view also takes every React Native view prop, such as style.

StateMeans
inactiveOff, off screen, or interrupted
permission-requiredCamera permission is not granted
unavailableNo camera faces that way
readyPreviewing, scanning, and able to take pictures
unsupportedNone of the code types can be read
failedThe camera could not start

Platform differences

iOSAndroidWeb
EngineAVFoundationCameraX and ML KitThrows
Picture encodingCamera JPEG as capturedDecoded, turned upright, re-encoded
Repeated codeEvery frameOnce in a row
In backgroundStops, then restartsPaused
Permission granted laterNotices on returnNeeds a new start
First state when offInactiveNone

Limits and gotchas

Examples

Show the state while scanning

import { useState } from 'react'
import { Text, View } from 'react-native'
import { One } from 'one'
type CameraState = 'inactive' | 'permission-required' | 'unavailable' | 'ready' | 'unsupported' | 'failed'
const messages: Record<CameraState, string> = {
inactive: 'Camera off',
'permission-required': 'Allow camera access in Settings',
unavailable: 'No camera found',
ready: 'Point at a code',
unsupported: 'This camera cannot read these codes',
failed: 'The camera could not start',
}
function ScannerWithStatus({ onCode }: { onCode: (data: string) => void }) {
const [state, setState] = useState<CameraState>('inactive')
return (
<View style={{ flex: 1 }}>
<One.Android.CameraView style={{ flex: 1 }} onStateChange={setState} onCodeScanned={({ data }) => onCode(data)} />
<Text>{messages[state]}</Text>
</View>
)
}

Every state gets a message, so people always know why they see no preview. One.Android.CameraView and One.iOS.CameraView are the same component, so this runs on both platforms.

Scan product barcodes with the front camera

import { useState } from 'react'
import { Button, View } from 'react-native'
import { One } from 'one'
function ProductScanner({ onProduct }: { onProduct: (code: string) => void }) {
const [facing, setFacing] = useState<'back' | 'front'>('back')
return (
<View style={{ flex: 1 }}>
<One.iOS.CameraView
style={{ flex: 1 }}
facing={facing}
codeTypes={['ean13', 'ean8', 'upce']}
onCodeScanned={({ type, data }) => onProduct(`${type}:${data}`)}
/>
<Button title="Flip camera" onPress={() => setFacing(facing === 'back' ? 'front' : 'back')} />
</View>
)
}

Limiting codeTypes to retail barcodes keeps the scanner from firing on unrelated QR codes nearby. Flipping facing restarts the session on the other camera.

Edit this page on GitHub.