В этой статье мы поговорим как в Kotlin работать с External Storage файлами. Рассмотрим следующие вопросы: как записать файл в Kotlin External Storage, как прочитать файл в Kotlin External Storage, как фильтровать и найти файл в Kotlin External Storage директории, в том числе и рекурсивный поиск файлов.
Разрешение на запись в External Storage
Для того, чтобы работать с внешней памятью на Android устройствах нужно получить разрешение на запись, для этого необходимо сделать несколько шагов.
- Добавим разрешение в AndroidManifest.xml
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
И для Android устройств ниже версии 10, в AndroidManifest.xml пропишем авто получение разрешения.
<application android:requestLegacyExternalStorage="true" ...
2. Проверим наличие разрешения на запись следующей функцией:
// Получить разрешение на ЗАПИСЬ fun checkWritePermissions(){ when{ ContextCompat.checkSelfPermission(APP, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED -> { //РАЗРЕШЕНИЕ УЖЕ ПОЛУЧЕНО } else -> { //РАЗРЕШЕНИЕ НЕ ПОЛУЧЕНО // Запрашиваем разрешение у пользователя permisLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE) } } }
3. Установим слушатель разрешения на запись следующей функцией:
// Слушатель разрешения fun registerPermissionListener(){ permisLauncher = APP.registerForActivityResult(ActivityResultContracts.RequestPermission()){ if(it) { //РАЗРЕШЕНИЕ ПОЛУЧЕНО }else{ //ПОЛЬЗОВАТЕЛЬ ОТКАЗАЛ В РАЗРЕШЕНИИ // Запустим диалог поясняющий пользователю, зачем нужно это разрешение showDialog() } } }
4. Запустим диалог поясняющий пользователю, зачем нужно это разрешение
// Диалог поясняющий необходимость разрешения fun showDialog() { AlertDialog.Builder(APP) .setTitle("Разрешение на запись") .setMessage("Дайте разрешение на запись. В противном случае приложение работать не будет!") .setPositiveButton("Разрешить") { dialog, which -> // Снова запрашиваем разрешение checkWritePermissions() } .setNegativeButton("Отклонить") { dialog, which -> // Закрываем приложение APP.finish() } .show() }
5. Теперь в MainActivity в методе onCreate вызовем методы созданные ранее:
... override fun onCreate(savedInstanceState: Bundle?) { ... registerPermissionListener() checkWritePermissions() ...
Теперь при старте, приложение будет проверять и при необходимости запрашивать разрешение на запись в External Storage. Можно приступать к работе с файлами.
Запись файла
Задача
Создадим метод, который создает файл “hello.txt” и записывает в него текст “Запись 1“.
Решение
Входными параметрами функции будут: путь (filePath), имя файла (fileName), текст (text). Все действия обернём в try catch для исключения ошибок.
// Записать файл fun writeFile(filePath: String, fileName: String, text: String) { try { //Создается объект файла, при этом путь к файлу находиться методом Environment val myFile = File(Environment.getExternalStorageDirectory().toString() + "/" + filePath + fileName) // Создается файл, если он не был создан myFile.createNewFile() // После чего создаем поток для записи val outputStream = FileOutputStream(myFile) // Производим непосредственно запись outputStream.write(text.toByteArray()) // Закрываем поток outputStream.close() // Просто для удобства визуального контроля исполнения метода в приложении Toast.makeText(this, "File is write", Toast.LENGTH_SHORT).show() } catch (e: Exception) { e.printStackTrace() Toast.makeText(this, "File is not write", Toast.LENGTH_SHORT).show() } }
Вызвать функцию можно следующим образом:
writeFile("download/", "hello.txt", "Запись 1")
Эта функция создаст файл “hello.txt” в папке Download и запишет в него следующий текст “Запись 1“.
Чтение файла
Теперь получим текст из ранее созданного файла.
Входными параметрами функции будут: путь (filePath), имя файла (fileName). Выходной параметр типа String. То есть на выходи функции получим строку с текстом из файла.
// Чтение файла fun readFile(filePath: String, fileName: String): String { // Аналогично создается объект файла val myFile = File(Environment.getExternalStorageDirectory().toString() + "/" + filePath + fileName) try { val inputStream = FileInputStream(myFile) // Буферизируем данные из выходного потока файла val bufferedReader = BufferedReader(InputStreamReader(inputStream)) // Класс для создания строк из последовательностей символов val stringBuilder = StringBuilder() var line: String? try { // Производим построчное считывание данных из файла в конструктор строки, while (bufferedReader.readLine().also { line = it } != null) { stringBuilder.append(line) } return stringBuilder.toString() } catch (e: IOException) { e.printStackTrace() return "" } } catch (e: FileNotFoundException) { e.printStackTrace() return "" } }
Вызвать функцию можно следующим образом:
val text = readFile("download/", "hello.txt")
Находим файлы в директории с нужным расширением
Создадим функцию для поиска фалов в главной директории с расширением “.txt” рекурсивно (то есть включая все подкаталоги)
Входными параметрами функции будут: путь (filePath), фильтр (filter). Выходной параметр типа ArrayList<File>. То есть на выходи функции получим список файлов.
fun allFilesInDir(filePath: String, filter: String): ArrayList<File>{ var filesFromDir = ArrayList<File>() val path = Environment.getExternalStorageDirectory().toString() + "/" + filePath val directory = File(path) val files = directory.walkTopDown() // Перебираем все файлы for (file in files) { // Отсеиваем файлы подходящие под фильтр if(file.name.endsWith(filter)) { filesFromDir.add(file) } } return filesFromDir }
Вызовем эту функцию и получим результат её работы:
val text = allFilesInDir("", ".txt").map { it.name }.toString()
Заключение
Работа с External Storage файлами в Kotlin очень лаконичная и удобная. С типом данных File вы можете делать много операций, таких как: удалить, переименовать, получить имя, расширение, размер файла и многое другое.