Repository navigation
Conversation
Add Nitro specs, native HybridObject scaffolding, Android CMake integration, and VisionCamera v5 example frame-output wiring. Replace the old VisionCamera v4 frame processor plugin registration with the new Nitro-backed factory surface. Live ML Kit frame processing remains scaffolded for the next implementation phase.
frame recognition
Bumps react-native 0.81.1 -> 0.86.0, react 19.1.0 -> 19.2.3, and related packages (reanimated, screens, svg, worklets, nitro-image, RN CLI) across the workspace and example app. Also bumps react-native-gesture-handler 2.30.0 -> 3.0.2, required because RN 0.86 (New Architecture only) removed the legacy Paper-only renderer shim (react-native/Libraries/Renderer/shims/ReactNative) that gesture-handler 2.x still imported, breaking the Metro bundle step. Regenerates native project files that follow from the upgrade: Gradle wrapper 8.14.3 -> 9.3.1, MainApplication.kt bridgeless-only template, AndroidManifest cleartext-traffic placeholder (folding the debug-only manifest into main), Podfile.lock, Xcode project settings, Info.plist, and jest preset.
The Android-to-Nitro BoundingBox mapping only set x/y/width/height, leaving centerX, centerY, top, left, bottom and right at their zero default even though the JS BoundingBox type declares all of them.
…ntime execution useMLKitPlugin wrapped the native TextRecognizer/BarcodeScanner HybridObject in a JS-level worklet function, and useTextRecognition/ useBarcodeScanning wrapped that again in their own worklet. A worklet closing over another worklet, two levels deep, does not reliably survive being re-shared into a different Worklet Runtime, degrading into a non-callable object and throwing "Object is not a function" whenever called through useAsyncRunner's native-thread-backed runtime. useMLKitPlugin now returns the memoized native HybridObject directly, so each feature hook only needs a single worklet layer closing directly over it. CameraView also drops the useAsyncRunner hop entirely and calls the plugin inside the top-level onFrame worklet, matching the pattern the library's README documents.
…ive FPS graph CameraView enabled VisionCamera v5's native gesture controllers (enableNativeZoomGesture, enableNativeTapToFocusGesture) at the same time as a hand-rolled react-native-gesture-handler GestureDetector for tap-to- focus and double-tap-to-flip. Native UIKit/CameraX recognizers intercept touches before RNGH's JS-driven detector ever sees them, so enabling any native gesture silenced the RNGH gestures entirely, and disabling zoom let pinch touches misfire as double-taps. It also meant tap-to-focus never called back into JS, so the focus-ring overlay never rendered. All gestures now live in one RNGH composition: pinch-to-zoom drives a Reanimated-backed zoom SharedValue via Gesture.Pinch(), combined with the existing tap/double-tap Gesture.Exclusive() through Gesture.Simultaneous(). The native gesture props are removed. Also: - Fixes a Reanimated "reading `value` during render" violation where `savedZoom` was seeded by reading `zoom.value` instead of the same plain initial expression. - Stops logging the expected "metering operation has been canceled" rejection that fires whenever a new focus request supersedes a still- settling one. - Restores the FPS graph toggle, which the v5 migration left as a dead no-op (state value was discarded and the `enableFpsGraph` Camera prop it fed was removed upstream in v5). Frame arrivals are now counted in the onFrame worklet and sampled on an interval to drive a real FpsGraphOverlay component.
…ted compatibility The Terminal bottom sheet presented but never animated open. Installed 5.2.8 predates react-native-gesture-handler v3.0.0 (2026-05-28) and bottom-sheet's own "Full support for react-native-reanimated 4 and worklets" issue was still open upstream at the time. 5.2.14 was already within the declared "^5" range but had never been re-resolved.
… orientation settings Surfaces VisionCamera v5 Camera-behavior capabilities that were fully supported by the library but never exposed in the Settings tab: - Exposure Bias slider, clamped to the device's supported range. - Low Light Boost switch, gated on device.supportsLowLightBoost - the enableLowLightBoost prop is only included on <Camera /> when it should be true, since passing it as false still triggers native validation and throws on devices that don't support the feature at all. - Target FPS constraint picker (15/24/30/60/120), applied after the existing resolutionBias constraint so the ML frame output's resolution match keeps priority. - Video Stabilization mode picker, covering all 8 real TargetStabilizationMode values. - Orientation Source picker (interface/device). Also cleans up stale copy: relabels "Frame Processor FPS (runAtTargetFps)" (runAtTargetFps doesn't exist in v5) to describe the actual JS-side frame throttle, and removes the deprecated "Frame Process Interval" picker, which duplicated that throttle at the native level via an option already marked @deprecated.
README.md, docs/text-recognition.md, and docs/barcode-scanning.md still documented the removed v4 API end-to-end (useFrameProcessor, runAsync(frame, cb), runAtTargetFps, react-native-worklets-core plus its Babel plugin) and listed the wrong peer dependencies, even though the library and example app were migrated to v5 earlier. runAtTargetFps no longer exists anywhere in react-native-vision-camera's source. Replaces every v4 reference with the real v5 usage (useFrameOutput, direct in-worklet calls, frame.dispose(), the actual v5 peer deps and Babel plugin), and points links at visioncamera.margelo.com instead of the old react-native-vision-camera.com domain. Also rewords the frameProcessInterval JSDoc in src/core/types.ts, which pointed consumers at the same removed runAtTargetFps API.
…on and barcode scanning (#13) * feat(core): add shared RegionOfInterest type and validator Introduces the public RegionOfInterest/RegionOfInterestUnit types and a fail-fast JS-side validator, wired into both the live-frame (PluginFactory) and static-image (NativeBridge) paths before any native call is made. Extends the Nitro-facing MLKitRecognizerOptions so the field actually crosses the bridge for text recognition and barcode scanning. * feat(android): implement region of interest crop and coordinate remap Crops the working bitmap to the resolved ROI pixel rect after rotation and before scale/invert, on both the live-frame (Nitro) and static-image (legacy bridge) paths for text recognition and barcode scanning. Disables the raw-MediaImage fast path whenever an ROI is set, since it can't crop. Adds a shared coordinate-remap primitive that maps result bounding boxes and corner points back to full source-frame coordinates using the recorded crop offset and scale factor - this also fixes result coordinates for the pre-existing scaleFactor option, which previously had no remapping at all. * feat(ios): implement region of interest crop and coordinate remap Mirrors the Android ROI implementation: crops via CGImage/CIImage after orientation resolution and before scale/invert, on both the live-frame (Nitro) and static-image (legacy bridge) paths for text recognition and barcode scanning. Disables the raw-CMSampleBuffer fast path whenever an ROI is set, since it can't crop. Adds the same shared coordinate-remap primitive as Android, mapping result coordinates back to full source-frame space using the recorded crop offset and scale factor. Domain type is named DomainRegionOfInterest (file included) to avoid colliding with the Nitro-generated RegionOfInterest, matching the existing DomainOrientation/Orientation precedent. Also fixes two smaller pre-existing gaps hit while wiring this through: RecognizeBarcodesUseCase's static-image overload was dropping outputOrientation when reconstructing ImagePreprocessingOptions, and a CMSampleBuffer conversion-failure path used fatalError instead of failing gracefully - now reachable in normal operation via ROI validation. Includes the Podfile.lock update from re-running pod install after adding the new native files. * feat(example): add region of interest control and camera overlay Adds a shared RegionOfInterestControl (enable switch + 4 normalized sliders) used by both the Settings tab (live camera options) and the Image screen (static-image options), plus an ROIOverlay component that renders the configured rectangle over the camera preview - both for composing an ROI and for visually confirming the native crop/remap lines up with the recognized result coordinates. * docs: document the roi option for text recognition and barcode scanning Adds a roi row to every frame/image options list in README.md and both feature docs, plus a "Region of Interest" reference section in the main README explaining the type shape, default normalized coordinates, and usage. * test: add unit tests for validateRegionOfInterest Covers valid normalized and pixel regions, negative x/y, non-positive width/height, and normalized x+width/y+height exceeding 1.
Swift-only native modules are never auto-discovered by React Native's bridge; they require a companion Objective-C file using RCT_EXTERN_MODULE to actually register the class. That file never existed for VisionCameraMLKitModule, so TurboModuleRegistry.getEnforcing threw at runtime for any static-image call (processImageTextRecognition / processImageBarcodeScanning) on iOS - confirmed via device testing. Pre-existing since the original v1 rewrite, unrelated to the ROI feature; never caught because the static-image path had not been exercised on a real iOS build before. The live-frame path is unaffected - it uses Nitro HybridObjects, a separate registration mechanism, already working. Android is unaffected: its equivalent module registers explicitly via BaseReactPackage/ReactModuleInfoProvider (isTurboModule = false), which does not depend on this bridge file.
…ve-bottom-sheet (#14) * fix(example): replace @gorhom/bottom-sheet with @swmansion/react-native-bottom-sheet Removes the suspected root cause of the reverted Terminal sheet toggle and close-on-blur bugs: the new library's sheet host, gestures, snapping, and scroll negotiation all run natively and depend on neither react-native-gesture-handler nor react-native-reanimated, unlike @gorhom's implementation which was never validated against our RNGH v3 combo. TerminalProvider moves from an imperative BottomSheetModal ref (present()/dismiss()) to a controlled index state (0 closed, 1 open), and stabilizes open/close with useCallback (previously recreated on every isOpen change). TerminalBottomSheet swaps ModalBottomSheet/FlatList in for BottomSheetModal/BottomSheetFlatList and hand-builds the handle pill and rounded background the old library provided by default, to keep the same visual styling. Percentage snap points (25/60/90%) become pixel detents derived from useWindowDimensions, with 0 as the new explicit closed detent. Scoped narrowly to the swap itself: behavior (button-only open, no toggle, no auto-close-on-blur) is unchanged from before. Toggle/close-on-blur remain a separate follow-up, now cheaper thanks to the controlled index model. * fix(example): restore sheet z-order and add Terminal toggle/close-on-blur The library swap dropped @gorhom's containerStyle zIndex/elevation (assumed to be gorhom-specific boilerplate), which was actually what kept the Terminal sheet above PortalProvider's zIndex:1/elevation:1 host view (used by PortalSegmentedToggle). Restored via ModalBottomSheet's `style` prop, since the new library exposes no equivalent containerStyle prop directly. Also delivers the toggle and close-on-blur behavior called out as out-of-scope in the previous commit, now that it's cheap with the controlled-index model: - TerminalProvider exposes a stabilized `toggle()` alongside open/close. - CameraControls' terminal button (icon-only, no explicit label) now toggles instead of only ever opening; renamed onOpenTerminal -> onToggleTerminal to match the sibling onToggleTorch/onToggleFpsGraph props. ImageScreen's two explicit "open" call sites (auto-open after processing, "Open Terminal" button) are unchanged, since toggling those would be the wrong affordance. - CameraScreen and ImageScreen close the terminal via useFocusEffect's cleanup on blur (covers Settings navigation, Home, and switching between Camera/Image through PortalSegmentedToggle's stack replace), instead of the previous global NavigationContainer.onStateChange attempt that broke opening the sheet.
) react-native-vision-camera-worklets and react-native-worklets were never imported anywhere in src/, android/, or ios/ - the library's live-frame hooks are worklet-callable Nitro HybridObject methods, not code that imports the Worklets packages directly. They're an app-level opt-in required to use react-native-vision-camera's frame-processor feature at all, a requirement flowing from Vision Camera's own frame-processor API contract, not from this library. Vision Camera itself doesn't declare them as peer dependencies either, confirming this. Blanketing them as required peer deps also wrongly implied the static-image APIs (processImageTextRecognition/processImageBarcodeScanning, which go through the classic native bridge and never touch Vision Camera or Worklets) need a camera at all. react-native-nitro-image is similarly unused anywhere in this library's own code - confirmed absent from src/, android/src, ios/, nitro.json, and the iOS podspec. It's a hard transitive peer/pod dependency of react-native-vision-camera itself (its own peerDependencies and podspec both declare it), so consumers still get nagged for it via Vision Camera's own peer-dependency declaration - verified empirically via `yarn install`, which surfaces "react-native-vision-camera-mlkit doesn't provide react-native-nitro-image ... requested by react-native-vision-camera" without this library redeclaring it. Also removes the matching unused `implementation project(":react-native-nitro-image")` line from android/build.gradle - not referenced by any Kotlin source or generated Nitrogen output on either platform. README updated to reflect the real dependency shape: react-native-vision-camera + react-native-nitro-modules for the base install, react-native-vision-camera-worklets + react-native-worklets only if using the live-frame hooks, and an explicit callout that the static-image APIs need neither.
…and npm metadata (#17) * docs: stop overselling unimplemented ML Kit features in npm metadata and README The npm description and keywords listed face detection and pose detection alongside text recognition and barcode scanning as if all were delivered. Only text recognition v2 and barcode scanning are implemented in src/ (see the README's own roadmap table) - everything else is unimplemented Phase 3 work. Reworded the description to reflect what ships today and frame the rest as roadmap, and dropped face-detection/pose-detection from keywords since they misrepresent the package's actual capabilities to anyone evaluating it on npm. Applied the same fix to the README's intro paragraph, which had the identical problem. Also restructured the README's iOS troubleshooting content: the Apple Silicon simulator note was oddly nested under "iOS Orientation Notes" (an unrelated topic), and docs/xcode-26.4-fmt-consteval-workaround.md was never linked from anywhere. Moved both into a new "Troubleshooting" section. * docs: expand thin API result-shape sections in feature docs docs/text-recognition.md's "Result shape" section just named the nesting levels without describing fields, despite the page's stated goal of documenting the API "in more detail than the README quick-start". Expanded it into the full TextRecognitionResult field breakdown (bounds/corners/ languages at every level, Android-only confidence/angle/symbols), sourced from src/features/text-recognition/types.ts, and added the missing TextRecognitionResult type to the import example. docs/barcode-scanning.md's BarcodeData field list didn't explain that format/valueType are raw ML Kit numeric codes versus the formatName/ valueTypeName string equivalents apps should actually match against, or that bounds/corners can be absent and rawValue/rawBytes can be null for potential barcodes - both surprising to a new consumer without reading BarcodeData's type definition directly. * docs: fix inaccurate claims and gaps in CONTRIBUTING and CODE_OF_CONDUCT CONTRIBUTING.md: - Claimed pre-commit hooks verify tests pass; lefthook.yml's pre-commit only runs eslint and tsc, and the commit message check lives in a separate commit-msg hook, not pre-commit. Corrected both claims and pointed contributors at `yarn test` before opening a PR. - Referenced "the pull request template" but no .github/PULL_REQUEST_TEMPLATE.md exists in this repo - replaced with a pointer to the issue templates that do exist. - This is a Nitro Module (native interface in src/specs/*.nitro.ts) but CONTRIBUTING never mentioned `yarn specs` (nitrogen codegen), needed whenever a .nitro.ts spec file changes. Added a "Working with the Nitro spec" section and the missing script to the Scripts list, plus `yarn clean` and `yarn prepare`. - Linked docs/xcode-26.4-fmt-consteval-workaround.md, which was previously unreferenced from anywhere in the repo despite being directly relevant to building the example app's iOS target. - Commit type list was missing `perf`, `ci`, and `build`, all of which are actually used in this repo's git history per commitlint's conventional config. CODE_OF_CONDUCT.md: - Directed harassment/conduct reports to the public GitHub issue tracker, which isn't private - inconsistent with SECURITY.md's own private reporting channel for vulnerabilities. Switched to a direct email contact.
* chore: replace labels.yml with updated shared taxonomy Adopt the new personal cross-repo label taxonomy (type/status/priority/ platform/technology/area/impact/pr/contribution), trimmed to what actually applies to this repo: drops backend/web platforms, Java, Objective-C, Flutter, and Dart (Android impl is Kotlin-only, iOS impl is Swift-only, no Flutter/Dart anywhere), and drops analytics, auth, database, design-system, localization, notifications, offline, synchronization, and the networking-flavored "api" area, plus "impact: data loss" — none of these concepts exist in this camera/OCR frame-processor library. Kept accessibility/navigation/ui-ux since the example/ demo app has real navigation, UI components, and accessibility labels. * chore: align workflow and issue-template label references with new taxonomy Update the stale-bot config (stale/exempt/only labels) and the bug report, build error, and feature request issue templates to reference the new label names instead of the old emoji-prefixed ones. GitHub Actions label matching is exact-string, so leftover references to the old names would have silently stopped working once labels.yml changed.
* chore: untrack personal AI tooling state from .claude/.codex Audit of .agents/.claude/.codex (added by an ECC auto-setup process), using the same rationale that got .context/ gitignored: keep genuinely shareable repo-specific tooling tracked (skills, slash-command scaffolds, Codex sub-agent role definitions - none of it contains personal data), but untrack files that are personal/machine-specific state: - .claude/identity.json: user's personal AI verbosity/style/domain preferences, not a project fact. - .claude/ecc-tools.json: ECC tool bookkeeping/state manifest with generation timestamps and readiness scores. - .claude/homunculus/instincts/inherited/*.yaml: personal continuous- learning cache, content overlaps with the tracked SKILL.md docs. - .codex/config.toml: personal sandbox/approval policy and MCP server list - the Codex equivalent of .claude/settings.local.json, which is already untracked. Files stay on disk (not deleted), just removed from git's index, so local AI tooling keeps working. .gitignore gains a new section for these patterns, appended alongside (not replacing) the existing .context/ entry. * chore: gitignore the untracked personal AI tooling state files * chore: untrack .agents/ AI tooling scaffolding as well Not personal data (verified byte-identical to the kept .claude/skills/ copy, plus generic tool-discovery metadata) - untracked by preference, to keep the repo free of visible ECC-generated AI-tooling scaffolding regardless of whether it's personal. Files stay on disk.
Removes the last tracked ECC-generated files (.claude/commands/*.md, .claude/skills/.../SKILL.md, .codex/AGENTS.md, .codex/agents/*.toml) - previously kept on a personal-vs-shareable distinction, but going fully local-only for now regardless of that distinction, matching the .agents/ call. Files stay on disk, just removed from git's index. .gitignore simplified to blanket-ignore .agents/, .claude/, and .codex/ instead of itemized file patterns, since nothing under them is tracked anymore. What AI tooling (if any) should be shared via the git remote is left as an open question to revisit later, not decided by this commit.
Delivers Phase 4 of the roadmap: an app.plugin.js so Expo prebuild users don't have to hand-edit android/build.gradle / ios/Podfile to selectively enable ML Kit models, mirroring the existing manual instructions in the README exactly (same flag names, same defaults left untouched for any key the consumer omits). - plugin/mlkitConfig.js - the 16 documented feature keys, fail-fast validation (throws on unknown keys instead of silently ignoring typos, matching this repo's validateRegionOfInterest.ts precedent). - plugin/withAndroid.js - prepends the `ext["react-native-vision-camera-mlkit"]` Groovy block to the project-level android/build.gradle via withProjectBuildGradle; throws on Kotlin DSL (.kts), which the native config doesn't support either. - plugin/withIOS.js - inserts the `$VisionCameraMLKit` Ruby hash before the first `target` block in ios/Podfile via withPodfile, dropping the three Android-only keys (faceMeshDetection, subjectSegmentation, documentScanner) that don't exist in the podspec's config. - Both mods are idempotent (marker-checked, not just createRunOncePlugin's own dedup, since a real `expo prebuild` invocation builds a fresh config object each run) - verified end-to-end against real files via compileModsAsync, not just the pure string-transform unit tests. @expo/config-plugins added as an optional peer dependency (peerDependenciesMeta) - Expo projects already have it transitively via `expo` itself; non-Expo consumers never need it, matching this session's earlier peer-dependency cleanup (#15). README: new "Expo (config plugin)" subsection under ML Kit Models Installation, and the old blanket "follow the Vision Camera guide" note now clarifies that's for Vision Camera's own Expo setup, not this plugin.
…w fixes) (#20) * feat: harden v2 Nitro release architecture * fix(ios): downsample full-resolution static images * fix: address v2 release-hardening review findings - android: recycle intermediate bitmaps in ImagePreprocessor, guarded by reference-equality checks since Bitmap.createBitmap/scale return the source instance when no transform is actually applied - android: serialize access to the shared ML Kit detector between the synchronous frame-processor path and the async static-image path - ios: skip the redundant re-render pass in normalized(to:) when the target orientation is already .up - ci: validate the release tag before building the package in npm-release.yml, so a mismatch fails fast - chore: drop native-tests/ios/StaticImageDecodePlan.test.swift, the test:ios-static script, and its CI wiring; the hardcoded /private/tmp paths and hand-rolled swiftc compile step weren't worth keeping without a proper SPM/XCTest target * fix(ci): stop pinning a hardcoded Xcode version in build-ios.yml The XCODE_VERSION: 16.3 pin predates this branch and no longer exists on GitHub's macos-latest runner image (only 26.0.1-26.6.0 are installed now, per Apple's year-aligned Xcode versioning). Switch to latest-stable so this doesn't silently break again next time GitHub rotates old Xcode versions out of the image. * fix(ci): unpin stale Bundler version that crashes on Ruby 3.4 example/Gemfile.lock's BUNDLED WITH 2.1.4 forces bundle install to download and switch to that ancient Bundler on every run, but its vendored thor lib references DidYouMean::SPELL_CHECKERS, which no longer exists on Ruby 3.4 (the default on current macOS runners) - crashing before pod install ever runs. Bump to 4.0.17, the version already installed and working on the runner before the forced downgrade kicked in. * fix(android): scope RNGP codegen resolution to this library's package android/build.gradle sits at the repo root and is linked into the example app by local path, unlike every other native dependency here (all node_modules-nested). Without jsRootDir set, RNGP appears to resolve the JS root against this package's own package.json, which also declares "workspaces": ["example"], causing it to aggregate codegen output for every native module in the workspace - including @swmansion/react-native-bottom-sheet's - duplicating a class that module already builds for itself and failing app:mergeLibDexDebug. * fix(ci): forward IOS_DESTINATION into the build:ios turbo task turbo.json's build:ios task didn't allowlist IOS_DESTINATION, so Turborepo stripped it from the task's environment before running "react-native build-ios --destination \"\${IOS_DESTINATION:-...}\"". The script's fallback (generic/platform=iOS Simulator) then hit xcodebuild's own destination handling and failed instantly - "xcodebuild exited with error code '64'" with no compile output, less than 2ms after starting. Allowlisting it lets the simulator destination computed by build-ios.yml actually reach the build command. * fix(ci): stop duplicating the platform key in the iOS build destination react-native build-ios unconditionally prepends its own "generic/platform=iOS Simulator," to whatever --destination value is passed - it never replaces it. Our computed IOS_DESTINATION already included "platform=iOS Simulator", so the combined string ended up with two "platform=" keys ("generic/platform=iOS Simulator,platform=iOS Simulator,OS=...,name=..."), which xcodebuild rejected instantly with exit code 64 and zero compile output. Drop the redundant prefix from both the computed value and the local-dev fallback. Also revert the earlier jsRootDir addition to android/build.gradle: two CI runs confirmed it had no effect on the BottomSheetSurfaceViewManagerDelegate duplicate-class error, and the yarn-hoisting theory behind it doesn't hold (the package isn't hoisted to the repo root node_modules), so it wasn't worth keeping as unverified guesswork. * fix(android): restore the package's own react-native.config.js Every other native dependency in the tree (nitro-image, vision-camera, vision-camera-worklets) ships an identical self-descriptor at its package root declaring empty ios/android platforms. Ours was missing entirely, even though package.json's files list expects it to exist. Without it, example/react-native.config.js worked around the gap by inlining an empty platforms override directly on the manual dependencies[pkg.name] entry. That's a different code path than a proper self-descriptor, and correlates with the one concrete anomaly in CI: every sibling library's generateCodegenSchemaFromJavaScript task logs "No modules to process" - ours didn't, and our own module ended up compiling com.facebook.react.viewmanagers.BottomSheetSurface- ViewManagerDelegate into itself, colliding with bottom-sheet's own copy at dex-merge time. Restoring the root config and dropping the now-redundant inline override lets the CLI resolve our package the same way it resolves every other properly-behaved dependency. * fix(android): scope codegen to our own src/specs directory Root cause found via a stray local build artifact (android/build/generated/source/codegen/schema.json): our library's generateCodegenSchemaFromJavaScript task was producing a 158-module schema covering the ENTIRE example app's dependency tree (BlurView, RNGestureHandler, RNSVG, BottomSheetSurfaceView, etc.), not just our own package. That's why our module compiled its own copy of com.facebook.react.viewmanagers.BottomSheetSurfaceViewManagerDelegate, colliding with bottom-sheet's own copy at D8 dex-merge time. Without a codegenConfig, RNGP has nothing to scope the schema combiner to just our package, so it fell back to scanning our whole package root - which, unlike sibling npm packages, has the entire example/ workspace nested inside it. Verified locally with combine-js-to-schema-cli: - scanning "." (previous behavior): 158 modules, includes BottomSheetSurfaceView - scanning "src/specs" (this fix): 0 modules, "No modules to process" - matching every other Nitro library in the tree src/specs only contains .nitro.ts files, which never match the old architecture's codegenNativeComponent/TurboModuleRegistry patterns, so scoping codegen there is safe and produces an empty schema by design. * fix(android): drop unused react-native-nitro-image CMake dependency The dex-merge duplicate that failed earlier builds was masking a second issue: android/build.gradle never declares a Gradle dependency on react-native-nitro-image, so its prefab package was never on CMAKE_FIND_ROOT_PATH, and find_package(react-native-nitro-image REQUIRED) failed with "Findreact-native-nitro-image.cmake not found". Verified react-native-nitro-image is never referenced anywhere in cpp/, nitrogen/, or src/ - it's unused leftover from a template, not an actual dependency. Removing the find_package call and its target_link_libraries entry fixes the CMake configure step without touching anything the library actually uses.
…tion and barcode scanning The v2 hardening PR fixed a real compile failure by making com.google.mlkit:text-recognition and com.google.mlkit:barcode-scanning unconditional dependencies, defeating the library's selective-install purpose: a consumer who only wants barcode scanning still got the full text-recognition SDK bundled, and vice versa. Root cause: Kotlin has no #if-equivalent preprocessor. Six Kotlin files (TextRecognitionServiceFactory/MLKitTextRecognitionService/MLKitTextAdapter and their barcode equivalents) import concrete MLKit types at file scope, so they fail to compile whenever the corresponding artifact is missing from the classpath - regardless of whether the runtime feature flag ever lets that code execute. iOS never had this problem since its Swift equivalents use #if MLKIT_TEXT_RECOGNITION_ANY / #if MLKIT_BARCODE_SCANNING, which genuinely elides the guarded code (and its imports) at compile time. Fixed by giving Kotlin the same guarantee via Gradle conditional source sets instead of reflection (MLKit's real client APIs return async Task<T> objects with listener callbacks, which would need a java.lang.reflect.Proxy to invoke reflectively - fragile under R8 and adds reflection overhead to code that runs every camera frame): - Moved the 6 MLKit-coupled files into textRecognitionEnabled/ barcodeScanningEnabled source sets (content unchanged, path only). - Added tiny stub factories in textRecognitionDisabled/ barcodeScanningDisabled that throw if ever reached (they aren't - HybridVisionCameraMLKit already gates construction before this point), existing purely so the always-compiled Hybrid* classes have something to compile against. - android/build.gradle picks the enabled/disabled source dir per flag, and re-gates the two base MLKit SDKs behind their flags again. - Added com.google.mlkit:vision-common as an explicit, always-on dependency: three files (ProcessedImage, ImagePreprocessor, HybridFrameSpec+toProcessedImage) use its InputImage type unconditionally for the shared image-preprocessing pipeline. This isn't a version bump - it's already resolved transitively today at the same 17.3.0. - Updated the README caveat that documented this exact limitation, since it's no longer true. Verified locally (not just "it compiles"): with barcodeScanning:false, com.google.mlkit:barcode-scanning is fully absent from the resolved debugRuntimeClasspath; with textRecognition and all 4 language variants false, com.google.mlkit:text-recognition is fully absent. Full yarn test suite passes (93/93).
Audited README.md and docs/*.md against what v2 actually ships. Content was accurate, but two Expo plugin behaviors were undocumented anywhere (Android-only keys silently dropped from the iOS Podfile hash, and no Kotlin DSL/.kts support), and neither selective install nor the Expo plugin had a dedicated doc - both only existed condensed in the README. Add docs/selective-mlkit-install.md: what "selective" guarantees now (compile-time removal on both platforms per the recent Android fix, not just runtime gating), defaults table, Android/iOS/Expo setup, and how to verify a disabled feature's SDK is actually absent from the resolved dependency graph. Add docs/expo-plugin.md: usage examples, what the plugin actually writes and where, validation behavior, and the previously-undocumented platform caveats (Android-only key dropping, Groovy-only support, idempotency via marker comments, createRunOncePlugin behavior). Link both from README's API Docs list and from the inline Expo section.
Description now mentions the selective/optional install guarantee restored this release. Keywords gain nitro, expo, and config-plugin to reflect v2's Nitro architecture and the Expo config plugin, both of which had no keyword coverage before.
Contributor
|
ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR. |
…it stubs The textRecognitionDisabled/barcodeScanningDisabled stub factories added for selective install had single-throw block bodies, which ktlint's standard:function-expression-body rule flags in favor of = throw ... expression-body form. Auto-fixed via `ktlint --format`; no behavior change.
Contributor
|
ECC bundle files are already tracked in this repository. Skipping generation of another bundle PR. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Major release: migrates the library from the legacy TurboModule bridge to VisionCamera v5 + Nitro Modules. This is a breaking release (v1 → v2) — see docs/migration-v1-to-v2.md.
BoundingBoxfield population (Android), a cross-Worklet-Runtime closure bug fix, Bottom Sheet dependency swap in the example app.#if-based behavior — disablingtextRecognition/barcodeScanningnow genuinely removes the native SDK from the build on both platforms, not just at runtime.xcodebuilddestination string bug) and Android (codegen duplicate-class bug, missing CMake dependency) build failures uncovered while auditing the branch for release-readiness.docs/selective-mlkit-install.mdanddocs/expo-plugin.md, refreshedpackage.jsondescription/keywords and GitHub repo description/topics to reflect the Nitro architecture, selective install, and Expo plugin.package.jsonversion is already2.0.0on this branch (no further bump needed before tagging).Test plan
yarn testsuite passes (93/93)barcodeScanning:falseandtextRecognition:false(+ all language variants) each compile cleanly and the correspondingcom.google.mlkit:*artifact is fully absent from the resolved Gradle dependency graphnpm install react-native-vision-camera-mlkit@2.0.0in a scratch project after publish