From a3fee36bde1772cd545f2581cd5c27c7b7ceea24 Mon Sep 17 00:00:00 2001 From: Vojtech Novak Date: Thu, 1 Jan 2026 20:33:20 +0000 Subject: [PATCH 1/2] chore(android): internal refactors --- .../CopyDestination.kt | 3 +- .../DocumentMetadataBuilder.kt | 7 +- .../FileOperations.kt | 4 +- .../IntentFactory.kt | 36 ------ .../IsKnownTypeImpl.kt | 54 ++++----- .../MetadataGetter.kt | 114 +++++++++++------- .../reactnativedocumentpicker/PickOptions.kt | 66 ++++++---- .../PromiseWrapper.java | 105 ---------------- .../PromiseWrapper.kt | 83 +++++++++++++ .../RNDocumentPickerModule.kt | 10 +- packages/document-picker/package.json | 3 +- 11 files changed, 235 insertions(+), 250 deletions(-) delete mode 100644 packages/document-picker/android/src/main/java/com/reactnativedocumentpicker/IntentFactory.kt delete mode 100644 packages/document-picker/android/src/main/java/com/reactnativedocumentpicker/PromiseWrapper.java create mode 100644 packages/document-picker/android/src/main/java/com/reactnativedocumentpicker/PromiseWrapper.kt diff --git a/packages/document-picker/android/src/main/java/com/reactnativedocumentpicker/CopyDestination.kt b/packages/document-picker/android/src/main/java/com/reactnativedocumentpicker/CopyDestination.kt index 6ea558a5..9e42193b 100644 --- a/packages/document-picker/android/src/main/java/com/reactnativedocumentpicker/CopyDestination.kt +++ b/packages/document-picker/android/src/main/java/com/reactnativedocumentpicker/CopyDestination.kt @@ -6,7 +6,6 @@ enum class CopyDestination(val preset: String) { DOCUMENT_DIRECTORY("documentDirectory"); companion object { - // keep values() for RN 73 compatibility - fun fromPath(path: String): CopyDestination = values().find { it.preset == path } ?: CACHES_DIRECTORY + fun fromPath(path: String): CopyDestination = entries.find { it.preset == path } ?: CACHES_DIRECTORY } } diff --git a/packages/document-picker/android/src/main/java/com/reactnativedocumentpicker/DocumentMetadataBuilder.kt b/packages/document-picker/android/src/main/java/com/reactnativedocumentpicker/DocumentMetadataBuilder.kt index 15593444..49e6b924 100644 --- a/packages/document-picker/android/src/main/java/com/reactnativedocumentpicker/DocumentMetadataBuilder.kt +++ b/packages/document-picker/android/src/main/java/com/reactnativedocumentpicker/DocumentMetadataBuilder.kt @@ -52,10 +52,11 @@ class DocumentMetadataBuilder(forUri: Uri) { openableMimeTypes?.let { val arrayOfExtensionsAndMime = Arguments.createArray() it.forEach { mimeType -> - val virtualFileDetails = Arguments.createMap() val maybeExtension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType) - virtualFileDetails.putString("mimeType", mimeType) - virtualFileDetails.putString("extension", maybeExtension) + val virtualFileDetails = Arguments.createMap().apply { + putString("mimeType", mimeType) + putString("extension", maybeExtension) + } arrayOfExtensionsAndMime.pushMap(virtualFileDetails) } map.putArray("convertibleToMimeTypes", arrayOfExtensionsAndMime) diff --git a/packages/document-picker/android/src/main/java/com/reactnativedocumentpicker/FileOperations.kt b/packages/document-picker/android/src/main/java/com/reactnativedocumentpicker/FileOperations.kt index 7e1f98b0..074c3a72 100644 --- a/packages/document-picker/android/src/main/java/com/reactnativedocumentpicker/FileOperations.kt +++ b/packages/document-picker/android/src/main/java/com/reactnativedocumentpicker/FileOperations.kt @@ -189,8 +189,8 @@ class FileOperations(private val uriMap: MutableMap) { } val copyStreamToAnother: (InputStream, OutputStream) -> Long = { inputStream, outputStream -> - inputStream.use { input -> - outputStream.use { output -> + inputStream.use { _ -> + outputStream.use { _ -> val bytesCopied = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { FileUtils.copy(inputStream, outputStream) } else { diff --git a/packages/document-picker/android/src/main/java/com/reactnativedocumentpicker/IntentFactory.kt b/packages/document-picker/android/src/main/java/com/reactnativedocumentpicker/IntentFactory.kt deleted file mode 100644 index 62590abb..00000000 --- a/packages/document-picker/android/src/main/java/com/reactnativedocumentpicker/IntentFactory.kt +++ /dev/null @@ -1,36 +0,0 @@ -package com.reactnativedocumentpicker - -import android.content.Intent -import android.os.Build -import android.provider.DocumentsContract - -object IntentFactory { - fun getPickIntent(options: PickOptions): Intent { - // TODO option for extra task on stack? - // reminder - flags are for granting rights to others - - return Intent(options.action).apply { - val types = options.mimeTypes - - type = - if (types.size > 1) { - putExtra(Intent.EXTRA_MIME_TYPES, types) - options.intentFilterTypes - } else { - types[0] - } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && - options.initialDirectoryUrl != null - ) { - // only works for ACTION_OPEN_DOCUMENT - // TODO must be URI - putExtra(DocumentsContract.EXTRA_INITIAL_URI, options.initialDirectoryUrl) - } - if (!options.allowVirtualFiles) { - addCategory(Intent.CATEGORY_OPENABLE) - } - putExtra(Intent.EXTRA_LOCAL_ONLY, options.localOnly) - putExtra(Intent.EXTRA_ALLOW_MULTIPLE, options.multiple) - } - } -} diff --git a/packages/document-picker/android/src/main/java/com/reactnativedocumentpicker/IsKnownTypeImpl.kt b/packages/document-picker/android/src/main/java/com/reactnativedocumentpicker/IsKnownTypeImpl.kt index 1811e29e..52303587 100644 --- a/packages/document-picker/android/src/main/java/com/reactnativedocumentpicker/IsKnownTypeImpl.kt +++ b/packages/document-picker/android/src/main/java/com/reactnativedocumentpicker/IsKnownTypeImpl.kt @@ -4,37 +4,35 @@ import android.webkit.MimeTypeMap import com.facebook.react.bridge.Arguments import com.facebook.react.bridge.WritableMap -class IsKnownTypeImpl { - companion object { - fun isKnownType(kind: String, value: String): WritableMap { - return when (kind) { - "mimeType" -> { - val extensionForMime = MimeTypeMap.getSingleton().getExtensionFromMimeType(value) - createMap( - isKnown = extensionForMime != null, - preferredFilenameExtension = extensionForMime, - mimeType = if (extensionForMime != null) value else null - ) - } - "extension" -> { - val mimeForExtension = MimeTypeMap.getSingleton().getMimeTypeFromExtension(value) - createMap( - isKnown = mimeForExtension != null, - preferredFilenameExtension = if (mimeForExtension != null) value else null, - mimeType = mimeForExtension - ) - } - else -> createMap(isKnown = false, preferredFilenameExtension = null, mimeType = null) +object IsKnownTypeImpl { + fun isKnownType(kind: String, value: String): WritableMap { + return when (kind) { + "mimeType" -> { + val extensionForMime = MimeTypeMap.getSingleton().getExtensionFromMimeType(value) + createMap( + isKnown = extensionForMime != null, + preferredFilenameExtension = extensionForMime, + mimeType = if (extensionForMime != null) value else null + ) } + "extension" -> { + val mimeForExtension = MimeTypeMap.getSingleton().getMimeTypeFromExtension(value) + createMap( + isKnown = mimeForExtension != null, + preferredFilenameExtension = if (mimeForExtension != null) value else null, + mimeType = mimeForExtension + ) + } + else -> createMap(isKnown = false, preferredFilenameExtension = null, mimeType = null) } + } - private fun createMap(isKnown: Boolean, preferredFilenameExtension: String?, mimeType: String?): WritableMap { - return Arguments.createMap().apply { - putNull("UTType") - putBoolean("isKnown", isKnown) - putString("preferredFilenameExtension", preferredFilenameExtension) - putString("mimeType", mimeType) - } + private fun createMap(isKnown: Boolean, preferredFilenameExtension: String?, mimeType: String?): WritableMap { + return Arguments.createMap().apply { + putNull("UTType") + putBoolean("isKnown", isKnown) + putString("preferredFilenameExtension", preferredFilenameExtension) + putString("mimeType", mimeType) } } } diff --git a/packages/document-picker/android/src/main/java/com/reactnativedocumentpicker/MetadataGetter.kt b/packages/document-picker/android/src/main/java/com/reactnativedocumentpicker/MetadataGetter.kt index 6b5c59fb..ecb00d0c 100644 --- a/packages/document-picker/android/src/main/java/com/reactnativedocumentpicker/MetadataGetter.kt +++ b/packages/document-picker/android/src/main/java/com/reactnativedocumentpicker/MetadataGetter.kt @@ -75,17 +75,39 @@ class MetadataGetter(private val uriMap: MutableMap) { contentResolver: ContentResolver, metadataBuilder: DocumentMetadataBuilder, couldBeVirtualFile: Boolean + ) { + try { + queryContentResolverMetadataInternal(contentResolver, metadataBuilder, couldBeVirtualFile) + } catch (e: Exception) { + val suppressedSummary = + e.suppressed.joinToString(separator = "; ") { suppressed -> + "${suppressed.javaClass.simpleName}: ${suppressed.message ?: "no message"}" + } + metadataBuilder.metadataReadingError( + "Could not read file metadata: ${e.javaClass.simpleName}: ${e.message ?: "no message"}" + + (" (suppressed summary: [$suppressedSummary])") + ) + } + } + + private fun queryContentResolverMetadataInternal( + contentResolver: ContentResolver, + metadataBuilder: DocumentMetadataBuilder, + couldBeVirtualFile: Boolean ) { val forUri = metadataBuilder.getUri() + val hasNoMime = !metadataBuilder.hasMime() val projection = mutableListOf( - DocumentsContract.Document.COLUMN_MIME_TYPE, OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE, ).apply { if (couldBeVirtualFile) { add(DocumentsContract.Document.COLUMN_FLAGS) } + if (hasNoMime) { + add(DocumentsContract.Document.COLUMN_MIME_TYPE) + } }.toTypedArray() contentResolver @@ -97,39 +119,41 @@ class MetadataGetter(private val uriMap: MutableMap) { null ) .use { cursor -> - if (cursor != null && cursor.moveToFirst()) { - metadataBuilder.name( - getCursorValue(cursor, OpenableColumns.DISPLAY_NAME, String::class.java) - ) - - if (!metadataBuilder.hasMime()) { - metadataBuilder.mimeType( - getCursorValue( - cursor, DocumentsContract.Document.COLUMN_MIME_TYPE, String::class.java - ) + if (cursor == null) { + metadataBuilder.metadataReadingError("Could not read file metadata because cursor was null. This is likely an issue with the underlying ContentProvider.") + return + } + if (!cursor.moveToFirst()) { + metadataBuilder.metadataReadingError("Could not read file metadata because cursor could not move to the first result row. This is likely an issue with the underlying ContentProvider. Row count: ${cursor.count}, columns: ${cursor.columnNames.joinToString(",")}") + return + } + metadataBuilder.name( + getCursorValue(cursor, OpenableColumns.DISPLAY_NAME, String::class.java) + ) + metadataBuilder.size(getCursorValue(cursor, OpenableColumns.SIZE, Long::class.java)) + + if (hasNoMime) { + metadataBuilder.mimeType( + getCursorValue( + cursor, DocumentsContract.Document.COLUMN_MIME_TYPE, String::class.java ) - } + ) + } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - // https://developer.android.com/training/data-storage/shared/documents-files#open-virtual-file - val isVirtual = - if (couldBeVirtualFile) { - val cursorValue: Int = - getCursorValue( - cursor, DocumentsContract.Document.COLUMN_FLAGS, Int::class.java - ) - ?: 0 - cursorValue and DocumentsContract.Document.FLAG_VIRTUAL_DOCUMENT != 0 - } else { - false - } - metadataBuilder.virtual(isVirtual) - } - metadataBuilder.size(getCursorValue(cursor, OpenableColumns.SIZE, Long::class.java)) - } else { - // metadataBuilder only contains the uri, type and error in this unlikely case - // there's nothing more we can do - metadataBuilder.metadataReadingError("Could not read file metadata") + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + // https://developer.android.com/training/data-storage/shared/documents-files#open-virtual-file + val isVirtual = + if (couldBeVirtualFile) { + val cursorValue: Int = + getCursorValue( + cursor, DocumentsContract.Document.COLUMN_FLAGS, Int::class.java + ) + ?: 0 + cursorValue and DocumentsContract.Document.FLAG_VIRTUAL_DOCUMENT != 0 + } else { + false + } + metadataBuilder.virtual(isVirtual) } } } @@ -137,19 +161,19 @@ class MetadataGetter(private val uriMap: MutableMap) { @Suppress("UNCHECKED_CAST") private fun getCursorValue(cursor: Cursor, columnName: String, valueType: Class): T? { val columnIndex = cursor.getColumnIndex(columnName) - if (columnIndex != -1 && !cursor.isNull(columnIndex)) { - return runCatching { - when (valueType) { - String::class.java -> cursor.getString(columnIndex) as T - Int::class.java -> cursor.getInt(columnIndex) as T - Long::class.java -> cursor.getLong(columnIndex) as T - Double::class.java -> cursor.getDouble(columnIndex) as T - Float::class.java -> cursor.getFloat(columnIndex) as T - else -> null - } - // throw should not happen but if it does, we return null - }.getOrNull() + if (columnIndex == -1 || cursor.isNull(columnIndex)) { + return null } - return null + return runCatching { + when (valueType) { + String::class.java -> cursor.getString(columnIndex) as T + Int::class.java -> cursor.getInt(columnIndex) as T + Long::class.java -> cursor.getLong(columnIndex) as T + Double::class.java -> cursor.getDouble(columnIndex) as T + Float::class.java -> cursor.getFloat(columnIndex) as T + else -> null + } + // throw should not happen but if it does, we return null + }.getOrNull() } } diff --git a/packages/document-picker/android/src/main/java/com/reactnativedocumentpicker/PickOptions.kt b/packages/document-picker/android/src/main/java/com/reactnativedocumentpicker/PickOptions.kt index 68cfea8b..184f8960 100644 --- a/packages/document-picker/android/src/main/java/com/reactnativedocumentpicker/PickOptions.kt +++ b/packages/document-picker/android/src/main/java/com/reactnativedocumentpicker/PickOptions.kt @@ -2,6 +2,8 @@ package com.reactnativedocumentpicker import android.content.Intent +import android.os.Build +import android.provider.DocumentsContract import com.facebook.react.bridge.ReadableArray import com.facebook.react.bridge.ReadableMap @@ -14,6 +16,20 @@ data class PickOptions( val requestLongTermAccess: Boolean, val allowVirtualFiles: Boolean, ) { + constructor(readableMap: ReadableMap) : this( + mode = readableMap.getString("mode"), + mimeTypes = if (readableMap.hasKey("type") && !readableMap.isNull("type")) { + readableMap.getArray("type")?.let { readableArrayToStringArray(it) } ?: arrayOf("*/*") + } else { + arrayOf("*/*") + }, + initialDirectoryUrl = if (readableMap.hasKey("initialDirectoryUrl")) readableMap.getString("initialDirectoryUrl") else null, + localOnly = readableMap.hasKey("localOnly") && readableMap.getBoolean("localOnly"), + multiple = readableMap.hasKey("allowMultiSelection") && readableMap.getBoolean("allowMultiSelection"), + requestLongTermAccess = readableMap.hasKey("requestLongTermAccess") && readableMap.getBoolean("requestLongTermAccess"), + allowVirtualFiles = readableMap.hasKey("allowVirtualFiles") && readableMap.getBoolean("allowVirtualFiles") + ) + val action: String get() = if ("open" == mode) Intent.ACTION_OPEN_DOCUMENT else Intent.ACTION_GET_CONTENT @@ -26,33 +42,37 @@ data class PickOptions( mimeTypes.joinToString("|") } } -} -fun parsePickOptions(readableMap: ReadableMap): PickOptions { - val mode = readableMap.getString("mode") + fun getPickIntent(): Intent { + // TODO option for extra task on stack? + // reminder - flags are for granting rights to others - val mimeTypes = if (readableMap.hasKey("type") && !readableMap.isNull("type")) { - readableMap.getArray("type")?.let { readableArrayToStringArray(it) } ?: arrayOf("*/*") - } else { - arrayOf("*/*") - } + return Intent(action).apply { + val types = mimeTypes - val initialDirectoryUrl = if (readableMap.hasKey("initialDirectoryUrl")) readableMap.getString("initialDirectoryUrl") else null - val localOnly = readableMap.hasKey("localOnly") && readableMap.getBoolean("localOnly") - val multiple = readableMap.hasKey("allowMultiSelection") && readableMap.getBoolean("allowMultiSelection") - val requestLongTermAccess = readableMap.hasKey("requestLongTermAccess") && readableMap.getBoolean("requestLongTermAccess") - val allowVirtualFiles = readableMap.hasKey("allowVirtualFiles") && readableMap.getBoolean("allowVirtualFiles") - - return PickOptions( - mode = mode, - mimeTypes = mimeTypes, - initialDirectoryUrl = initialDirectoryUrl, - localOnly = localOnly, - multiple = multiple, - requestLongTermAccess = requestLongTermAccess, - allowVirtualFiles = allowVirtualFiles, - ) + type = + if (types.size > 1) { + putExtra(Intent.EXTRA_MIME_TYPES, types) + intentFilterTypes + } else { + types[0] + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && + initialDirectoryUrl != null + ) { + // only works for ACTION_OPEN_DOCUMENT + // TODO must be URI + putExtra(DocumentsContract.EXTRA_INITIAL_URI, initialDirectoryUrl) + } + if (!allowVirtualFiles) { + addCategory(Intent.CATEGORY_OPENABLE) + } + putExtra(Intent.EXTRA_LOCAL_ONLY, localOnly) + putExtra(Intent.EXTRA_ALLOW_MULTIPLE, multiple) + } + } } + fun readableArrayToStringArray(readableArray: ReadableArray): Array { /** * MIME type and Uri scheme matching in the diff --git a/packages/document-picker/android/src/main/java/com/reactnativedocumentpicker/PromiseWrapper.java b/packages/document-picker/android/src/main/java/com/reactnativedocumentpicker/PromiseWrapper.java deleted file mode 100644 index 08103627..00000000 --- a/packages/document-picker/android/src/main/java/com/reactnativedocumentpicker/PromiseWrapper.java +++ /dev/null @@ -1,105 +0,0 @@ -// LICENSE: see License.md in the package root -package com.reactnativedocumentpicker; - -import android.util.Log; - -import androidx.annotation.NonNull; - -import com.facebook.react.bridge.Promise; - -public class PromiseWrapper { - - private Promise promise; - private String nameOfCallInProgress; - public static final String ASYNC_OP_IN_PROGRESS = "ASYNC_OP_IN_PROGRESS"; - public static final String E_DOCUMENT_PICKER_CANCELED = "OPERATION_CANCELED"; - private final String MODULE_NAME; - - public PromiseWrapper(String moduleName) { - MODULE_NAME = moduleName; - } - - public void setPromiseRejectingPrevious(Promise promise, @NonNull String fromCallsite) { - Promise previousPromise = this.promise; - if (previousPromise != null) { - rejectPreviousPromiseBecauseNewOneIsInProgress(previousPromise, fromCallsite); - } - this.promise = promise; - nameOfCallInProgress = fromCallsite; - } - - public boolean trySetPromiseRejectingIncoming(Promise promise, @NonNull String fromCallsite) { - Promise previousPromise = this.promise; - if (previousPromise != null) { - rejectNewPromiseBecauseOldOneIsInProgress(promise, fromCallsite); - return false; - } - this.promise = promise; - nameOfCallInProgress = fromCallsite; - return true; - } - - public void resolve(Object value) { - Promise resolver = promise; - if (resolver == null) { - Log.e(MODULE_NAME, "cannot resolve promise because it's null"); - return; - } - - resetMembers(); - resolver.resolve(value); - } - - public void reject(@NonNull String code, Exception e) { - String message = e.getLocalizedMessage() != null ? e.getLocalizedMessage() : - e.getMessage() != null ? e.getMessage() : "unknown error"; - - this.reject(code, message, e); - } - - public void reject(Exception e) { - String message = e.getLocalizedMessage() != null ? e.getLocalizedMessage() : - e.getMessage() != null ? e.getMessage() : "unknown error"; - - this.reject(nameOfCallInProgress, message, e); - } - - public void rejectAsUserCancelledOperation() { - this.reject(E_DOCUMENT_PICKER_CANCELED, "user canceled the document picker"); - } - - public void reject(@NonNull String code, @NonNull String message) { - reject(code, message, null); - } - public void reject(@NonNull String code, @NonNull String message, Exception e) { - Promise rejecter = promise; - if (rejecter == null) { - Log.e(MODULE_NAME, "cannot reject promise because it's null"); - return; - } - - resetMembers(); - rejecter.reject(code, message, e); - } - - public String getNameOfCallInProgress() { - return nameOfCallInProgress; - } - - private void resetMembers() { - nameOfCallInProgress = null; - promise = null; - } - - - private void rejectPreviousPromiseBecauseNewOneIsInProgress(Promise promise, String requestedOperation) { - // TODO better message - promise.reject(ASYNC_OP_IN_PROGRESS, "Warning: previous promise did not settle and was overwritten. " + - "You've called \"" + requestedOperation + "\" while \"" + getNameOfCallInProgress() + "\" was already in progress and has not completed yet."); - } - private void rejectNewPromiseBecauseOldOneIsInProgress(Promise promise, String requestedOperation) { - // TODO better message - promise.reject(ASYNC_OP_IN_PROGRESS, "Warning: previous promise did not settle and you attempted to overwrite it. " + - "You've called \"" + requestedOperation + "\" while \"" + getNameOfCallInProgress() + "\" was already in progress and has not completed yet."); - } -} diff --git a/packages/document-picker/android/src/main/java/com/reactnativedocumentpicker/PromiseWrapper.kt b/packages/document-picker/android/src/main/java/com/reactnativedocumentpicker/PromiseWrapper.kt new file mode 100644 index 00000000..9ff99475 --- /dev/null +++ b/packages/document-picker/android/src/main/java/com/reactnativedocumentpicker/PromiseWrapper.kt @@ -0,0 +1,83 @@ +// LICENSE: see License.md in the package root +package com.reactnativedocumentpicker + +import android.util.Log +import com.facebook.react.bridge.Promise + +class PromiseWrapper(private val MODULE_NAME: String) { + private var promise: Promise? = null + private var nameOfCallInProgress: String? = null + + fun trySetPromiseRejectingIncoming(promise: Promise, fromCallsite: String): Boolean { + val previousPromise = this.promise + if (previousPromise != null) { + rejectNewPromiseBecauseOldOneIsInProgress(promise, fromCallsite) + return false + } + this.promise = promise + nameOfCallInProgress = fromCallsite + return true + } + + fun resolve(value: Any?) { + val resolver = promise + if (resolver == null) { + Log.e(MODULE_NAME, "cannot resolve promise because it's null") + return + } + + resetMembers() + resolver.resolve(value) + } + + fun reject(code: String, e: Exception) { + val message = + e.localizedMessage ?: e.message ?: "unknown error" + + this.reject(code, message, e) + } + + fun reject(e: Exception) { + val message = + e.localizedMessage ?: e.message ?: "unknown error" + + this.reject(nameOfCallInProgress ?: "unknown call in progress", message, e) + } + + fun rejectAsUserCancelledOperation() { + this.reject(E_DOCUMENT_PICKER_CANCELED, "user canceled the document picker") + } + + fun reject(code: String, message: String, e: Exception? = null) { + val rejecter = promise + if (rejecter == null) { + Log.e(MODULE_NAME, "cannot reject promise because it's null") + return + } + + resetMembers() + rejecter.reject(code, message, e) + } + + private fun resetMembers() { + nameOfCallInProgress = null + promise = null + } + + private fun rejectNewPromiseBecauseOldOneIsInProgress( + promise: Promise, + requestedOperation: String? + ) { + // TODO better message + promise.reject( + ASYNC_OP_IN_PROGRESS, + "Warning: previous promise did not settle and you attempted to overwrite it. " + + "You've called \"" + requestedOperation + "\" while \"" + this.nameOfCallInProgress + "\" was already in progress and has not completed yet." + ) + } + + companion object { + const val ASYNC_OP_IN_PROGRESS: String = "ASYNC_OP_IN_PROGRESS" + const val E_DOCUMENT_PICKER_CANCELED: String = "OPERATION_CANCELED" + } +} diff --git a/packages/document-picker/android/src/main/java/com/reactnativedocumentpicker/RNDocumentPickerModule.kt b/packages/document-picker/android/src/main/java/com/reactnativedocumentpicker/RNDocumentPickerModule.kt index 9d94627c..05cdcf3e 100644 --- a/packages/document-picker/android/src/main/java/com/reactnativedocumentpicker/RNDocumentPickerModule.kt +++ b/packages/document-picker/android/src/main/java/com/reactnativedocumentpicker/RNDocumentPickerModule.kt @@ -86,11 +86,11 @@ class RNDocumentPickerModule(reactContext: ReactApplicationContext) : if (!promiseWrapper.trySetPromiseRejectingIncoming(promise, "pick")) { return } - val options = parsePickOptions(opts) + val options = PickOptions(opts) currentPickOptions = options try { - val intent = IntentFactory.getPickIntent(options) + val intent = options.getPickIntent() currentActivity.startActivityForResult(intent, PICK_FILES_REQUEST_CODE) } catch (e: ActivityNotFoundException) { promiseWrapper.reject(UNABLE_TO_OPEN_FILE_TYPE, e) @@ -140,7 +140,7 @@ class RNDocumentPickerModule(reactContext: ReactApplicationContext) : if (!promiseWrapper.trySetPromiseRejectingIncoming(promise, "pickDirectory")) { return } - val options = parsePickOptions(opts) + val options = PickOptions(opts) currentPickOptions = options try { val intent = @@ -316,13 +316,13 @@ class RNDocumentPickerModule(reactContext: ReactApplicationContext) : companion object { fun rejectWithNullActivity(promise: Promise) { - promise.reject(PRESENTER_IS_NULL, PRESENTER_IS_NULL) + promise.reject(NULL_PRESENTER, "Current activity is null. Cannot present sign-in UI. Make sure there are no modal windows being presented or dismissed.") } private const val PICK_FILES_REQUEST_CODE = 41 private const val PICK_DIR_REQUEST_CODE = 42 private const val SAVE_DOC_REQUEST_CODE = 43 - private const val PRESENTER_IS_NULL = "NULL_PRESENTER" + private const val NULL_PRESENTER = "NULL_PRESENTER" private const val UNABLE_TO_OPEN_FILE_TYPE = "UNABLE_TO_OPEN_FILE_TYPE" private const val E_OTHER_PRESENTING_ERROR = "OTHER_PRESENTING_ERROR" private const val E_INVALID_DATA_RETURNED = "INVALID_DATA_RETURNED" diff --git a/packages/document-picker/package.json b/packages/document-picker/package.json index 751ab6ff..6805e503 100644 --- a/packages/document-picker/package.json +++ b/packages/document-picker/package.json @@ -61,7 +61,8 @@ }, "homepage": "https://github.com/react-native-documents/document-picker#readme", "publishConfig": { - "access": "public" + "access": "public", + "provenance": true }, "devDependencies": { "@tsconfig/node18": "^18.2.4", From 62581123bf687b66babe9abfa04b873b43bc377a Mon Sep 17 00:00:00 2001 From: Vojtech Novak Date: Fri, 2 Jan 2026 23:21:12 +0100 Subject: [PATCH 2/2] Improve error handling for file metadata queries --- .changeset/mighty-carrots-itch.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/mighty-carrots-itch.md diff --git a/.changeset/mighty-carrots-itch.md b/.changeset/mighty-carrots-itch.md new file mode 100644 index 00000000..6fabf856 --- /dev/null +++ b/.changeset/mighty-carrots-itch.md @@ -0,0 +1,5 @@ +--- +"@react-native-documents/picker": patch +--- + +chore(android): improve error handling when querying files metadata