Skip to content

feat: Initial release of React Native Image Code Scanner v0.1.0 - #1

Closed
anngth wants to merge 3 commits into
mainfrom
fix_error
Closed

feat: Initial release of React Native Image Code Scanner v0.1.0#1
anngth wants to merge 3 commits into
mainfrom
fix_error

Conversation

@anngth

@anngth anngth commented Aug 26, 2025

Copy link
Copy Markdown
Owner

📝 Description

This PR introduces the first release of react-native-image-code-scanner, a high-performance React Native library for scanning QR codes and barcodes from images with automatic preprocessing for optimal recognition.

✨ Key Features

  • 🚀 Native Performance: iOS Vision Framework & Android ML Kit
  • 📱 Cross-Platform: Full iOS and Android support
  • 🎯 13+ Barcode Formats: QR, Code128, EAN, UPC, PDF417, Data Matrix, and more
  • 🔧 Automatic Preprocessing: Smart image enhancement for better recognition
  • New Architecture Ready: Full Turbo Modules support
  • 🛠️ Expo Compatible: Works with Expo (requires prebuild)

🔄 Changes Made

Core Library

  • ✅ Native iOS implementation using Vision Framework
  • ✅ Native Android implementation using ML Kit
  • ✅ TypeScript bindings with full type safety
  • ✅ Automatic preprocessing (grayscale, contrast, rotation)
  • ✅ Ultra-simple API - just pass image path and formats

Documentation & Examples

  • ✅ Comprehensive README with API reference
  • ✅ Full Expo example app with modern UI
  • ✅ Installation and troubleshooting guides
  • ✅ CHANGELOG and compatibility matrix

Build & CI/CD

  • ✅ GitHub Actions workflows for CI
  • ✅ Automated publishing workflow
  • ✅ Pre-publish validation scripts
  • ✅ ESLint and TypeScript configurations

📦 API Overview

// Simple, intuitive API
const results = await ImageCodeScanner.scan({
  path: imagePath,
  formats: [BarcodeFormat.QR_CODE] // Optional, defaults to QR
});

Preprocessing is always automatic - no configuration needed!

🧪 Testing

  • iOS simulator testing
  • Android emulator testing
  • TypeScript compilation
  • ESLint validation
  • Package structure validation
  • Example app functionality

📋 Pre-publish Checklist

  • All tests pass
  • Documentation complete
  • CHANGELOG updated
  • Version set to 0.1.0
  • npm authentication verified
  • Package size: 15.5 kB (packed)

🚀 Next Steps

After merging:

  1. Publish to npm: npm publish or npm publish --tag beta
  2. Create GitHub release with tag v0.1.0
  3. Monitor npm package page for user feedback

📸 Screenshots

The library includes a comprehensive example app demonstrating all features:

  • Image selection from camera/gallery
  • Multiple barcode format selection
  • Automatic preprocessing information
  • Real-time scanning with performance metrics

🔗 Related Links

🏷️ Type of Change

  • New feature (non-breaking change which adds functionality)
  • Documentation update
  • This change requires a documentation update

✅ Quality Assurance

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • New and existing unit tests pass locally with my changes

Ready for review and merge! 🎉

Summary by CodeRabbit

  • New Features
    • Automatic image preprocessing enabled by default (grayscale, contrast, rotations) for better recognition.
    • Simplified API: provide image path and formats only.
    • Improved iOS scanning reliability and results.
  • Documentation
    • README updated to reflect automatic preprocessing and simplified API.
    • New comprehensive compatibility guide (React Native, React, Expo, platforms).
    • Example app/docs updated; changelog date adjusted.
  • Chores
    • Dependency updates and peer range alignment; example app dependencies refreshed.
    • Project configuration and ignore rules updated.
  • Style
    • Linting and formatting refinements with no behavior changes.

@coderabbitai

coderabbitai Bot commented Aug 27, 2025

Copy link
Copy Markdown

Walkthrough

The PR standardizes automatic image preprocessing across the API and implementation, removes user-configurable preprocessing options, updates documentation and example app accordingly, adds a new iOS native scanner with Vision-based processing, refactors JS glue, and adjusts tooling/configs and dependencies.

Changes

Cohort / File(s) Summary
Documentation updates
README.md, CHANGELOG.md, COMPATIBILITY.md, example/README.md
Docs revised to reflect automatic preprocessing, simplified API (removed preprocessing/platformOverrides), updated release date, added compatibility guide, and adjusted example notes.
Core JS API and native spec
src/index.tsx, src/NativeImageCodeScanner.ts
Public API simplified: removed PreprocessingOptions and platformOverrides from ScanOptions; JS now always passes fixed native preprocessing flags; spec formatting only.
iOS native implementation
ios/ImageCodeScanner.swift
Added Vision-based scanner with preprocessing variants (grayscale, contrast, rotations), async iterative scanning, QR fallback, and RN-bridged method scanFromPath:formats:options:resolver:rejecter:.
Example app code
example/App.tsx, example/src/App.tsx
Replaced re-export with full app; removed preprocessing controls; added format selection, image pick/permission flow, scan timing, results UI, and notes on automatic preprocessing.
Example app config/deps
example/package.json, example/babel.config.js
Dependency bumps (expo-image-picker, expo-status-bar), removed expo-permissions and @types/react-native; cleaned babel config imports.
Repo config and tooling
.gitignore, eslint.config.mjs, scripts/validate-package.js, package.json
Ignore yarn.lock; restructured ESLint config with explicit ignores and Prettier options; formatting-only script tweaks; dependency ranges adjusted (RN/React peer floors, devDeps changes).

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant App as Example App (JS)
  participant API as ImageCodeScanner (JS)
  participant Native as Native Module
  participant iOS as iOS Vision Pipeline

  User->>App: Tap "Scan Image"
  App->>API: scan({ path, formats })
  note right of API: Preprocessing always enabled (flags fixed)
  API->>Native: scanFromPath(path, formats, { enhanceContrast, grayscale, rotations })

  par Prepare variants
    Native->>iOS: Load original image
    iOS-->>Native: Original
    Native->>iOS: Grayscale/Contrast
    iOS-->>Native: Enhanced variants
    Native->>iOS: Rotations (0°, 90°, 180°, 270°)
    iOS-->>Native: Rotated variants
  end

  loop For each variant
    Native->>iOS: VNDetectBarcodesRequest(formats)
    iOS-->>Native: Results or none
  end

  alt Codes found
    Native-->>API: Resolve [payloads]
    API-->>App: Results
    App-->>User: Show results list
  else None found or error
    Native->>iOS: Fallback QR (CIDetector)
    iOS-->>Native: Results or none
    alt Any results
      Native-->>API: Resolve [payloads]
      API-->>App: Results
    else
      Native-->>API: Resolve []
      API-->>App: No results
      App-->>User: Notify no codes
    end
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

I twitch my ears at preprocessing’s glow,
Rotations spin—0, 90, 180—go!
Vision sniffs the stripes and squares,
JS and Swift now match their fares.
Docs aligned, the demo’s keen—
Hop, scan, cheer: results on-screen! 🐇📷✨

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix_error

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
README.md (1)

372-379: Example app bullets reference “real-time preprocessing option toggles,” which were removed.

Align the bullets with the new UI that only shows informational text.

