You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Jetpack Compose中选取文件的实际路径获取及Retrofit上传报错解决

Hey there! Let's break down your Jetpack Compose file handling questions and fix that frustrating Cursor error step by step.

1. 获取Jetpack Compose中选取文件的实际路径

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
    }
}
2. 解决Cursor报错并结合Retrofit完成文件上传

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.27 21:57:50