Мне здесь не нужно ничего сумасшедшего. Большая часть приложения проста: это приложение базы данных для отслеживания коллекции в автономном режиме. Для меня это просто приложение, возможно, для других оно будет бесплатным, если они захотят, но оно не появится в магазине или что-то в этом роде. Мой собственный телефон — Pixel 5 под управлением Android 11, но мой основной тестовый телефон — Pixel 4a5G под управлением Android 14.
Однако я хотел добавить импорт CSV, поскольку у меня уже есть сбор в другом приложении, которое мне не нравится, работает не так, как я хочу, и единственными вариантами экспорта являются CSV, и я не хочу делать это вручную при 135 записях, и если другие, которые собирают то же самое, захотят приложение тоже, я уверен, что они тоже оценят эту возможность.
Итак, поскольку CSV вряд ли является стандартным, а схемы могут быть разными, я решил использовать простой мастер. Цель состоит в том, чтобы, когда выбран импорт CSV, он получает заголовок и первую запись и отображает результаты. Оттуда вы можете выбрать, является ли потенциальный заголовок на самом деле заголовком (который затем установит параметр для пропуска первой строки во время импорта) или записью (включая первую строку). Это также позволяет увидеть, что CSV читается правильно, и выбрать, какие столбцы в CSV сопоставляются с какими значениями базы данных Room (которая представляет собой одну простую таблицу из 7 или 8 значений, из которых требуются только два). Я решил использовать зависимость от Apache Commons CSV. У меня есть настройка вспомогательного класса CSV и начальный экран импорта...
Но сейчас я даже не могу зайти так далеко. Этот импорт файлов — единственное, что мне нужно сделать с файлами, возможно, экспорт и/или резервное копирование (в файлах CSV или Db, но это в будущем). Я даже не знаю, работает ли мой код синтаксического анализа, потому что я даже не могу выбрать файл, чтобы посмотреть, произойдет ли с ним что-нибудь или произойдет сбой.
Я решил пока пропустить вопрос о разрешение (потому что все, что я пробовал, приводило к сбоям) и просто предоставляю его вручную, но когда я могу заставить его работать (на моем Android 11 Pixel 5), я могу только предоставлять разрешения для мультимедиа, а не для файлов. И во всех случаях сборщик файлов работает, запускается, файлы вижу, но выбрать не могу. Я даже проверил, что мой CSV-файл имеет правильный тип MIME и все остальное и заканчивается на .csv.
Текущий целевой SDK — 34, а минимальный — 26. Он написан на Compose, потому что , ну, это те лабораторные работы, которые я взял, и примеры, к которым у меня был доступ, чтобы учиться на них. Но современная установка... одно действие, контейнер приложения, экраны + модели просмотра, отдельный график навигации. Все зависимости — это последние версии и последняя версия Android Studio IDE.
TopAppBar имеет действия, в которых я разместил раскрывающееся меню, потому что будет только 3 варианта (импорт, экспорт и настройки). чтобы перейти на экран настроек, но, возможно, даже не это, потому что единственное предпочтение, которое я могу установить, - это принудительно установить светлый/темный цвет или использовать системные настройки). Именно здесь я хочу реализовать опцию импорта CSV.
Это меню видно только в TopAppBar на главном экране, не уверен, что это будет актуально.
На данный момент я прочитал, что вам либо не нужно явно предоставлять разрешение, либо вам нужно создать какой-то странный вызов активности и вызывать разрешения... и я не знаю. Очень противоречивые ответы, так что давайте просто перейдем к тому, где находится этот странный код, который дал мне ИИ (потому что все остальные образцы, которые я пробовал, тоже не работали)... и это настолько близко, насколько я подошел, что средство выбора файлов открывается, я могу просматривать внешнее хранилище, но не могу выбрать файл. Весь код, добавленный на данный момент, пытается заставить работать только одну вещь (не уверен, что то, что у меня есть в мастере импорта, работает, потому что я не могу пройти мимо выбора файла для его проверки):
Во-первых, я заявил в манифесте, что использует разрешения, хотя они устарели:
Код: Выделить всё
Код: Выделить всё
@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(...
Код: Выделить всё
DropdownMenuItem(
text = { Text(text = stringResource(R.string.import_csv)) },
onClick = {
launcher.launch(intent) // Launch SAF filepicker
expanded = false
},
Код: Выделить всё
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, val firstRecord: List) : CsvResult()
object Empty : CsvResult()
data class Error(val exception: Exception) : CsvResult()
}
Код: Выделить всё
@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",
)
}
}
}
Код: Выделить всё
class CsvImportViewModel() : ViewModel() {
private val _csvImportState = mutableStateOf(CsvImportState())
val csvImportState: State = _csvImportState
fun onCsvLoaded(header: List, firstRecord: List) {
_csvImportState.value = CsvImportState(header, firstRecord)
}
}
data class CsvImportState(
val header: List = emptyList(),
val firstRecord: List = emptyList()
)
В настоящее время я могу выбирать файлы (по крайней мере, в запущенном эмуляторе) API 30 пикселей 5). Основываясь на дальнейших исследованиях, которые привели меня к этому вопросу/ответу StackOverflow и некоторым другим, я изменил тип на «text/*». Хотя было бы неплохо, если бы кто-нибудь мог это подтвердить наверняка, «text/csv» просто не работает.
Подробнее здесь: https://stackoverflow.com/questions/788 ... -csv-permi
Мобильная версия