-- Real-time preprocessing option toggles
+- Automatic preprocessing (no toggles required)
🧹 Nitpick comments (26)
.gitignore (1)

71-71: Do not ignore yarn.lock; commit it for reproducible installs/CI.

For Yarn 3 workspaces, keeping the lockfile in VCS avoids resolution drift and speeds CI. Recommend removing this ignore.

- yarn.lock
scripts/validate-package.js (2)

6-8: Nit: single-line log is fine; current multi-line call is noisy.

Functionally identical; consider reverting to a single line to keep the script compact.

-console.log(
-  '🔍 Validating React Native Image Code Scanner package structure...\n'
-);
+console.log('🔍 Validating React Native Image Code Scanner package structure...\n');

86-91: Avoid warning on lib/ — it’s expected after bob build; warn only if tracked.

lib is a build artifact but expected locally; current check creates false positives. Either drop lib from unwantedDirs or only warn if files under lib are tracked by git.

-const unwantedDirs = ['modules', 'build', 'lib', '.turbo'];
+const unwantedDirs = ['modules', 'build', '.turbo'];

Optional: add a tracked-files check.

+const { execSync } = require('child_process');
@@
 // Check for unwanted files
-const unwantedDirs = ['modules', 'build', 'lib', '.turbo'];
+const unwantedDirs = ['modules', 'build', '.turbo'];
 unwantedDirs.forEach((dir) => {
   if (fs.existsSync(path.join(__dirname, '..', dir))) {
     warnings.push(`⚠️  Found directory that should be cleaned: ${dir}`);
   }
 });
+
+// Warn only if lib has tracked files (don’t commit build artifacts)
+try {
+  const tracked = execSync('git ls-files lib').toString().trim();
+  if (tracked) warnings.push('⚠️  lib contains tracked files; avoid committing build artifacts.');
+} catch {}
package.json (1)

35-50: Run the validator in prepublishOnly (and add a script target).

