[fix](client): scan QR codes with MLKit's barcode output, not the iOS-only object output

Tapping "Scan a QR code" crashed the app on Android:

    com.facebook.react.common.JavascriptException: Error:
    CameraFactory.createObjectOutput(...): java.lang.Error:
    CameraObjectOutput is not available on Android!

`useObjectOutput` is Apple Vision only — the comment claiming v5 ships a
cross-platform code scanner in core was wrong, and the native factory throws as
soon as the screen mounts. The Barcode Scanner output is the cross-platform one
(MLKit on Android, Vision on iOS); it is attached to the same `<Camera>` via
`outputs`, scans `qr-code` only, and keeps the hand-paste fallback. The scan
callback now takes the barcode values directly instead of the object-output
shape.
This commit is contained in:
2026-10-19 15:18:31 +01:00
parent b5c1710b99
commit b32df04ab9
3 changed files with 36 additions and 22 deletions
+32 -22
View File
@@ -14,8 +14,8 @@ import {
Camera,
useCameraDevice,
useCameraPermission,
useObjectOutput,
} from 'react-native-vision-camera';
import { useBarcodeScannerOutput } from 'react-native-vision-camera-barcode-scanner';
import { ScanLine, X } from 'lucide-react-native';
import { AppText, Button, ErrorBanner, Field, Header, TextInputBox } from '../components/ui';
import { useAlarm } from '../state/AlarmContext';
@@ -158,32 +158,42 @@ export default function ScannerScreen() {
[busy, joinAlert, navigation, perms],
);
const onScanned = useCallback(
(objects: readonly { type: string; value?: string }[]) => {
for (const obj of objects) {
const value = typeof obj.value === 'string' ? obj.value : undefined;
if (obj.type !== 'qr' || !value) continue;
const parsed = parseQrPayload(value);
if (parsed.ok) {
if (targetAlertId && parsed.payload.alertId !== targetAlertId) {
setError('That code is for a different alert.');
continue;
}
complete(parsed.payload.alertId, parsed.payload.subscribeToken);
} else {
setError(qrErrorMessage(parsed.error));
// Both platforms scan through MLKit here. The core library's
// `useObjectOutput` is Apple-Vision only: on Android its native factory
// throws `CameraObjectOutput is not available on Android!` the moment the
// screen mounts, which crashed the app on "Scan a QR code". The Barcode
// Scanner output is cross-platform (MLKit on Android, Vision on iOS).
const handleScanValue = useCallback(
(value: string) => {
const parsed = parseQrPayload(value);
if (parsed.ok) {
if (targetAlertId && parsed.payload.alertId !== targetAlertId) {
setError('That code is for a different alert.');
return;
}
return;
complete(parsed.payload.alertId, parsed.payload.subscribeToken);
} else {
setError(qrErrorMessage(parsed.error));
}
},
[complete, targetAlertId],
);
// v5 ships the scanner in core: `useObjectOutput` is the code-scanner — there
// is no separate vision-camera-code-scanner plugin for 5.x.
const objectOutput = useObjectOutput({
types: ['qr'],
onObjectsScanned: onScanned,
const barcodeOutput = useBarcodeScannerOutput({
barcodeFormats: ['qr-code'],
onBarcodeScanned(barcodes) {
for (const barcode of barcodes) {
const value = barcode.rawValue;
if (typeof value === 'string' && value.length > 0) {
handleScanValue(value);
return;
}
}
},
onError(error) {
setError('The camera could not start scanning. You can paste the code by hand.');
console.log('barcode scanner error', error.message);
},
});
const submitManual = async () => {
@@ -236,7 +246,7 @@ export default function ScannerScreen() {
style={StyleSheet.absoluteFill}
device={device}
isActive={!joinedName}
outputs={[objectOutput]}
outputs={[barcodeOutput]}
/>
) : (
<View style={styles.permission}>