← Files Scandit SDKARCHIVED FILE

skills/matrixscan-batch-rn/references/third-party-migration.md

11 KB · Sep 30, 2026 · 23:13 UTC

↓ Download file

# Third-Party Multi-Barcode Scanner → MatrixScan Batch Migration (React Native)

## Before anything else

Read the existing code. Do not ask the user to describe what their scanner does. Identify:

- Which library is in use (read the imports and `package.json` dependencies).
- Which code types / symbologies are enabled.
- How the project collects "all visible barcodes" (a per-frame callback that receives an array of codes, a frame processor, a continuous-scan loop).
- What result-handling logic exists (deduplication by value, accumulation in a `Map` / `Set` / state array, filtering by type / prefix, a running count or summary).
- What data models are defined.
- How the scanner UI is rendered (a full-screen camera component, an embedded preview).

Common third-party multi-barcode scanners in React Native codebases:

- **react-native-vision-camera** (`useCodeScanner`, `Camera` with a `codeScanner` prop) — `useCodeScanner({ codeTypes, onCodeScanned })` calls `onCodeScanned(codes)` with an **array** of every code detected in the frame. This is the multi-barcode pattern closest to BarcodeBatch: the callback fires per frame with all visible codes, and the app dedupes on its own.
- **react-native-camera / RNCamera** (`onGoogleVisionBarcodesDetected`, `onBarCodeRead`) — deprecated; the Google Vision callback reports `barcodes: [...]` per frame.
- **@react-native-ml-kit/barcode-scanning** / expo-barcode-scanner — single-shot or per-frame barcode arrays.

MatrixScan Batch replaces all of the above: it owns the camera, runs the recognizer on every frame, **tracks each barcode across frames (assigning a stable per-barcode tracking `identifier`)**, and reports additions / updates / removals via `BarcodeBatchListener.didUpdateSession`. There is no per-frame array to dedupe from scratch — the same physical barcode keeps the same `identifier` across frames, and new barcodes arrive in `session.addedTrackedBarcodes`.

---

## Remove

- The third-party dependency from `package.json` (e.g. `react-native-vision-camera`).
- The library imports (`Camera`, `useCameraDevice`, `useCodeScanner`, `Code` from `react-native-vision-camera`).
- The scanner hook and its callback (`useCodeScanner({ codeTypes, onCodeScanned })`).
- The library's camera component and its scanner prop (`<Camera ... codeScanner={codeScanner} />`).
- Any UI specific to the old scanner — custom viewfinder, manually-drawn highlight boxes from the per-code bounding boxes. MatrixScan Batch's `DataCaptureView` + `BarcodeBatchBasicOverlay` (or `BarcodeBatchAdvancedOverlay`) replace all of it.

---

## Integrate MatrixScan Batch

Follow `references/integration.md`. The key shape of the rewrite:

1. **Initialize the context once.** `DataCaptureContext.initialize('-- ENTER YOUR SCANDIT LICENSE KEY HERE --')`, then use `DataCaptureContext.sharedInstance`.
2. **Replace the scanner's `codeTypes` with `BarcodeBatchSettings`.** `new BarcodeBatchSettings()` + `settings.enableSymbologies([...])`, mapping each code type using the table below. Then `const barcodeBatch = new BarcodeBatch(settings)` and `dataCaptureContext.setMode(barcodeBatch)`.
3. **Replace the library's camera with the Scandit camera pipeline.** `BarcodeBatch.createRecommendedCameraSettings()` → `Camera.withSettings(settings)` → `dataCaptureContext.setFrameSource(camera)` → `camera.switchToDesiredState(FrameSourceState.On)`. Drive on/off from app state / focus as in integration.md.
4. **Replace the library's camera component with `<DataCaptureView>`.** Render `<DataCaptureView context={dataCaptureContext} ref={...} />` and add the overlay with `view.addOverlay(overlay)` in the `ref` callback.
5. **Replace the `onCodeScanned(codes)` callback with `BarcodeBatchListener.didUpdateSession`.** Use the result-pattern mapping table below.
6. **Replace any manually-drawn highlight with `BarcodeBatchBasicOverlay`.** `new BarcodeBatchBasicOverlay(barcodeBatch, BarcodeBatchBasicOverlayStyle.Frame)` (or `Dot`).

