Jetpack Compose中选取文件的实际路径获取及Retrofit上传报错解决
Hey there! Let's break down your Jetpack Compose file handling questions and fix that frustrating Cursor error step by step.
First off, it's important to note that on Android 10+ (API 29+), direct access to real file paths is restricted by scoped storage—so relying on file paths isn't always the most reliable approach. That said, if you absolutely need a path, here's how to safely retrieve it:
Step 1: Pick the file using Activity Result Contracts
In Compose, we use rememberLauncherForActivityResult to trigger file selection:
val context = LocalContext.current val pickFileLauncher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri -> uri?.let { val realPath = getRealPathFromUri(context, it) // Use the path as needed } } // Trigger file picker Button(onClick = { pickFileLauncher.launch("*/*") }) { Text("Select File") }
Step 2: Implement the path retrieval function
This function handles both file:// and content:// URIs, and avoids Cursor errors by validating column existence:
fun getRealPathFromUri(context: Context, uri: Uri): String? { // Handle direct file URIs if (uri.scheme == "file") { return uri.path } // Handle content URIs (most common case) var cursor: Cursor? = null try { // Only query the DATA column for media files (images/videos/audio) val projection = arrayOf(MediaStore.MediaColumns.DATA) cursor = context.contentResolver.query(uri, projection, null, null, null) if (cursor != null && cursor.moveToFirst()) { // Check if the column exists before accessing it (fixes your error!) val columnIndex = cursor.getColumnIndex(MediaStore.MediaColumns.DATA) if (columnIndex != -1) { return cursor.getString(columnIndex) } else { // For non-media files (like docs), copy to cache to get a usable path return copyUriToCache(context, uri) } } } catch (e: Exception) { e.printStackTrace() // Fallback to cache copy for any unexpected errors return copyUriToCache(context, uri) } finally { cursor?.close() } return null } // Helper to copy URI content to a cache file (works for all file types) fun copyUriToCache(context: Context, uri: Uri): String? { return try { val inputStream = context.contentResolver.openInputStream(uri) ?: return null val tempFile = File(context.cacheDir, "temp_${System.currentTimeMillis()}") inputStream.copyTo(FileOutputStream(tempFile)) inputStream.close() tempFile.absolutePath } catch (e: IOException) { e.printStackTrace() null } }
Why you're seeing that Cursor error
The error Couldn't read row 0, col -1 from CursorWindow happens because you're trying to access a column that doesn't exist in the Cursor. For non-media files (like PDFs, docs), the DATA column isn't available in the MediaStore query—so we need to either validate column existence or avoid relying on file paths entirely.
Better approach: Upload directly using the URI (no path needed!)
Scoped storage encourages using URIs and ContentResolver instead of file paths. Here's how to upload with Retrofit without needing a real path:
Step 1: Define your Retrofit service
interface FileUploadApi { @Multipart @POST("your-upload-endpoint") suspend fun uploadFile( @Part file: MultipartBody.Part ): Response<UploadResponse> // Replace with your response model }
Step 2: Implement the upload function
suspend fun uploadFile(context: Context, uri: Uri) { val retrofit = Retrofit.Builder() .baseUrl("https://your-api-base-url.com/") .addConverterFactory(GsonConverterFactory.create()) .build() .create(FileUploadApi::class.java) // Get the MIME type of the file val mimeType = context.contentResolver.getType(uri) ?: "application/octet-stream" // Create RequestBody directly from the URI (no file path required!) val requestBody = object : RequestBody() { override fun contentType(): MediaType? = MediaType.parse(mimeType) override fun writeTo(sink: BufferedSink) { context.contentResolver.openInputStream(uri)?.use { inputStream -> sink.writeAll(inputStream.source()) } } } // Create the multipart part val filePart = MultipartBody.Part.createFormData( name = "file", // Match your API's expected field name filename = "uploaded_file", // You can extract the real name from the URI if needed body = requestBody ) try { val response = retrofit.uploadFile(filePart) if (response.isSuccessful) { // Handle successful upload (e.g., show toast) } else { // Handle error (e.g., log response code) } } catch (e: Exception) { e.printStackTrace() // Handle network errors } }
Step 3: Update your file picker to trigger upload
Modify the launcher to call the upload function directly:
val pickFileLauncher = rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri -> uri?.let { lifecycleScope.launch { uploadFile(context, it) } } }
Key takeaways
- Avoid relying on real file paths for Android 10+—use URIs and ContentResolver instead to comply with scoped storage rules.
- The Cursor error was caused by accessing a non-existent column; always validate column indices before using them.
- Uploading directly from the URI is more reliable and avoids path-related issues entirely.
内容的提问来源于stack exchange,提问作者Akshay Kc

