Description
extractTextFromImage() throws a generic "err" error when passed file paths from react-native-vision-camera on Android 10+. This is due to Android's scoped storage restrictions preventing file:// URIs from being used with InputImage.fromFilePath().
Environment
- Package version:
expo-text-extractor@0.1.2
- Platform: Android 10+ (API 29+)
- Camera library:
react-native-vision-camera (returns raw file system paths)
- Expo SDK: 54
Steps to Reproduce
- Capture photo using
react-native-vision-camera:
const photoData = await cameraRef.current.takePhoto();
// photoData.path = "/data/user/0/com.example.app/cache/photo.jpg"
- Pass the path to
extractTextFromImage:
const textBlocks = await extractTextFromImage(photoData.path);
- Error thrown:
CodedException("err", ...)
Expected Behavior
Text extraction should work with raw file system paths from camera libraries that don't use content URIs.
Actual Behavior
Function fails with generic "err" error. Logs show:
Text recognition failed - ML Kit error {
"errorCode": "ERR_CODED",
"errorMessage": "err",
"errorName": "Error"
}
Root Cause Analysis
Current native implementation (ExpoTextExtractorModule.kt):
val uri = if (uriString.startsWith("content://")) {
Uri.parse(uriString)
} else {
val file = File(uriString)
Uri.fromFile(file) // Creates file:// URI
}
val inputImage = InputImage.fromFilePath(context, uri) // ❌ Fails on Android 10+
Problem: According to the ML Kit InputImage documentation, fromFilePath(Context context, Uri imageUri) expects a proper content URI or an accessible file URI. On Android 10+ with scoped storage, file:// URIs created from Uri.fromFile() throw FileUriExposedException when passed to this method.
Why it works in the demo:
The demo uses expo-image-picker, which returns properly formatted content:// URIs that Android's ContentResolver can handle. Apps using other camera libraries (like react-native-vision-camera) that return raw file system paths will fail.
Suggested Fix
Use InputImage.fromBitmap() for file paths instead:
AsyncFunction("extractTextFromImage") { uriString: String, promise: Promise ->
try {
val context = appContext.reactContext!!
val inputImage = if (uriString.startsWith("content://")) {
// Handle content URIs (from expo-image-picker)
val uri = Uri.parse(uriString)
InputImage.fromFilePath(context, uri)
} else {
// Handle raw file paths (from react-native-vision-camera, etc.)
val file = File(uriString)
if (!file.exists()) {
throw Exception("File not found: $uriString")
}
// Use fromBitmap instead of fromFilePath to avoid FileUriExposedException
val bitmap = BitmapFactory.decodeFile(uriString)
if (bitmap == null) {
throw Exception("Failed to decode image file: $uriString")
}
InputImage.fromBitmap(bitmap, 0)
}
val recognizer = TextRecognition.getClient(TextRecognizerOptions.DEFAULT_OPTIONS)
recognizer.process(inputImage)
.addOnSuccessListener { visionText ->
val recognizedTexts = visionText.textBlocks.map { it.text }
promise.resolve(recognizedTexts)
}
.addOnFailureListener { error ->
// Include actual error message for debugging
promise.reject(
CodedException(
"ERR_TEXT_RECOGNITION",
error.message ?: "Text recognition failed",
error
)
)
}
} catch (error: Exception) {
promise.reject(
CodedException(
"ERR_UNKNOWN",
error.message ?: "Unknown error",
error
)
)
}
}
Additional Improvement
Update error handling to return the actual error message instead of generic "err":
.addOnFailureListener { error ->
promise.reject(
CodedException(
"ERR_TEXT_RECOGNITION",
error.message ?: "Text recognition failed",
error
)
)
}
Currently returns just CodedException("err", error) which loses the actual error information.
Current Workaround
For apps affected by this issue, we've had to disable post-capture OCR on Android:
if (Platform.OS === 'android') {
// Skip broken expo-text-extractor, show manual entry instead
return false;
}
Impact
This affects any app using:
- ✅ Works:
expo-image-picker (returns content:// URIs)
- ❌ Fails:
react-native-vision-camera (returns file system paths)
- ❌ Fails: Any direct file system image access
- ❌ Fails: Android 10+ with scoped storage enabled
References
Description
extractTextFromImage()throws a generic "err" error when passed file paths fromreact-native-vision-cameraon Android 10+. This is due to Android's scoped storage restrictions preventingfile://URIs from being used withInputImage.fromFilePath().Environment
expo-text-extractor@0.1.2react-native-vision-camera(returns raw file system paths)Steps to Reproduce
react-native-vision-camera:extractTextFromImage:CodedException("err", ...)Expected Behavior
Text extraction should work with raw file system paths from camera libraries that don't use content URIs.
Actual Behavior
Function fails with generic "err" error. Logs show:
Root Cause Analysis
Current native implementation (
ExpoTextExtractorModule.kt):Problem: According to the ML Kit InputImage documentation,
fromFilePath(Context context, Uri imageUri)expects a proper content URI or an accessible file URI. On Android 10+ with scoped storage,file://URIs created fromUri.fromFile()throwFileUriExposedExceptionwhen passed to this method.Why it works in the demo:
The demo uses
expo-image-picker, which returns properly formattedcontent://URIs that Android's ContentResolver can handle. Apps using other camera libraries (likereact-native-vision-camera) that return raw file system paths will fail.Suggested Fix
Use
InputImage.fromBitmap()for file paths instead:Additional Improvement
Update error handling to return the actual error message instead of generic "err":
.addOnFailureListener { error -> promise.reject( CodedException( "ERR_TEXT_RECOGNITION", error.message ?: "Text recognition failed", error ) ) }Currently returns just
CodedException("err", error)which loses the actual error information.Current Workaround
For apps affected by this issue, we've had to disable post-capture OCR on Android:
Impact
This affects any app using:
expo-image-picker(returnscontent://URIs)react-native-vision-camera(returns file system paths)References