From 2d2d2e274c6138d12bec68abbba26bffdb8dae7b Mon Sep 17 00:00:00 2001 From: Daniel Jimenez <122808735+daniJimen@users.noreply.github.com> Date: Fri, 19 Dec 2025 09:16:53 +0100 Subject: [PATCH 1/2] feat(android): Cache file only when `withData` is true When `withData` is false, this change avoids caching the file locally and instead builds the `FileInfo` object directly with the available details. The file is only copied to the cache directory if its byte data is explicitly requested. Additionally, this commit adds comprehensive KDoc documentation to the `openFileStream` function, explaining its purpose, parameters, and return value. --- .../mr/flutter/plugin/filepicker/FileUtils.kt | 69 ++++++++++++------- 1 file changed, 44 insertions(+), 25 deletions(-) diff --git a/android/src/main/kotlin/com/mr/flutter/plugin/filepicker/FileUtils.kt b/android/src/main/kotlin/com/mr/flutter/plugin/filepicker/FileUtils.kt index 0dcaad1d..4e852c7a 100644 --- a/android/src/main/kotlin/com/mr/flutter/plugin/filepicker/FileUtils.kt +++ b/android/src/main/kotlin/com/mr/flutter/plugin/filepicker/FileUtils.kt @@ -506,6 +506,26 @@ object FileUtils { } } + /** + * Opens a file stream from a URI, caches the file locally, and retrieves its metadata. + * + * This function takes a content URI, copies the file to the application's cache directory + * to ensure persistent access, and then builds a [FileInfo] object containing details + * about the file such as its path, name, URI, and size. + * + * If the `withData` flag is true, the file's content will also be read into a byte array + * and included in the returned [FileInfo] object. This is memory-intensive and should be + * used cautiously with large files. + * + * The cached file is placed in a structured directory within the app's cache to avoid + * name collisions. + * + * @param context The application context, used for accessing the content resolver and cache directory. + * @param uri The URI of the file to be opened. + * @param withData A boolean flag indicating whether to load the file's raw byte data into memory. + * @return A [FileInfo] object containing the file's details, or `null` if the file + * could not be accessed or cached. + */ @JvmStatic fun openFileStream(context: Context, uri: Uri, withData: Boolean): FileInfo? { var fileInputStream: InputStream? = null @@ -515,39 +535,38 @@ object FileUtils { val path = context.cacheDir.absolutePath + "/file_picker/" + System.currentTimeMillis() + "/" + (fileName ?: "unamed") - val file = File(path) + if (withData) { - if (!file.exists()) { - try { - file.parentFile?.mkdirs() + if (!file.exists()) { + try { + file.parentFile?.mkdirs() - fileInputStream = context.contentResolver.openInputStream(uri) - fileOutputStream = FileOutputStream(file) + fileInputStream = context.contentResolver.openInputStream(uri) + fileOutputStream = FileOutputStream(file) - val out = BufferedOutputStream(fileOutputStream) - val buffer = ByteArray(8192) - var len: Int + val out = BufferedOutputStream(fileOutputStream) + val buffer = ByteArray(8192) + var len: Int - while ((fileInputStream!!.read(buffer).also { len = it }) >= 0) { - out.write(buffer, 0, len) - } - out.flush() - } catch (e: Exception) { - Log.e(TAG, "Failed to retrieve and cache file: " + e.message, e) - return null - } finally { - try { - fileOutputStream?.fd?.sync() - fileOutputStream?.close() - fileInputStream?.close() - } catch (ex: IOException) { - Log.e(TAG, "Failed to close file streams: " + ex.message, ex) + while ((fileInputStream!!.read(buffer).also { len = it }) >= 0) { + out.write(buffer, 0, len) + } + out.flush() + } catch (e: Exception) { + Log.e(TAG, "Failed to retrieve and cache file: " + e.message, e) + return null + } finally { + try { + fileOutputStream?.fd?.sync() + fileOutputStream?.close() + fileInputStream?.close() + } catch (ex: IOException) { + Log.e(TAG, "Failed to close file streams: " + ex.message, ex) + } } } - } - if (withData) { loadData(file, fileInfo) } From ef5445b2b1833a89a6aca13143a0298fde84da84 Mon Sep 17 00:00:00 2001 From: Daniel Jimenez <122808735+daniJimen@users.noreply.github.com> Date: Fri, 19 Dec 2025 09:43:57 +0100 Subject: [PATCH 2/2] Refactor: Format code in `FileUtils.kt` --- .../kotlin/com/mr/flutter/plugin/filepicker/FileUtils.kt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/android/src/main/kotlin/com/mr/flutter/plugin/filepicker/FileUtils.kt b/android/src/main/kotlin/com/mr/flutter/plugin/filepicker/FileUtils.kt index 4e852c7a..1212ef33 100644 --- a/android/src/main/kotlin/com/mr/flutter/plugin/filepicker/FileUtils.kt +++ b/android/src/main/kotlin/com/mr/flutter/plugin/filepicker/FileUtils.kt @@ -40,6 +40,7 @@ import java.util.Locale object FileUtils { private const val TAG = "FilePickerUtils" + // On Android, the CSV mime type from getMimeTypeFromExtension() returns // "text/comma-separated-values" which is non-standard and doesn't filter // CSV files in Google Drive. @@ -355,7 +356,7 @@ object FileUtils { } mimes.add(mime) - if(allowedExtensions[i] == CSV_EXTENSION) { + if (allowedExtensions[i] == CSV_EXTENSION) { // Add the standard CSV mime type. mimes.add(CSV_MIME_TYPE) } @@ -457,7 +458,11 @@ object FileUtils { val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date()) val imageFileName = "IMAGE_" + timeStamp + "_" val storageDir = context.cacheDir - return File.createTempFile(imageFileName, "." + getCompressFormatBasedFileExtension(compressFormat), storageDir) + return File.createTempFile( + imageFileName, + "." + getCompressFormatBasedFileExtension(compressFormat), + storageDir + ) } /**