When configuring `BarcodeBatchSettings`, map code types from the old scanner using the table below. **Do not guess or derive Scandit symbology names from the old library's names** — they differ (e.g. VisionCamera's `'qr'` maps to `Symbology.QR`, and `'ean-13'`/`'upc-a'` both map to `Symbology.EAN13UPCA`).

### Symbology mapping

| react-native-vision-camera `CodeType` | Scandit `Symbology.*` |
|---|---|
| `'qr'` | `Symbology.QR` |
| `'ean-13'` | `Symbology.EAN13UPCA` |
| `'ean-8'` | `Symbology.EAN8` |
| `'upc-a'` | `Symbology.EAN13UPCA` (UPC-A is read by the EAN-13/UPC-A symbology in Scandit) |
| `'upc-e'` | `Symbology.UPCE` |
| `'code-39'` | `Symbology.Code39` |
| `'code-93'` | `Symbology.Code93` |
| `'code-128'` | `Symbology.Code128` |
| `'itf'` | `Symbology.InterleavedTwoOfFive` |
| `'codabar'` | `Symbology.Codabar` |
| `'data-matrix'` | `Symbology.DataMatrix` |
| `'aztec'` | `Symbology.Aztec` |
| `'pdf-417'` | `Symbology.PDF417` |

If you encounter a code type not in this table, fetch the [BarcodeBatch API reference](https://docs.scandit.com/data-capture-sdk/react-native/barcode-capture/api.html) for the correct `Symbology` enum value before writing the code.

### Result-pattern mapping

| Old scanner concept | MatrixScan Batch equivalent |
|---|---|
| `onCodeScanned(codes)` per-frame array callback | `BarcodeBatchListener.didUpdateSession(barcodeBatch, session)` — fires per processed frame; `session.addedTrackedBarcodes` reports the codes newly tracked since the last frame. |
| `code.value` | `trackedBarcode.barcode.data` (`string | null`) |
| `code.type` | `trackedBarcode.barcode.symbology` (a `Symbology` enum value) |
| Per-code bounding box (`code.frame` / `code.corners`) | `trackedBarcode.location` (a `Quadrilateral` in image-space; the basic overlay draws the highlight for you — requires the MatrixScan AR add-on) |
| "Have I seen this code yet?" (manual `Map`/`Set` dedupe on `code.value`) | `trackedBarcode.identifier` is the stable per-barcode tracking id. Dedupe on the identifier rather than the value: the same physical barcode keeps its identifier across frames, and new barcodes arrive in `session.addedTrackedBarcodes`. |
| "Which barcodes are currently visible?" | `session.trackedBarcodes` — a `{ [identifier: string]: TrackedBarcode }` map. Iterate with `Object.values(session.trackedBarcodes)`. |
| A running unique count / summary | Keep your `Set<number>` (or `Map`) of identifiers; update it from `session.addedTrackedBarcodes` and render its `.size`. |

---

## Preserve

- Custom data models — keep them as-is.
- Result accumulation and deduplication logic — move it into `didUpdateSession`. Iterate `session.addedTrackedBarcodes`, dedupe by `trackedBarcode.identifier`, and append to the existing collection / update state.
- The running-count or summary display — drive it from the deduped identifier set.
- Any downstream business logic triggered on a new barcode (network lookup, navigation).
- Validation / reject behavior — if the old scanner had an "is this code valid?" check, port it as a guard when iterating `addedTrackedBarcodes`.

> **Session safety**: The session is only safe to access from inside `didUpdateSession`. Copy the values you keep (`barcode.data`, `identifier`) out of the callback — do not store the `session` object or its arrays and read them later.

---

## Putting it all together

A typical "vision-camera `useCodeScanner` replaced with MatrixScan Batch" shape:

```tsx
import React, { useEffect, useRef, useState } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import {
  BarcodeBatch,
  BarcodeBatchBasicOverlay,
  BarcodeBatchBasicOverlayStyle,
  BarcodeBatchSession,
  BarcodeBatchSettings,
  Symbology,
  TrackedBarcode,
} from 'scandit-react-native-datacapture-barcode';
import {
  Camera,
  DataCaptureContext,
  DataCaptureView,
  FrameSourceState,
} from 'scandit-react-native-datacapture-core';

DataCaptureContext.initialize('-- ENTER YOUR SCANDIT LICENSE KEY HERE --');
const dataCaptureContext = DataCaptureContext.sharedInstance;

export const ScanScreen = () => {
  const viewRef = useRef<DataCaptureView>(null);
  const cameraRef = useRef<Camera | null>(null);
  // Dedupe by stable tracking identifier, mirroring the old Map-by-value dedupe.
  const seenIdentifiers = useRef<Set<number>>(new Set());
  const [uniqueCount, setUniqueCount] = useState(0);

  const barcodeBatchRef = useRef<BarcodeBatch>(null!);
  if (!barcodeBatchRef.current) {
    const settings = new BarcodeBatchSettings();
    settings.enableSymbologies([
      Symbology.EAN13UPCA, // 'ean-13'
      Symbology.Code128,   // 'code-128'
      Symbology.QR,        // 'qr'
    ]);

    const batch = new BarcodeBatch(settings);
    batch.addListener({
      didUpdateSession: async (_batch: BarcodeBatch, session: BarcodeBatchSession) => {
        // Same dedupe-and-count as the old onCodeScanned, but driven by per-frame deltas.
        session.addedTrackedBarcodes.forEach((trackedBarcode: TrackedBarcode) => {
          if (trackedBarcode.barcode.data && !seenIdentifiers.current.has(trackedBarcode.identifier)) {
            seenIdentifiers.current.add(trackedBarcode.identifier);
          }
        });
        setUniqueCount(seenIdentifiers.current.size);
      },
    });

    dataCaptureContext.setMode(batch);
    barcodeBatchRef.current = batch;
  }

  const overlayRef = useRef<BarcodeBatchBasicOverlay>(null!);
  if (!overlayRef.current) {
    overlayRef.current = new BarcodeBatchBasicOverlay(
      barcodeBatchRef.current,
      BarcodeBatchBasicOverlayStyle.Frame,
    );
  }

  useEffect(() => {
    const initCamera = async () => {
      if (!cameraRef.current) {
        const cameraSettings = BarcodeBatch.createRecommendedCameraSettings();
        const camera = Camera.withSettings(cameraSettings);
        if (!camera) throw new Error('No camera available');
        await dataCaptureContext.setFrameSource(camera);
        await camera.switchToDesiredState(FrameSourceState.On);
        cameraRef.current = camera;
      }
    };
    void initCamera();

    return () => {
      barcodeBatchRef.current.isEnabled = false;
      dataCaptureContext.removeMode(barcodeBatchRef.current);
    };
  }, []);

  return (
    <View style={styles.container}>
      <DataCaptureView
        style={StyleSheet.absoluteFill}
        context={dataCaptureContext}
        ref={view => {
          if (view && !viewRef.current) {
            view.addOverlay(overlayRef.current);
            viewRef.current = view;
          }
        }}
      />
      <View style={styles.counter}>
        <Text style={styles.counterText}>Unique codes: {uniqueCount}</Text>
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: { flex: 1 },
  counter: { position: 'absolute', top: 48, left: 16 },
  counterText: { color: '#fff', fontSize: 16, fontWeight: 'bold' },
});
```

---

When done, show only what changed. Do not list APIs that were unchanged. Include the setup checklist from `references/integration.md` so the user knows which packages to install (`scandit-react-native-datacapture-core`, `scandit-react-native-datacapture-barcode`), to run `npx pod-install` for iOS, to add `NSCameraUsageDescription` to `ios/<App>/Info.plist`, and to replace the license key placeholder.

SHA-256: 2d661ba5c3e3acbb984d709272224ebd9708ad2c14917e5067e3d1757c814fcc