Skip to content

extractTextFromImage fails with file:// URIs on Android 10+ (FileUriExposedException) #17

@spsaucier

Description

@spsaucier

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

  1. Capture photo using react-native-vision-camera:
const photoData = await cameraRef.current.takePhoto();
// photoData.path = "/data/user/0/com.example.app/cache/photo.jpg"
  1. Pass the path to extractTextFromImage:
const textBlocks = await extractTextFromImage(photoData.path);
  1. 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

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions