react-native-image-crop-picker
Native iOS/Android image and video picker for React Native with built-in cropping, camera capture, and multi-select support.
Repository Health
Technical Analysis
react-native-image-crop-picker wraps the native photo library and camera APIs on iOS and Android behind a single JavaScript surface, giving React Native apps a picker that supports single or multiple selection, in-place image cropping, video selection, and configurable compression without shipping a separate native module for each platform.
The library exposes a small set of promise-based functions (openPicker, openCamera, openCropper, clean, cleanSingle) that return normalized asset metadata (path, dimensions, mime type, size, optional EXIF and base64 data) regardless of which OS produced them, while still surfacing dozens of platform-specific tuning options (cropper toolbar colors, smart album filters, rotation gestures) for apps that need finer control.
What You Get
- openPicker/openCamera/openCropper promise-based APIs for gallery, camera, and standalone cropping flows
- Single or multiple selection with configurable minFiles/maxFiles
- Built-in cropping UI on both platforms with per-platform styling hooks (toolbar color, guidelines, circular overlay)
- Video selection and configurable video compression presets
- Optional base64 encoding and EXIF metadata extraction on the returned asset
- clean()/cleanSingle() helpers to purge the temporary files the picker creates
Common Use Cases
- Profile photo upload flows that need cropping before submission
- Multi-image attachments in chat, social, or listing apps
- In-app camera capture for KYC/document or receipt scanning flows
- Video attachment selection with size-conscious compression before upload
Under The Hood
Architecture The module is a thin TurboModule spec (src/NativeImageCropPicker.ts) re-exported from index.js, with the real logic living in separate native implementations per platform: android/src/main/java/com/reactnative/ivpusic/imagepicker/ImageCropPicker.java handles the Android bridge module, delegating to helper classes (Compression.java, ResultCollector.java, ExifExtractor.java, RealPathUtil.java, IvpusicImagePickerFileProvider.java) for compression, multi-select result aggregation, EXIF parsing, and content-URI resolution; separate android/src/newArch and android/src/oldArch java trees let the same core logic build against both the legacy bridge and the new Fabric/TurboModule architecture. iOS mirrors this with ios/src/ImageCropPicker.mm as the bridge entry point plus UIImage+Extension/UIImage+Resize/Compression helpers, and vendors a forked QBImagePicker framework (ios/QBImagePicker, ios/ImageCropPickerSDK) for the gallery/crop UI rather than building one from scratch. Changing the core response shape would ripple through both native layers and the shared TypeScript types.
Tech Stack On the JS side the package has no runtime dependencies beyond peerDependencies on react and react-native, ships TypeScript types via index.d.ts, and defines a TurboModule spec for codegen (codegenConfig in package.json targets RNCImageCropPickerSpec). Android builds with Gradle against AndroidX Activity/Fragment/ExifInterface libraries and the system photo/camera intents; iOS builds via CocoaPods (RNImageCropPicker.podspec) linking the vendored QBImagePicker framework and a TOCropViewController-style cropper. No separate JS build step is needed since consuming apps’ own Metro/RN toolchain handles the bundle.
Code Quality There are no automated JS tests (the package.json test script only echoes an error and exits 1) and no visible native unit test suite (android/src/test contains only an IDE module file, not test code). Native error handling is explicit and promise-based - Android’s ImageCropPicker.java rejects with named error codes (E_ACTIVITY_DOES_NOT_EXIST, E_NO_CAMERA_PERMISSION_KEY, E_ERROR_WHILE_CLEANING_FILES) wrapped in try/catch rather than swallowing exceptions, and permission checks run explicitly before triggering camera/gallery intents. Naming follows consistent per-platform conventions, and the JS/TS surface is fully typed, but there is no CI-visible linter/formatter config and no automated test coverage guarding native or JS behavior.
API Design The public API is intentionally small - five promise-returning functions (openPicker, openCamera, openCropper, clean, cleanSingle) cover the whole feature set, and almost all customization goes through one flat options object per call, keeping the common single-picker case to one line. The tradeoff is a large flat options surface spanning both iOS-only and Android-only knobs, so platform scope is encoded in option names (e.g. an android-only toolbar color option) rather than a typed per-platform split, pushing consumers to the README table to know which options apply where. The TurboModule Spec and the fuller documented PickerOptions type also diverge somewhat in surface, suggesting the typed spec lags the broader option set.