Android Compose MVVM File Picker Can See File, Cannot Select (Import CSV), permissions, other issues SKD 30+

I’ve searched everywhere and I can’t find any answers that make any sense, and every answer I tried I could find, causes the app to crash instantly. I even got desperate and asked Gemini for help, which… was just only giving me stuff I already know won’t work or already tried and stuff that’s way deprecated, and I’m absolutely lost. But, I’m also not a developer, I only know the very basics of coding. I tried looking up tutorials on, relevant to this issue but there’s none. It’s like all the tutorials go from Kindergarten/Elementary School to Doctorate level post-grad with nothing in between.

I don’t need anything crazy here. Most of the app is simple, it’s a database app for tracking a collection, all offline. This is just an app for me, possibly others for free if they want it, but it’s not going on store or anything. My own phone is a Pixel 5 running Android 11 (and I’m never upgrading from 11, I do not care, I hate 12+), but my main test phone is a Pixel 4a5G running Android 14.

However, I wanted to add a CSV import, because I already have a collection on a different app that I don’t like that doesn’t work how I want, and it’s only options for export are CSV, and I don’t want to manually at 135 entries, and if others that collect this same thing want the app too, I’m sure they also would appreciate the option.

So, because CSV is hardly standard, and schemas can be different, I decided to do a simple wizard. The goal is when import CSV is selected, it gets the header and first record and displays the results. From there, you’d choose whether or not the potential header is actually a header (which would then set an option to skip the first row during import) or is a record (include the first row). This also allows seeing that the CSV is being read correctly, and choosing which columns in the CSV map to which values of the Room db (which is one simple table of 7 or 8 values, only two of which are required). I decided to go with the dependency for Apache Commons CSV. I got a CSV helper class setup and the initial import screen…

But right now, I can’t even get that far. This file import is the only thing I need to do with files, maybe an export and/or backup (in CSV or the Db files, but that’s a ways down the road). I don’t even know if my parsing code works, because I can’t even select the file to see if it does anything or crashes.

I decided to skip for now the asking of permission (because everything I tried caused crashes) and just grant it manually, but when I can get it to work (on my Android 11 Pixel 5), I can only grand media permissions, not files. And in every case, the file picker works, launches, I can see files, but can’t select them. I even checked that my CSV file does have the correct MIME type and everything.

The current target SDK is 34 and the minimum is 26. It’s written in Compose because, well that’s the codelabs I took and the examples I had access to in order to learn from. But a modern setup… one activity, app container, screens + view models, separate nav graph. All depencenies are latest versions and the latest version of Android Studio IDE.

TopAppBar has actions, where I put a dropdown menu because there’s only going to be 3 options (import, export, and preferences to go to a preferences screen, but maybe not even that because the only preference I can thing to want to be set is force light/dark or use system settings). It is here that I want to implement the import CSV option.

This menu is only visible in the TopAppBar from the HomeScreen, not sure if that will be relevant.

So far, what I’ve read, you either don’t need to explicity grant permission or you need to create some weird get activity call and have to call permissions… and I don’t know. Very conflicting answers so, let’s just get to where this weird code is that the AI gave me (because all the other samples I tried didn’t work either)… and this is as close as I have come, that the file picker opens, I can brows external storage, but cannot select a file. All the code added so far trying to get just this one thing to work (not sure if what I have of the import wizard works yet, because I can’t get past selecting a file to test it):

First, I declared in the manifest uses permissions even though it’s deprecated:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_MEDIA_*" />
<uses-permission android:name="android.permission.WRITE_MEDIA_*" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />

Then… in the App file where the TopAppBar is composed:

@Composable
MyAppBar(...) {
val context = LocalContext.current
    val intent = Intent(Intent.ACTION_OPEN_DOCUMENT).apply {
        addCategory(Intent.CATEGORY_OPENABLE)
        type = "text/csv"
    }
    val csvHelper = CsvHelper()
    val viewModel: CsvImportViewModel = viewModel()
    val navigateToCsvImportScreen: () -> Unit = {}
    val launcher = rememberLauncherForActivityResult(
        contract = ActivityResultContracts.StartActivityForResult(),
    ) { result ->
            if (result.resultCode == Activity.RESULT_OK) {
                val uri = result.data?.data
                uri?.let {
                    val contentResolver = context.contentResolver
                    contentResolver.openInputStream(uri)?.use { inputStream ->
                        when (val result = csvHelper.csvFileReader(inputStream)) {
                            is CsvResult.Success -> {
                                viewModel.onCsvLoaded(result.header, result.firstRecord)
                                navigateToCsvImportScreen()
                            }
                            is CsvResult.Error -> {
                                /*TODO show error dialog*/
                            }
                            is CsvResult.Empty -> {
                                /*TODO show error dialog*/
                            }
                        }
                    }
                }
            }
        }
    TopAppBar(
        colors = TopAppBarDefaults.topAppBarColors(...

And the TopAppBar Dropdownmenuitem (which, I think navigation call should be here probably and not in the val declared at the top, that’s what makes sense to me, but I’m not a developer and that wasn’t working either how it intuitively made sense to me):

DropdownMenuItem(
    text = { Text(text = stringResource(R.string.import_csv)) },
    onClick = {
        launcher.launch(intent) // Launch SAF filepicker
        expanded = false
    },

And if it’s relevant, CSV helper so far:

class CsvHelper {

    fun csvFileReader(inputStream: InputStream): CsvResult {
        return try {
            val parser = CSVParser.parse(inputStream, Charset.defaultCharset(),
                CSVFormat.DEFAULT)
            val records = parser.records
            if (records.isNotEmpty()) {
                val header = records.first().toList().map { it.toString() }
                val firstRecord = if (records.size > 1) records[1].toList().map { it.toString() }
                else emptyList()
                CsvResult.Success(header, firstRecord)
            } else {
                CsvResult.Empty
            }
        } catch (e: Exception) {
            CsvResult.Error(e)
        }
    }
}

sealed class CsvResult {
    data class Success(val header: List<String>, val firstRecord: List<String>) : CsvResult()
    object Empty : CsvResult()
    data class Error(val exception: Exception) : CsvResult()
}

CsvImportScreen:

@Composable
fun CsvImportBody(
    viewModel: CsvImportViewModel,
    modifier: Modifier = Modifier,
) {
    val csvImportState by viewModel.csvImportState
    val header = csvImportState.header
    val firstRecord = csvImportState.firstRecord

    Column (
        modifier = modifier
            .padding(16.dp)
            .fillMaxWidth()
    ) {
        Text(
            text = stringResource(R.string.csv_import_instructions),
            modifier = modifier
                .padding(bottom = 16.dp),
            softWrap = true
        )
        Column (
            modifier = modifier
                .fillMaxWidth()
        ) {
            Text(
                text = stringResource(R.string.possible_header),
            )
            Text(
                text = "    $header",
            )
        }
        Column (
            modifier = modifier
                .fillMaxWidth()
        ) {
            Text(
                text = stringResource(R.string.possible_record),
            )
            Text(
                text = "    $firstRecord",
            )
        }
    }
}

CsvImportViewModel:

class CsvImportViewModel() : ViewModel() {

    private val _csvImportState = mutableStateOf(CsvImportState())
    val csvImportState: State<CsvImportState> = _csvImportState

    fun onCsvLoaded(header: List<String>, firstRecord: List<String>) {
        _csvImportState.value = CsvImportState(header, firstRecord)
    }
}

data class CsvImportState(
    val header: List<String> = emptyList(),
    val firstRecord: List<String> = emptyList()
)

Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa Dịch vụ tổ chức sự kiện 5 sao Thông tin về chúng tôi Dịch vụ sinh nhật bé trai Dịch vụ sinh nhật bé gái Sự kiện trọn gói Các tiết mục giải trí Dịch vụ bổ trợ Tiệc cưới sang trọng Dịch vụ khai trương Tư vấn tổ chức sự kiện Hình ảnh sự kiện Cập nhật tin tức Liên hệ ngay Thuê chú hề chuyên nghiệp Tiệc tất niên cho công ty Trang trí tiệc cuối năm Tiệc tất niên độc đáo Sinh nhật bé Hải Đăng Sinh nhật đáng yêu bé Khánh Vân Sinh nhật sang trọng Bích Ngân Tiệc sinh nhật bé Thanh Trang Dịch vụ ông già Noel Xiếc thú vui nhộn Biểu diễn xiếc quay đĩa Dịch vụ tổ chức tiệc uy tín Khám phá dịch vụ của chúng tôi Tiệc sinh nhật cho bé trai Trang trí tiệc cho bé gái Gói sự kiện chuyên nghiệp Chương trình giải trí hấp dẫn Dịch vụ hỗ trợ sự kiện Trang trí tiệc cưới đẹp Khởi đầu thành công với khai trương Chuyên gia tư vấn sự kiện Xem ảnh các sự kiện đẹp Tin mới về sự kiện Kết nối với đội ngũ chuyên gia Chú hề vui nhộn cho tiệc sinh nhật Ý tưởng tiệc cuối năm Tất niên độc đáo Trang trí tiệc hiện đại Tổ chức sinh nhật cho Hải Đăng Sinh nhật độc quyền Khánh Vân Phong cách tiệc Bích Ngân Trang trí tiệc bé Thanh Trang Thuê dịch vụ ông già Noel chuyên nghiệp Xem xiếc khỉ đặc sắc Xiếc quay đĩa thú vị
Trang chủ Giới thiệu Sinh nhật bé trai Sinh nhật bé gái Tổ chức sự kiện Biểu diễn giải trí Dịch vụ khác Trang trí tiệc cưới Tổ chức khai trương Tư vấn dịch vụ Thư viện ảnh Tin tức - sự kiện Liên hệ Chú hề sinh nhật Trang trí YEAR END PARTY công ty Trang trí tất niên cuối năm Trang trí tất niên xu hướng mới nhất Trang trí sinh nhật bé trai Hải Đăng Trang trí sinh nhật bé Khánh Vân Trang trí sinh nhật Bích Ngân Trang trí sinh nhật bé Thanh Trang Thuê ông già Noel phát quà Biểu diễn xiếc khỉ Xiếc quay đĩa
Thiết kế website Thiết kế website Thiết kế website Cách kháng tài khoản quảng cáo Mua bán Fanpage Facebook Dịch vụ SEO Tổ chức sinh nhật