Helps catch packaging issues before publish.

   "scripts": {
@@
-    "prepublishOnly": "yarn clean && yarn typecheck && yarn lint && yarn test && yarn build",
+    "validate": "node scripts/validate-package.js",
+    "prepublishOnly": "yarn clean && yarn typecheck && yarn lint && yarn test && yarn build && node scripts/validate-package.js",
example/README.md (1)

97-99: Docs now claim “automatic,” but other sections still mention toggles — align messaging

These bullets are good. However, earlier sections still promise “Preprocessing Options,” “Real-time Configuration,” and step 3 says “Configure Preprocessing,” which conflicts with “automatic” behavior.

  • Update Features (Lines 10–12) to remove “Preprocessing Options” and “Real-time Configuration.”
  • Update Usage (Lines 91–93) to remove “Configure Preprocessing.”
  • Update Troubleshooting (Lines 121–124) to remove “Try enabling preprocessing options.”
  • Update Development (Lines 156–158) to remove “Update preprocessing options in the state.”

I can draft a PR-ready edit if you want.

CHANGELOG.md (2)

10-22: Release date and “automatic preprocessing” look good — add a verification note and tighten phrasing

  • Keep 2025-08-27; before publishing, verify the tag URL resolves (Line 50).
  • Minor copy polish for consistency:
    • “Multiple rotation attempts” → “Multiple rotation attempts: 0°, 90°, 180°, 270°.”
    • “Ultra-simple API – just pass image path and formats; preprocessing is automatic.”

If you’d like, I’ll push wording tweaks.


31-33: Combine these two bullets to avoid repetition

Consider: “Automatic preprocessing enabled by default (contrast, grayscale, rotations) with smart retry logic.”

eslint.config.mjs (2)

18-19: Ignoring example/ may hide issues in the showcased app**

Consider dropping 'example/**' from ignores so the example app stays linted; it’s often the first integration point users see.


27-33: Add prettier “turn-off-conflicts” to avoid rule clashes

With flat config, keep the plugin rule, but also disable conflicting stylistic rules:

   ...fixupConfigRules(compat.extends('@react-native')),
+  ...fixupConfigRules(compat.extends('prettier')),

This mirrors eslint-config-prettier’s effect under FlatCompat.

ios/ImageCodeScanner.swift (2)

181-185: Use the Core Image QR fallback before returning empty results

Call the existing fallback when Vision finds nothing:

-        print("ImageCodeScanner iOS - No barcodes found after trying all preprocessing options")
-        safeResolve([])
+        print("ImageCodeScanner iOS - No Vision results; trying CI QR fallback")
+        tryQRCodeFallback(image: originalImage, safeResolve: safeResolve, safeReject: safeReject)

83-136: Prefer RCTLog over print for RN-friendly logging

Swap print(...) with RCTLogInfo/RCTLogWarn/RCTLogError to make logs consistent with RN tooling and filters.

src/NativeImageCodeScanner.ts (1)

5-13: Type sync check across platforms; otherwise looks good

  • Ensure Android and iOS both accept the same options keys (enhanceContrast, convertToGrayscale, tryRotations) even if currently ignored.
  • If the public JS API always uses automatic preprocessing internally, consider marking options as internal-only or making it optional.

Optional tightenings:

  • formats: readonly string[]
  • Replace string union with an enum for known formats to catch typos at compile time.

I can push the types refinement if desired.

COMPATIBILITY.md (6)

3-20: Stop forecasting unreleased RN versions; mark “tested up to” with a date.

Claiming support for 0.80.x+ (“Ready when released”) can mislead. Prefer “Tested up to RN as of 2025-08-27.” Remove the unreleased row or mark as TBD.

Apply:

-| 0.80.x+     | 🔜 0.1.x       | New Architecture    | Ready when released |
+| (TBD)       | —              | —                   | Pending upstream release |

53-64: Reword Android requirements to align with RN templates and avoid stale numbers.

Hardcoding 33+ may drift. Recommend “use the RN template’s compile/target SDK,” and list ML Kit as the only required dep for image scanning (CameraX is optional).

- - **Target SDK**: 33+ (Android 13+)
- - **Compile SDK**: 33+
- - **Kotlin**: 1.6.0+
- - **Gradle**: 7.0+
- - **Android Gradle Plugin**: 7.0+
- - **Dependencies**:
-   - Google ML Kit Barcode Scanning: 17.3.0+
-   - AndroidX Camera Core: 1.3.1+
+ - **Target/Compile SDK**: Match the React Native template for your RN version
+ - **Kotlin/Gradle/AGP**: Match the React Native template (avoid overriding in library projects)
+ - **Dependencies**:
+   - Required: Google ML Kit Barcode Scanning (play-services-mlkit-barcode-scanning)
+   - Optional: CameraX (only if you add live camera scanning in the future)

If CameraX isn’t actually used anywhere, please remove it from docs entirely to prevent confusion.


88-109: New Architecture enablement instructions: add a caution for mixed-arch pods/gradle caches.

A one-liner note to “clean pods/Gradle” reduces common migration failures.

 cd ios && RCT_NEW_ARCH_ENABLED=1 pod install
+## If switching architectures, clean caches:
+## iOS: rm -rf ~/Library/Developer/Xcode/DerivedData && pod deintegrate && pod install
+## Android: ./gradlew clean

146-151: Use a neutrally stable init command and avoid pinning a possibly non-existent patch.

Pinning “0.79.2” may break when that tag isn’t available locally.

-npx react-native init TestApp --version 0.79.2
+npx react-native@latest init TestApp --version <desired RN version>

Confirm the exact RN versions you validated and replace with those numbers.


175-180: Types guidance likely outdated—RN ships its own types.

Advising to install @types/react-native can conflict with RN’s bundled types for modern versions.

- npm install --save-dev @types/react@^18.0.0 @types/react-native@^0.72.0
+ npm install --save-dev @types/react@^18
+ # React Native provides its own TypeScript types; no extra @types/react-native needed for modern RN.

Please verify the minimum RN where bundled types are reliable in your test matrix and reflect that here.


201-207: Add a “Last verified” note to Resources.

Helps readers interpret version tables without guessing freshness.

 - [Package Changelog](./CHANGELOG.md)
+ - [Package Changelog](./CHANGELOG.md)
+
+Last verified against React Native <version> on 2025-08-27.
README.md (3)

144-166: Object.values(BarcodeFormat) may not type-check without a cast.

In TS, Object.values on a string enum yields string[]. Cast to BarcodeFormat[] to satisfy ScanOptions.

-      formats: Object.values(BarcodeFormat), // All supported formats
+      formats: Object.values(BarcodeFormat) as BarcodeFormat[], // All supported formats

306-314: Terminology drift: “Smart Retry Logic” contradicts the earlier removal of user-configurable preprocessing.

Consider renaming to “Automatic retry strategy” and ensure it’s clearly non-configurable.

-- **Smart Retry Logic**: If initial scan fails, automatically tries with different preprocessing techniques
+- **Automatic retry strategy**: If initial scan fails, the scanner automatically retries with different preprocessing techniques

53-60: Compatibility table claims “0.80.x+ fully supported (when released).”

Mirror COMPATIBILITY.md guidance and avoid promising future compatibility.

-| 0.80.x+             | ✅ 0.1.x       | Fully Supported (when released) |
+| (TBD)               | —              | Pending upstream release |

Add “Tested up to RN as of 2025-08-27” below the table.

src/index.tsx (1)

31-35: Type nativeOptions and consider lifting to a const export for reuse/testing.

Improves readability and prevents drift with native signature.

-    const nativeOptions = {
+    type NativeOptions = {
+      enhanceContrast: boolean;
+      convertToGrayscale: boolean;
+      tryRotations: boolean;
+    };
+    const nativeOptions: NativeOptions = {
       enhanceContrast: true,
       convertToGrayscale: true,
       tryRotations: true,
     };

Confirm NativeImageCodeScanner.scanFromPath(path, string[], options) matches this shape exactly across iOS/Android.

example/src/App.tsx (3)

21-25: Remove unused field from ScanResult.

preprocessingUsed isn’t set or displayed.

-interface ScanResult {
-  data: string[];
-  time: number;
-  preprocessingUsed?: string;
-}
+interface ScanResult {
+  data: string[];
+  time: number;
+}

213-217: Disable “Scan” when no formats are selected.

Extra guard to avoid a no-op call.

-          disabled={!selectedImage || isScanning}
+          disabled={!selectedImage || isScanning || selectedFormats.length === 0}

44-58: Ask only for necessary permissions per action.

Requesting camera access when opening the gallery (and vice versa) adds friction.

Refactor requestPermissions to accept the action and request only the relevant permission. I can draft a patch if you want it integrated now.

example/App.tsx (1)

25-32: Drop unused “enabled” hints in BARCODE_FORMATS.

They’re never read; state drives selection.

-const BARCODE_FORMATS = [
-  { key: BarcodeFormat.QR_CODE, label: 'QR Code', enabled: true },
-  { key: BarcodeFormat.CODE_128, label: 'Code 128', enabled: false },
-  { key: BarcodeFormat.CODE_39, label: 'Code 39', enabled: false },
-  { key: BarcodeFormat.EAN_13, label: 'EAN-13', enabled: false },
-  { key: BarcodeFormat.PDF_417, label: 'PDF417', enabled: false },
-  { key: BarcodeFormat.DATA_MATRIX, label: 'Data Matrix', enabled: false },
-];
+const BARCODE_FORMATS = [
+  { key: BarcodeFormat.QR_CODE, label: 'QR Code' },
+  { key: BarcodeFormat.CODE_128, label: 'Code 128' },
+  { key: BarcodeFormat.CODE_39, label: 'Code 39' },
+  { key: BarcodeFormat.EAN_13, label: 'EAN-13' },
+  { key: BarcodeFormat.PDF_417, label: 'PDF417' },
+  { key: BarcodeFormat.DATA_MATRIX, label: 'Data Matrix' },
+];
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between eb5f74d and c7c608a.

📒 Files selected for processing (15)
  • .gitignore (1 hunks)
  • CHANGELOG.md (2 hunks)
  • COMPATIBILITY.md (1 hunks)
  • README.md (11 hunks)
  • eslint.config.mjs (1 hunks)
  • example/App.tsx (1 hunks)
  • example/README.md (1 hunks)
  • example/babel.config.js (1 hunks)
  • example/package.json (1 hunks)
  • example/src/App.tsx (9 hunks)
  • ios/ImageCodeScanner.swift (1 hunks)
  • package.json (2 hunks)
  • scripts/validate-package.js (4 hunks)
  • src/NativeImageCodeScanner.ts (1 hunks)
  • src/index.tsx (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
ios/ImageCodeScanner.swift (1)
android/src/main/java/com/imagecodescanner/ImageCodeScannerModule.kt (4)
  • convertToGrayscale (77-93)
  • name (19-265)
  • enhanceContrast (48-75)
  • scanFromPath (101-260)
example/App.tsx (1)
example/src/App.tsx (1)
  • App (36-248)
🪛 LanguageTool
COMPATIBILITY.md

[grammar] ~23-~23: There might be a mistake here.
Context: ...sions | React Version | Compatibility | |--------------|---------------| | 17.x ...

(QB_NEW_EN)


[grammar] ~24-~24: There might be a mistake here.
Context: ...ility | |--------------|---------------| | 17.x | ✅ Supported | | 18.x ...

(QB_NEW_EN)


[grammar] ~25-~25: There might be a mistake here.
Context: ...-------| | 17.x | ✅ Supported | | 18.x | ✅ Supported (Recommende...

(QB_NEW_EN)


[grammar] ~26-~26: There might be a mistake here.
Context: ....x | ✅ Supported (Recommended) | | 19.x | ✅ Supported (Beta) | #...

(QB_NEW_EN)


[grammar] ~31-~31: There might be a mistake here.
Context: ...eact Native | Package Version | Status | |----------|-------------|--------------...

(QB_NEW_EN)


[grammar] ~32-~32: There might be a mistake here.
Context: ...------------|-----------------|--------| | SDK 49 | 0.72.x | ✅ 0.1.x ...

(QB_NEW_EN)


[grammar] ~33-~33: There might be a mistake here.
Context: ... | Supported (requires prebuild) | | SDK 50 | 0.73.x | ✅ 0.1.x ...

(QB_NEW_EN)


[grammar] ~34-~34: There might be a mistake here.
Context: ... | Supported (requires prebuild) | | SDK 51 | 0.74.x | ✅ 0.1.x ...

(QB_NEW_EN)


[grammar] ~35-~35: There might be a mistake here.
Context: ... | Supported (requires prebuild) | | SDK 52 | 0.79.x | ✅ 0.1.x ...

(QB_NEW_EN)


[grammar] ~44-~44: There might be a mistake here.
Context: ...rements - Minimum iOS Version: 13.4 - Xcode: 14.0 or higher - Swift: 5.0...

(QB_NEW_EN)


[grammar] ~45-~45: There might be a mistake here.
Context: ...sion**: 13.4 - Xcode: 14.0 or higher - Swift: 5.0 or higher - **Frameworks Re...

(QB_NEW_EN)


[grammar] ~46-~46: There might be a mistake here.
Context: ...4.0 or higher - Swift: 5.0 or higher - Frameworks Required: - Vision Framew...

(QB_NEW_EN)


[grammar] ~47-~47: There might be a mistake here.
Context: ...5.0 or higher - Frameworks Required: - Vision Framework (iOS 11+) - Core Imag...

(QB_NEW_EN)


[grammar] ~48-~48: There might be a mistake here.
Context: ...quired**: - Vision Framework (iOS 11+) - Core Image (iOS 5+) - UIKit (iOS 2+) ...

(QB_NEW_EN)


[grammar] ~49-~49: There might be a mistake here.
Context: ...mework (iOS 11+) - Core Image (iOS 5+) - UIKit (iOS 2+) - Core Graphics (iOS 2+...

(QB_NEW_EN)


[grammar] ~50-~50: There might be a mistake here.
Context: ...- Core Image (iOS 5+) - UIKit (iOS 2+) - Core Graphics (iOS 2+) ### Android Requ...

(QB_NEW_EN)


[grammar] ~55-~55: There might be a mistake here.
Context: ...Minimum SDK**: 21 (Android 5.0 Lollipop) - Target SDK: 33+ (Android 13+) - **Comp...

(QB_NEW_EN)


[grammar] ~56-~56: There might be a mistake here.
Context: ...pop) - Target SDK: 33+ (Android 13+) - Compile SDK: 33+ - Kotlin: 1.6.0+ ...

(QB_NEW_EN)


[grammar] ~57-~57: There might be a mistake here.
Context: ...33+ (Android 13+) - Compile SDK: 33+ - Kotlin: 1.6.0+ - Gradle: 7.0+ - **...

(QB_NEW_EN)


[grammar] ~58-~58: There might be a mistake here.
Context: ...Compile SDK*: 33+ - Kotlin: 1.6.0+ - Gradle: 7.0+ - **Android Gradle Plugin...

(QB_NEW_EN)


[grammar] ~59-~59: There might be a mistake here.
Context: ... - Kotlin: 1.6.0+ - Gradle: 7.0+ - Android Gradle Plugin: 7.0+ - **Depend...

(QB_NEW_EN)


[grammar] ~60-~60: There might be a mistake here.
Context: ...: 7.0+ - Android Gradle Plugin: 7.0+ - Dependencies: - Google ML Kit Barcod...

(QB_NEW_EN)


[grammar] ~61-~61: There might be a mistake here.
Context: ...radle Plugin**: 7.0+ - Dependencies: - Google ML Kit Barcode Scanning: 17.3.0+ ...

(QB_NEW_EN)


[grammar] ~62-~62: There might be a mistake here.
Context: ... Google ML Kit Barcode Scanning: 17.3.0+ - AndroidX Camera Core: 1.3.1+ ### Node.j...

(QB_NEW_EN)


[grammar] ~67-~67: There might be a mistake here.
Context: ... Node.js: >=18.0.0 (LTS recommended) - npm: >=8.0.0 - Yarn: >=1.22.0 or >...

(QB_NEW_EN)


[grammar] ~68-~68: There might be a mistake here.
Context: ...0.0 (LTS recommended) - npm: >=8.0.0 - Yarn: >=1.22.0 or >=3.0.0 (Berry) ## ...

(QB_NEW_EN)


[grammar] ~155-~155: There might be a mistake here.
Context: ... Testing The library is tested against: - React Native 0.70.x (Old Architecture) -...

(QB_NEW_EN)


[grammar] ~156-~156: There might be a mistake here.
Context: ...- React Native 0.70.x (Old Architecture) - React Native 0.75.x (Both Architectures)...

(QB_NEW_EN)


[grammar] ~157-~157: There might be a mistake here.
Context: ...React Native 0.75.x (Both Architectures) - React Native 0.79.x (New Architecture) -...

(QB_NEW_EN)


[grammar] ~158-~158: There might be a mistake here.
Context: ...- React Native 0.79.x (New Architecture) - Latest React Native release ## Known Is...

(QB_NEW_EN)


[grammar] ~184-~184: There might be a mistake here.
Context: ... Latest 3 minor versions of React Native - Security Updates: Latest 6 minor versi...

(QB_NEW_EN)


[grammar] ~185-~185: There might be a mistake here.
Context: ... Latest 6 minor versions of React Native - Best Effort: Older versions on case-by...

(QB_NEW_EN)


[grammar] ~186-~186: There might be a mistake here.
Context: ...ative - Best Effort: Older versions on case-by-case basis ## Reporting Compat...

(QB_NEW_EN)


[grammar] ~194-~194: There might be a mistake here.
Context: ...nner/issues) 3. Create a new issue with: - React Native version - Package versio...

(QB_NEW_EN)


[grammar] ~195-~195: There might be a mistake here.
Context: ...ew issue with: - React Native version - Package version - Platform (iOS/Andro...

(QB_NEW_EN)


[grammar] ~196-~196: There might be a mistake here.
Context: ...eact Native version - Package version - Platform (iOS/Android) - Architecture...

(QB_NEW_EN)


[grammar] ~197-~197: There might be a mistake here.
Context: ...kage version - Platform (iOS/Android) - Architecture (Old/New) - Error messag...

(QB_NEW_EN)


[grammar] ~198-~198: There might be a mistake here.
Context: ...iOS/Android) - Architecture (Old/New) - Error messages/logs ## Resources - [Re...

(QB_NEW_EN)

README.md

[grammar] ~55-~55: There might be a mistake here.
Context: ...ive Version | Package Version | Status | |---------------------|-----------------...

(QB_NEW_EN)


[grammar] ~56-~56: There might be a mistake here.
Context: ...------------|-----------------|--------| | 0.70.x - 0.74.x | ✅ 0.1.x | ...

(QB_NEW_EN)


[grammar] ~57-~57: There might be a mistake here.
Context: ... | ✅ 0.1.x | Fully Supported | | 0.75.x - 0.79.x | ✅ 0.1.x | ...

(QB_NEW_EN)


[grammar] ~58-~58: There might be a mistake here.
Context: ...Supported (including New Architecture) | | 0.80.x+ | ✅ 0.1.x | ...

(QB_NEW_EN)


[grammar] ~61-~61: There might be a mistake here.
Context: ...rted (when released) | ### Requirements - React Native: >=0.70.0 - React: >=...

(QB_NEW_EN)


[grammar] ~62-~62: There might be a mistake here.
Context: ...equirements - React Native: >=0.70.0 - React: >=17.0.0 - iOS: 13.4+ - **A...

(QB_NEW_EN)


[grammar] ~63-~63: There might be a mistake here.
Context: ...Native**: >=0.70.0 - React: >=17.0.0 - iOS: 13.4+ - Android: minSdkVersio...

(QB_NEW_EN)


[grammar] ~64-~64: There might be a mistake here.
Context: ...0 - React: >=17.0.0 - iOS: 13.4+ - Android: minSdkVersion 21+ - Node:...

(QB_NEW_EN)


[grammar] ~65-~65: There might be a mistake here.
Context: ...: 13.4+ - Android: minSdkVersion 21+ - Node: >=18 ## Installation ```bash n...

(QB_NEW_EN)


[grammar] ~172-~172: There might be a mistake here.
Context: .... Original Image: First scan attempt 2. Grayscale Conversion: Improves detecti...

(QB_NEW_EN)


[grammar] ~173-~173: There might be a mistake here.
Context: ...mproves detection in colored backgrounds 3. Contrast Enhancement: Better recogniti...

(QB_NEW_EN)


[grammar] ~174-~174: There might be a mistake here.
Context: ...etter recognition in low-contrast images 4. Rotation Attempts: Tries 0°, 90°, 180°...

(QB_NEW_EN)


[grammar] ~313-~313: There might be a mistake here.
Context: ... automatically - Smart Retry Logic: If initial scan fails, automatically tries...

(QB_NEW_EN)

CHANGELOG.md

[grammar] ~17-~17: There might be a mistake here.
Context: ...preprocessing** for optimal recognition: - Contrast enhancement - Grayscale conve...

(QB_NEW_EN)


[grammar] ~18-~18: There might be a mistake here.
Context: ...al recognition: - Contrast enhancement - Grayscale conversion - Multiple rotati...

(QB_NEW_EN)


[grammar] ~19-~19: There might be a mistake here.
Context: ...ast enhancement - Grayscale conversion - Multiple rotation attempts (0°, 90°, 180...

(QB_NEW_EN)


[grammar] ~20-~20: There might be a mistake here.
Context: ... rotation attempts (0°, 90°, 180°, 270°) - Ultra-simple API - just pass image pat...

(QB_NEW_EN)


[grammar] ~31-~31: There might be a mistake here.
Context: ...ng** enabled by default for best results - Smart retry logic with multiple image en...

(QB_NEW_EN)


[grammar] ~32-~32: There might be a mistake here.
Context: ...th multiple image enhancement techniques - No additional setup required for Android...

(QB_NEW_EN)


[grammar] ~33-~33: There might be a mistake here.
Context: ...No additional setup required for Android - Minimal iOS setup with just pod install ...

(QB_NEW_EN)


[grammar] ~34-~34: There might be a mistake here.
Context: ... Minimal iOS setup with just pod install - Expo integration with proper prebuild ...

(QB_NEW_EN)


[grammar] ~35-~35: There might be a mistake here.
Context: ...egration** with proper prebuild workflow - Modern example app using Expo Image Pi...

(QB_NEW_EN)


[grammar] ~36-~36: There might be a mistake here.
Context: ...** using Expo Image Picker and StatusBar - Simplified API - just pass image path ...

(QB_NEW_EN)


[grammar] ~37-~37: There might be a mistake here.
Context: ...API** - just pass image path and formats - Performance metrics and timing measure...

(QB_NEW_EN)


[grammar] ~40-~40: There might be a mistake here.
Context: ...g measurements ### Example App Features - Modern Expo-based example application - ...

(QB_NEW_EN)


[grammar] ~41-~41: There might be a mistake here.
Context: ... - Modern Expo-based example application - Barcode format selection UI with real-ti...

(QB_NEW_EN)


[grammar] ~42-~42: There might be a mistake here.
Context: ...rmat selection UI with real-time toggles - Automatic preprocessing info with option...

(QB_NEW_EN)


[grammar] ~43-~43: There might be a mistake here.
Context: ...essing info with optional disable switch - Improved error handling and user feedbac...

(QB_NEW_EN)

🔇 Additional comments (7)
example/babel.config.js (1)

1-6: Babel config is minimal and correct for Expo.

Flat, cached config with babel-preset-expo is appropriate. No issues.

eslint.config.mjs (1)

16-21: Shareable ESLint config identifier ‘@react-native’ resolves correctly

package.json declares the scoped package @react-native/eslint-config (e.g. "@react-native/eslint-config": "^0.79.2"), so compat.extends('@react-native') will successfully load that shareable config. No change needed.

README.md (3)

28-32: Great clarity on automatic preprocessing.

The shift to automatic preprocessing is clearly explained and consistent with code changes.


168-178: Minor: tighten “How It Works” phrasing to reflect early-exit behavior.

You already do this—good callout. No change needed.


194-199: Comment aligns with API change.

“Automatic preprocessing is enabled by default” is consistent with src/index.tsx and native flow.

example/App.tsx (2)

325-333: Confirm RN “gap” support in your target versions.

gap in React Native styles is relatively new; ensure it’s supported across your documented RN matrix or replace with margins.

If needed, I can provide a small fallback style util.


222-233: Nice UX: prevents accidental double-tap by showing a spinner.

Clean pattern; no changes needed.

Comment thread example/package.json
Comment on lines 19 to 25
"expo": "~52.0.0",
"expo-status-bar": "~2.0.0",
"expo-image-picker": "~16.0.0",
"expo-permissions": "~18.0.0",
"expo-image-picker": "~16.0.3",
"expo-status-bar": "~2.0.1",
"react": "18.3.1",
"react-native": "0.76.3",
"react-native": "0.79.2",
"react-native-image-code-scanner": "file:../"
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Expo SDK-managed projects shouldn’t pin react-native; let Expo resolve it.

Pinning RN (0.79.2) can conflict with Expo SDK 52. Remove react-native here and rely on Expo (expo install/prebuild) to select the compatible version.

   "dependencies": {
     "expo": "~52.0.0",
     "expo-image-picker": "~16.0.3",
     "expo-status-bar": "~2.0.1",
     "react": "18.3.1",
-    "react-native": "0.79.2",
     "react-native-image-code-scanner": "file:../"
   },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"expo": "~52.0.0",
"expo-status-bar": "~2.0.0",
"expo-image-picker": "~16.0.0",
"expo-permissions": "~18.0.0",
"expo-image-picker": "~16.0.3",
"expo-status-bar": "~2.0.1",
"react": "18.3.1",
"react-native": "0.76.3",
"react-native": "0.79.2",
"react-native-image-code-scanner": "file:../"
},
"dependencies": {
"expo": "~52.0.0",
"expo-image-picker": "~16.0.3",
"expo-status-bar": "~2.0.1",
"react": "18.3.1",
"react-native-image-code-scanner": "file:../"
},
🤖 Prompt for AI Agents
In example/package.json around lines 19 to 25 the dependency list pins
"react-native": "0.79.2", which conflicts with Expo SDK-managed projects; remove
the "react-native" entry from package.json so Expo can manage the RN version,
then run the appropriate Expo tooling (expo install or expo prebuild) to let
Expo resolve and install the compatible react-native version and update the
lockfile; do not manually re-add a react-native version.

Comment thread example/src/App.tsx
Comment on lines 179 to 189
value={selectedFormats.includes(format.key)}
onValueChange={(value) => {
if (value) {
setSelectedFormats(prev => [...prev, format.key]);
setSelectedFormats((prev) => [...prev, format.key]);
} else {
setSelectedFormats(prev => prev.filter(f => f !== format.key));
setSelectedFormats((prev) =>
prev.filter((f) => f !== format.key)
);
}
}}
/>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Prevent deselecting the last barcode format.

Currently users can end up with formats: [], which yields no results.

-              <Switch
+              <Switch
                 value={selectedFormats.includes(format.key)}
                 onValueChange={(value) => {
                   if (value) {
-                    setSelectedFormats((prev) => [...prev, format.key]);
+                    setSelectedFormats((prev) =>
+                      prev.includes(format.key) ? prev : [...prev, format.key]
+                    );
                   } else {
-                    setSelectedFormats((prev) =>
-                      prev.filter((f) => f !== format.key)
-                    );
+                    setSelectedFormats((prev) => {
+                      if (prev.length === 1) {
+                        Alert.alert('Format Required', 'At least one format must be selected');
+                        return prev;
+                      }
+                      return prev.filter((f) => f !== format.key);
+                    });
                   }
                 }}
               />
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
value={selectedFormats.includes(format.key)}
onValueChange={(value) => {
if (value) {
setSelectedFormats(prev => [...prev, format.key]);
setSelectedFormats((prev) => [...prev, format.key]);
} else {
setSelectedFormats(prev => prev.filter(f => f !== format.key));
setSelectedFormats((prev) =>
prev.filter((f) => f !== format.key)
);
}
}}
/>
<Switch
value={selectedFormats.includes(format.key)}
onValueChange={(value) => {
if (value) {
setSelectedFormats((prev) =>
prev.includes(format.key) ? prev : [...prev, format.key]
);
} else {
setSelectedFormats((prev) => {
if (prev.length === 1) {
Alert.alert(
'Format Required',
'At least one format must be selected'
);
return prev;
}
return prev.filter((f) => f !== format.key);
});
}
}}
/>
🤖 Prompt for AI Agents
In example/src/App.tsx around lines 179 to 189, the checkbox onValueChange
handler allows removing the last selected format which results in an empty
formats array and no results; modify the handler to block deselection when
selectedFormats currently has length 1 (i.e., if value is false and
selectedFormats.length === 1) and simply return early or keep the array
unchanged, otherwise proceed with the existing add/remove logic so at least one
format remains selected.

Comment on lines +9 to +10
class ImageCodeScanner: NSObject {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Module not exported — conform to RCTBridgeModule (classic bridge) or wire up TurboModule codegen

As written, the class isn’t registered with RN. For classic bridge, conform to RCTBridgeModule and expose module metadata:

-@objc(ImageCodeScanner)
-class ImageCodeScanner: NSObject {
+@objc(ImageCodeScanner)
+class ImageCodeScanner: NSObject, RCTBridgeModule {
+  static func moduleName() -> String! { "ImageCodeScanner" }
+  static func requiresMainQueueSetup() -> Bool { false }

If you intend New Architecture/TurboModules, implement the codegen Spec (e.g., NativeImageCodeScannerSpec) and associated Swift adapter instead. I can provide that skeleton.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
class ImageCodeScanner: NSObject {
@objc(ImageCodeScanner)
class ImageCodeScanner: NSObject, RCTBridgeModule {
// Required by RCTBridgeModule to name the module exposed to JS
static func moduleName() -> String! {
return "ImageCodeScanner"
}
// If your module can be setup on a background queue, return false here.
static func requiresMainQueueSetup() -> Bool {
return false
}
// …rest of your implementation…
}
🤖 Prompt for AI Agents
In ios/ImageCodeScanner.swift around lines 9-10, the class is not registered
with React Native; either make it a classic bridge module by conforming to
RCTBridgeModule and exporting the module metadata (annotate with the ObjC
name/bridge export and implement required static metadata methods such as module
name and requiresMainQueueSetup) so RN can discover and call it, or implement
the New Architecture approach by creating the NativeImageCodeScannerSpec codegen
interface and a Swift adapter that conforms to that spec (wire up the generated
header and ensure the spec is registered in the TurboModule registry). Follow
one approach only and remove the other to avoid duplicate registrations.

Comment on lines +85 to +100
// Use atomic flag to prevent multiple promise calls
var hasResolved = false

func safeResolve(_ result: Any) {
if !hasResolved {
hasResolved = true
resolver(result)
}
}

func safeReject(_ code: String, _ message: String, _ error: Error?) {
if !hasResolved {
hasResolved = true
rejecter(code, message, error)
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Potential race on hasResolved across threads — guard with a serial queue

safeResolve/safeReject can be called from background and main threads. Use a dedicated queue:

-    var hasResolved = false
+    var hasResolved = false
+    let resolveQueue = DispatchQueue(label: "ImageCodeScanner.resolve")

-    func safeResolve(_ result: Any) {
-      if !hasResolved {
-        hasResolved = true
-        resolver(result)
-      }
-    }
+    func safeResolve(_ result: Any) {
+      resolveQueue.sync {
+        guard !hasResolved else { return }
+        hasResolved = true
+        resolver(result)
+      }
+    }

-    func safeReject(_ code: String, _ message: String, _ error: Error?) {
-      if !hasResolved {
-        hasResolved = true
-        rejecter(code, message, error)
-      }
-    }
+    func safeReject(_ code: String, _ message: String, _ error: Error?) {
+      resolveQueue.sync {
+        guard !hasResolved else { return }
+        hasResolved = true
+        rejecter(code, message, error)
+      }
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Use atomic flag to prevent multiple promise calls
var hasResolved = false
func safeResolve(_ result: Any) {
if !hasResolved {
hasResolved = true
resolver(result)
}
}
func safeReject(_ code: String, _ message: String, _ error: Error?) {
if !hasResolved {
hasResolved = true
rejecter(code, message, error)
}
}
// Use atomic flag to prevent multiple promise calls
var hasResolved = false
let resolveQueue = DispatchQueue(label: "ImageCodeScanner.resolve")
func safeResolve(_ result: Any) {
resolveQueue.sync {
guard !hasResolved else { return }
hasResolved = true
resolver(result)
}
}
func safeReject(_ code: String, _ message: String, _ error: Error?) {
resolveQueue.sync {
guard !hasResolved else { return }
hasResolved = true
rejecter(code, message, error)
}
}
🤖 Prompt for AI Agents
In ios/ImageCodeScanner.swift around lines 85 to 100, hasResolved is accessed
from multiple threads which can race; guard access with a dedicated serial
DispatchQueue (or a lock) so safeResolve and safeReject atomically check-and-set
hasResolved before calling resolver/rejecter. Create a private serial
DispatchQueue (e.g. let resolveQueue = DispatchQueue(label:
"com.app.imagecodescanner.resolve")), then wrap the check/set and subsequent
call to resolver/rejecter inside resolveQueue.sync (or async if you need async
semantics), ensuring only one path can set hasResolved and invoke the callbacks.

Comment on lines +102 to +112
// Remove file:// prefix if present
let cleanPath = path.replacingOccurrences(of: "file://", with: "")

guard let originalImage = UIImage(contentsOfFile: cleanPath) else {
safeReject("INVALID_IMAGE", "Cannot load image from path: \(path)", nil)
return
}

// Prepare images to try - always try all preprocessing options
var imagesToTry: [(String, UIImage)] = [("Original", originalImage)]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Scale large images to avoid memory spikes (parity with Android)

Add a scaler (e.g., maxDimension 2048) and use it before preprocessing:

-    guard let originalImage = UIImage(contentsOfFile: cleanPath) else {
+    guard let originalImage = UIImage(contentsOfFile: cleanPath) else {
       safeReject("INVALID_IMAGE", "Cannot load image from path: \(path)", nil)
       return
     }
-
-    // Prepare images to try - always try all preprocessing options
-    var imagesToTry: [(String, UIImage)] = [("Original", originalImage)]
+    let baseImage = scaleImageIfNeeded(originalImage, maxDimension: 2048)
+    // Prepare images to try - always try all preprocessing options
+    var imagesToTry: [(String, UIImage)] = [("Original", baseImage)]

Helper (place near preprocessing methods):

private func scaleImageIfNeeded(_ image: UIImage, maxDimension: CGFloat) -> UIImage {
  let w = image.size.width, h = image.size.height
  guard max(w, h) > maxDimension else { return image }
  let scale = maxDimension / max(w, h)
  let newSize = CGSize(width: floor(w * scale), height: floor(h * scale))
  let format = UIGraphicsImageRendererFormat.default()
  format.scale = 1
  return UIGraphicsImageRenderer(size: newSize, format: format).image { _ in
    image.draw(in: CGRect(origin: .zero, size: newSize))
  }
}
🤖 Prompt for AI Agents
In ios/ImageCodeScanner.swift around lines 102 to 112, large input images are
not being downscaled which can cause memory spikes; add a scaler that limits the
longest side to 2048 and apply it before any preprocessing. Implement the
provided scaleImageIfNeeded helper near the existing preprocessing methods, then
replace the originalImage usage by first calling
scaleImageIfNeeded(originalImage, maxDimension: 2048) and use that scaled image
when building imagesToTry (i.e., add ("Original", scaledImage) and pass
scaledImage into all subsequent preprocessing steps). Ensure the renderer format
uses scale = 1 to produce device-independent pixels.

Comment on lines +137 to +171
// Convert formats array to Vision symbologies
var symbologies: [VNBarcodeSymbology] = []

for format in formats {
switch format {
case "QR_CODE":
symbologies.append(.qr)
case "CODE_128":
symbologies.append(.code128)
case "CODE_39":
symbologies.append(.code39)
case "CODE_93":
symbologies.append(.code93)
case "EAN_13":
symbologies.append(.ean13)
case "EAN_8":
symbologies.append(.ean8)
case "UPC_A":
symbologies.append(.upce) // Vision uses UPCE for UPC-A
case "UPC_E":
symbologies.append(.upce)
case "PDF_417":
symbologies.append(.pdf417)
case "DATA_MATRIX":
symbologies.append(.dataMatrix)
case "AZTEC":
symbologies.append(.aztec)
case "ITF":
symbologies.append(.itf14) // ITF14 format
case "CODABAR":
symbologies.append(.codabar)
default:
break
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

❓ Verification inconclusive

UPC_A mapping: include EAN-13 for UPC-A compatibility

Vision doesn’t expose UPC-A directly; UPC-A often decodes as EAN-13 with a leading 0. When “UPC_A” is requested, include both .ean13 and .upce:

-      case "UPC_A":
-        symbologies.append(.upce) // Vision uses UPCE for UPC-A
+      case "UPC_A":
+        symbologies.append(contentsOf: [.ean13, .upce])

Action Required: Update UPC_A Mapping to Include EAN-13 for Correct UPC-A Support

According to Apple’s AVFoundation FAQ (Technical Note TN2325), UPC-A barcodes are formally a subset of EAN-13 and are output as an EAN-13 symbology with a leading zero in the decoded string value (developer.apple.com). To ensure “UPC_A” requests are handled correctly, the code should append both .ean13 (for UPC-A) and .upce (for UPC-E) rather than only .upce.

Apply the following change in ios/ImageCodeScanner.swift (around lines 137–171):

       case "UPC_A":
-        symbologies.append(.upce) // Vision uses UPCE for UPC-A
+        symbologies.append(contentsOf: [.ean13, .upce])  // UPC-A via EAN-13 + UPC-E
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Convert formats array to Vision symbologies
var symbologies: [VNBarcodeSymbology] = []
for format in formats {
switch format {
case "QR_CODE":
symbologies.append(.qr)
case "CODE_128":
symbologies.append(.code128)
case "CODE_39":
symbologies.append(.code39)
case "CODE_93":
symbologies.append(.code93)
case "EAN_13":
symbologies.append(.ean13)
case "EAN_8":
symbologies.append(.ean8)
case "UPC_A":
symbologies.append(.upce) // Vision uses UPCE for UPC-A
case "UPC_E":
symbologies.append(.upce)
case "PDF_417":
symbologies.append(.pdf417)
case "DATA_MATRIX":
symbologies.append(.dataMatrix)
case "AZTEC":
symbologies.append(.aztec)
case "ITF":
symbologies.append(.itf14) // ITF14 format
case "CODABAR":
symbologies.append(.codabar)
default:
break
}
}
case "EAN_8":
symbologies.append(.ean8)
case "UPC_A":
symbologies.append(contentsOf: [.ean13, .upce]) // UPC-A via EAN-13 + UPC-E
case "UPC_E":
symbologies.append(.upce)
🤖 Prompt for AI Agents
In ios/ImageCodeScanner.swift around lines 137 to 171, the mapping for "UPC_A"
only appends .upce but UPC-A is represented as EAN-13 (with leading zero) per
Apple TN2325; update the branch so that when format == "UPC_A" you append both
.ean13 and .upce to symbologies (keep existing UPC_E mapping as .upce), ensuring
UPC-A requests include EAN-13 support and UPC-E continues to map to UPCE.

Comment on lines +196 to +205
// Create Vision request for barcode detection
let request = VNDetectBarcodesRequest { request, error in
DispatchQueue.main.async {
if let error = error {
print("ImageCodeScanner iOS - \(description) failed: \(error.localizedDescription)")
// Try next image
tryScanning(images: images, index: index + 1)
return
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Pass correct image orientation to Vision

Use the UIImage orientation when creating the handler; otherwise detection can degrade:

-      let request = VNDetectBarcodesRequest { request, error in
+      let request = VNDetectBarcodesRequest { request, error in
         // ...
       }
       // ...
-      let handler = VNImageRequestHandler(cgImage: cgImage, options: [:])
+      let handler = VNImageRequestHandler(
+        cgImage: cgImage,
+        orientation: cgImagePropertyOrientation(from: currentImage.imageOrientation),
+        options: [:]
+      )

Helper:

private func cgImagePropertyOrientation(from o: UIImage.Orientation) -> CGImagePropertyOrientation {
  switch o {
  case .up: return .up
  case .down: return .down
  case .left: return .left
  case .right: return .right
  case .upMirrored: return .upMirrored
  case .downMirrored: return .downMirrored
  case .leftMirrored: return .leftMirrored
  case .rightMirrored: return .rightMirrored
  @unknown default: return .up
  }
}
🤖 Prompt for AI Agents
In ios/ImageCodeScanner.swift around lines 196 to 205, the
VNDetectBarcodesRequest handler is created without passing the image orientation
to the Vision handler which can degrade detection; convert the
UIImage.Orientation to a CGImagePropertyOrientation (use a helper like the
provided cgImagePropertyOrientation(from:) function) and pass that orientation
into the VNImageRequestHandler initializer (or perform request call) so the
image is processed with correct orientation.

Comment thread package.json
Comment on lines 82 to 104
"@eslint/eslintrc": "^3.3.0",
"@eslint/js": "^9.22.0",
"@evilmartians/lefthook": "^1.5.0",
"@react-native-community/cli": "15.0.0-alpha.2",
"@react-native-community/cli": "15.0.0",
"@react-native/babel-preset": "0.79.2",
"@react-native/eslint-config": "^0.78.0",
"@react-native/eslint-config": "^0.79.2",
"@release-it/conventional-changelog": "^9.0.2",
"@types/jest": "^29.5.5",
"@types/react": "^19.0.0",
"@types/react": "^18.3.12",
"commitlint": "^19.6.1",
"del-cli": "^5.1.0",
"eslint": "^9.22.0",
"eslint-config-prettier": "^10.1.1",
"eslint": "^8.56.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.2.3",
"jest": "^29.7.0",
"prettier": "^3.0.3",
"react": "19.0.0",
"react": "18.3.1",
"react-native": "0.79.2",
"react-native-builder-bob": "^0.40.13",
"release-it": "^17.10.0",
"turbo": "^1.10.7",
"typescript": "^5.8.3"
"typescript": "^5.3.0"
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

ESLint major mismatch: @eslint/js v9 with eslint v8 will break. Align to ESLint 9.

You’re using flat config (eslint.config.mjs per PR notes). Bump eslint to v9 to match @eslint/js v9.

-    "eslint": "^8.56.0",
+    "eslint": "^9.0.0",

Run to confirm majors align:


🏁 Script executed:

#!/bin/bash
node -e "const p=require('./package.json'); const dj=p.devDependencies; const mj=s=>/^\\D*(\\d+)/.exec((dj[s]||'0'))?.[1]; console.log({eslint:mj('eslint'), eslint_js:mj('@eslint/js')}); if(mj('eslint')!==mj('@eslint/js')){process.exit(1)}"

Length of output: 308


Bump ESLint to v9 to match @eslint/js v9

The project currently uses @eslint/js@^9.22.0 alongside eslint@^8.56.0, which will break the flat config loader. Upgrade ESLint to v9.

• File: package.json
– Location: devDependencies, lines ~82–104

-    "eslint": "^8.56.0",
+    "eslint": "^9.22.0",
🤖 Prompt for AI Agents
In package.json around lines 82 to 104, devDependencies list @eslint/js@^9.22.0
while eslint is pinned to ^8.56.0 which is incompatible with the v9 flat config
loader; update the eslint devDependency to a v9.x version (e.g., ^9.x) to match
@eslint/js and ensure package.json version ranges are consistent, then run
npm/yarn install and verify linting works.

Comment thread README.md
Comment on lines +321 to 323
3. **🔧 Preprocessing**: Automatic preprocessing improves recognition rates but may increase processing time. The library optimizes this automatically.

4. **📱 Platform Differences**: iOS Vision Framework and Android ML Kit may have slight differences in recognition capabilities. Test on both platforms for critical use cases.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Troubleshooting still tells users to “enable preprocessing options,” which no longer exist.

Update bullets to reflect the automatic nature—suggest specifying formats or resizing instead.

-2. Enable preprocessing options for challenging images
-4. Try different preprocessing combinations
+2. Specify only the relevant barcode formats to reduce noise
+4. Try resizing/cropping the image (e.g., focus on the barcode area) or improving contrast before scanning

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In README.md around lines 321 to 323, the troubleshooting text still instructs
users to “enable preprocessing options” which no longer exist; update the
bullets to state that preprocessing is automatic and cannot be toggled, remove
any references to enabling preprocessing, and instead recommend actionable steps
users can take such as providing images in supported formats, resizing or
downscaling/upscaling to recommended dimensions, and ensuring proper
color-space/rotation; also adjust any troubleshooting guidance to suggest these
format/resize checks and mention that platform differences may affect results.

Comment thread src/index.tsx
Comment on lines +27 to 41
const { path, formats = [BarcodeFormat.QR_CODE] } = options;

// Resolve preprocessing options based on platform and overrides
const resolvedOptions = resolvePreprocessingOptions(
preprocessing,
platformOverrides,
);

// Map to native module format
// Note: Preprocessing is always enabled in native implementation
// The native code automatically tries multiple preprocessing techniques
const nativeOptions = {
enhanceContrast: resolvedOptions.enhanceContrast ?? false,
convertToGrayscale: resolvedOptions.grayscale ?? false,
tryRotations: resolvedOptions.rotations ?? false,
enhanceContrast: true,
convertToGrayscale: true,
tryRotations: true,
};

return ImageCodeScanner.scanFromPath(
path,
formats.map(f => f.toString()),
nativeOptions,
formats.map((f) => f.toString()),
nativeOptions
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Guard against empty formats and validate path early.

If consumers pass formats: [], native may see no formats. Also fail fast on missing path.

-    const { path, formats = [BarcodeFormat.QR_CODE] } = options;
+    const { path, formats = [BarcodeFormat.QR_CODE] } = options;
+    if (!path) {
+      return Promise.reject(new Error('Image path is required'));
+    }
+    const effectiveFormats =
+      Array.isArray(formats) && formats.length > 0
+        ? formats
+        : [BarcodeFormat.QR_CODE];
@@
-      formats.map((f) => f.toString()),
+      effectiveFormats.map((f) => String(f)),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const { path, formats = [BarcodeFormat.QR_CODE] } = options;
// Resolve preprocessing options based on platform and overrides
const resolvedOptions = resolvePreprocessingOptions(
preprocessing,
platformOverrides,
);
// Map to native module format
// Note: Preprocessing is always enabled in native implementation
// The native code automatically tries multiple preprocessing techniques
const nativeOptions = {
enhanceContrast: resolvedOptions.enhanceContrast ?? false,
convertToGrayscale: resolvedOptions.grayscale ?? false,
tryRotations: resolvedOptions.rotations ?? false,
enhanceContrast: true,
convertToGrayscale: true,
tryRotations: true,
};
return ImageCodeScanner.scanFromPath(
path,
formats.map(f => f.toString()),
nativeOptions,
formats.map((f) => f.toString()),
nativeOptions
);
const { path, formats = [BarcodeFormat.QR_CODE] } = options;
if (!path) {
return Promise.reject(new Error('Image path is required'));
}
const effectiveFormats =
Array.isArray(formats) && formats.length > 0
? formats
: [BarcodeFormat.QR_CODE];
// Note: Preprocessing is always enabled in native implementation
// The native code automatically tries multiple preprocessing techniques
const nativeOptions = {
enhanceContrast: true,
convertToGrayscale: true,
tryRotations: true,
};
return ImageCodeScanner.scanFromPath(
path,
effectiveFormats.map((f) => String(f)),
nativeOptions
);
🤖 Prompt for AI Agents
In src/index.tsx around lines 27 to 41, validate inputs before calling
ImageCodeScanner.scanFromPath: check that path is a non-empty string and throw
or return a rejected Promise immediately if missing/invalid, and ensure formats
is not an empty array (if formats === undefined use default
[BarcodeFormat.QR_CODE], and if formats is provided but formats.length === 0
replace it with the default) before mapping to strings; then pass the validated
path and formats.map(...) along with nativeOptions to
ImageCodeScanner.scanFromPath.

@anngth anngth closed this Aug 27, 2025
@anngth
anngth deleted the fix_error branch August 27, 2025 13:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant