JetpackCompose から 共有ストレージのファイルにアクセスする
概要
Jetpack Compose から 共有ストレージのファイルにアクセスする方法を説明します。 具体的には以下を実装します。
- ユーザにフォルダーを選択させ、そこにファイルを作成する。
- ユーザにファイルを選択させ、その内容を読み取る。
なお、アプリがアクセスできるファイルやフォルダーの選択をユーザーが行うため、このメカニズムではシステム権限は必要ありません。
実装
UserDictionaryScreen.kt
package com.kyamada.listentonovels.androidApp.ui.compose.screens.dictionary
import android.net.Uri
import android.util.Log
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
import cafe.adriel.voyager.core.screen.Screen
import com.kyamada.listentonovels.androidApp.extension.getText
import org.koin.core.component.KoinComponent
import java.io.FileNotFoundException
import java.io.FileOutputStream
import java.io.IOException
const val TAG = "UserDictionaryScreen"
class UserDictionaryScreen : Screen, KoinComponent {
@Composable
override fun Content() {
val context = LocalContext.current
val createDocumentLauncher =
rememberLauncherForActivityResult(ActivityResultContracts.CreateDocument("text/tab-separated-values")) { uri: Uri? ->
val fileUri = uri ?: return@rememberLauncherForActivityResult
try {
context.contentResolver.openFileDescriptor(fileUri, "w")?.use { descriptor ->
FileOutputStream(descriptor.fileDescriptor).use { os ->
os.write("a,b,c".toByteArray())
}
}
} catch (e: FileNotFoundException) {
e.printStackTrace()
} catch (e: IOException) {
e.printStackTrace()
}
}
val openDocumentLauncher =
rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) { uri: Uri? ->
if (uri == null) return@rememberLauncherForActivityResult
val tsv = context.contentResolver.getText(uri, Charsets.UTF_8)
Log.d(TAG, tsv)
}
LazyColumn {
item {
TextButton(
onClick = {
// 1. ユーザにフォルダーを選択させ、そこにファイルを作成する。
// 引数には作成するファイル名を指定します。
createDocumentLauncher.launch("fileName.tsv")
}
) {
Text("exportToFile")
}
}
item {
TextButton(
onClick = {
// 2. ユーザにファイルを選択させ、その内容を読み取る。
// 引数にはファイルのMIMEタイプを指定します。
openDocumentLauncher.launch(arrayOf("text/tab-separated-values"))
}
) {
Text("importFromFile")
}
}
}
}
}
ContentResolver+.kt
import android.content.ContentResolver
import android.net.Uri
import io.ktor.utils.io.charsets.Charset
import java.io.BufferedReader
import java.io.InputStreamReader
// テキストファイルの内容を取得する拡張メソッド
fun ContentResolver.getText(uri: Uri, charset: Charset): String {
val stringBuilder = StringBuilder()
this.openInputStream(uri)?.use { inputStream ->
BufferedReader(InputStreamReader(inputStream, charset)).use { reader ->
var line: String? = reader.readLine()
while (line != null) {
stringBuilder.appendLine(line)
line = reader.readLine()
}
}
}
return stringBuilder.toString()
}