Android Compose MVVM SDK30+文件选择器可见CSV却无法选中
解决Android SAF文件选择器无法选中CSV文件的问题
核心原因
- 部分CSV文件的MIME类型被系统识别为
text/plain而非text/csv,仅指定text/csv时无法匹配到目标文件 - API30+存储权限模型变更,
READ_EXTERNAL_STORAGE权限对SAF操作无效,额外申请该权限可能引发崩溃
解决方案步骤
1. 修改文件选择Intent配置
在启动文件选择器的代码中,通过多MIME类型+文件扩展名过滤覆盖系统识别差异,确保精准匹配CSV文件:
// 替换原有的Intent配置 val pickCsvIntent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply { type = "*/*" // 基础类型设为所有类型,配合后续过滤规则 addCategory(Intent.CATEGORY_OPENABLE) // 添加CSV相关MIME类型,兼容系统识别偏差 putExtra(Intent.EXTRA_MIME_TYPES, arrayOf("text/csv", "text/plain")) // 强制过滤.csv后缀文件,避免匹配无关文本文件 putExtra(Intent.EXTRA_FILE_EXTENSIONS, arrayOf("csv")) } // Compose中使用启动器触发选择 val csvPickerLauncher = rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> if (result.resultCode == Activity.RESULT_OK) { result.data?.data?.let { uri -> // 将选中文件的Uri传递给ViewModel处理导入 viewModel.handleCsvImport(uri) } } } // 在TopAppBar的点击事件中启动选择器 csvPickerLauncher.launch(pickCsvIntent)
2. 清理冗余权限声明
使用SAF的ACTION_OPEN_DOCUMENT无需申请READ_EXTERNAL_STORAGE权限(SAF依赖文档选择器的临时授权),可从Manifest中移除该权限,避免不必要的权限申请崩溃:
<!-- 移除或注释掉以下权限(仅使用SAF时无需保留) --> <!-- <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> -->
3. 兼容CSV解析逻辑
在CSVHelper中直接通过ContentResolver读取文件,忽略MIME类型差异,仅按CSV格式解析:
class CSVHelper { fun parseCsv(uri: Uri, context: Context): List<YourCollectionModel> { val csvItems = mutableListOf<YourCollectionModel>() context.contentResolver.openInputStream(uri)?.use { inputStream -> BufferedReader(InputStreamReader(inputStream)).useLines { lines -> lines.forEachIndexed { index, line -> if (index == 0) return@forEachIndexed // 跳过表头(按需调整) val columns = line.split(",") // 映射为你的数据模型 val item = YourCollectionModel( id = columns[0].toInt(), name = columns[1], // 其他字段映射... ) csvItems.add(item) } } } ?: throw IllegalArgumentException("无法读取目标CSV文件") return csvItems } }
4. ViewModel中处理导入逻辑
在CsvImportViewModel中调用CSVHelper解析文件,并更新本地数据库:
class CsvImportViewModel(private val repository: YourCollectionRepository) : ViewModel() { private val _importState = MutableStateFlow<ImportState>(ImportState.Idle) val importState: StateFlow<ImportState> = _importState fun handleCsvImport(uri: Uri, context: Context) { viewModelScope.launch(Dispatchers.IO) { try { val csvItems = CSVHelper().parseCsv(uri, context) repository.insertAll(csvItems) _importState.emit(ImportState.Success) } catch (e: Exception) { _importState.emit(ImportState.Error(e.message ?: "导入失败")) } } } sealed class ImportState { object Idle : ImportState() object Success : ImportState() data class Error(val message: String) : ImportState() } }
内容的提问来源于stack exchange,提问作者sardonicus87
相关产品推荐
相关产品推荐

