KotlinでSDカードにアクセス

#はじめに
KotlinからSDカードにアクセスするときの処理を出来るだけ最新のAndroidでも動作するようにしてみました。

#準備
build.gradle(app)にDocumentFileが使えるように1行追加します。

```build.gradle
dependencies {
    // 省略
    implementation "androidx.documentfile:documentfile:1.0.1"
}
```

#パーミッションは使わない
パーミッションを使って権限を取得する方法は新しいバージョンだと使えません。
SDカードに書き込む方法を最新の方法で書くと

```sample.kt
        val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
        startActivityForResult(activity,intent,permissioRequestCode,null)
```

たった2行です。
これでユーザーにSDカードも含めて書き込む場所を問い合わせることが出来ます。

ユーザーが決定すると**onActivityResult**イベントが発生します。
イベント内の data: Intent?は使うので大事に取っておきます。

```sample.kt
        uri = data?.getData()
        context.getContentResolver().takePersistableUriPermission(
            uri!!,
            Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
        )
        picDir = DocumentFile.fromTreeUri(context,uri!!)
```

ユーザーが指定した場所をuriに、そしてそれを元にフォルダを取得します。
ここではPCのクセが抜けずにそのままの用語を使っていますがいわゆるファイル名の代わりにURI、フォルダに関してはAndroid独特の表現があります。

#ファイル名を取得
作る側が指定するファイル名をFileNameで指定して

```
   FileUri = picDir?.createFile("text/plain", FileName)
```

のようにするといよいよファイル名を示すものが得られます。
ですがこのファイル名、毎回変わります。命令を和訳すると「新規ファイル名を取得」ですからそれもそうですね。2回目はファイル名の後ろに(2)とかつきます。

なのでファイルが存在する場合はそれを使って、無い場合は作るようにします。

```sample.kt
    FileUri = picDir?.findFile(FileName)
    if (FileUri == null) {
        FileUri = picDir?.createFile("text/plain", FileName)
    }
```

#ファイルの新規書き込み
```sample.kt
    val Stream = context.getContentResolver().openOutputStream(FileUri!!.getUri())
    Stream?.write(DataStr.toByteArray())
```

#ファイルの追加書き込み
別の命令か書き込み属性の指定があるかと思ったらまさかの文字列形式で指定でした。

```sample.kt
    val Stream = context.getContentResolver().openOutputStream(FileUri!!.getUri(),"wa")
    Stream?.write(DataStr.toByteArray())
```

#ファイル削除
ファイル削除命令はこのようになりますが動きません。
これはSDカード内のファイルを消すにはこれもユーザーの許可が必要だからです。
問い合わせてまた**onActivityResult**を使って許可されたことを確認します。
面倒なのでサンプルは無しです。

```sample.kt
 context.getContentResolver().delete(FileUri!!.getUri(),null,null)
```

#注意点
ユーザーが許可をすれば問題ありませんが許可しない場合に何度も問い合わせを行う事は推奨されていません。
一度キャンセルされた後に各処理でさらに問い合わせが必要になった場合は処理が中断されることになるのでソースの書き方に工夫が必要です。
キャンセルされた場合はもうあきらめるというのでも良いかもしれません。

#ソース
```sd.kt
class SdcardControl(private val context: Context,private var activity: MainActivity) {

    var permissioRequestCode : Int = 1001

    private var picDir : DocumentFile? = null
    private var uri: Uri? = null
    private var mFileName : String = ""
    private var FileUri : DocumentFile? = null
    var FileName : String
    get() = mFileName
    set(value){
        mFileName = value
        if (picDir != null) {
            FileUri = picDir?.findFile(FileName)
            if (FileUri == null) {
                FileUri = picDir?.createFile("text/plain", FileName)
            }
        }
    }

    fun init(){
        val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
        startActivityForResult(activity,intent,permissioRequestCode,null)
    }

    fun resultUri(data : Intent?){
        uri = data?.getData()
        context.getContentResolver().takePersistableUriPermission(
            uri!!,
            Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
        )
        picDir = DocumentFile.fromTreeUri(context,uri!!)
    }

    fun saveFileCreate(DataStr: String) {
        try {
            if (FileUri == null) return
            val Stream = context.getContentResolver().openOutputStream(FileUri!!.getUri())
            Stream?.write(DataStr.toByteArray())
        } catch (e: IOException) {
            e.printStackTrace()
        }
    }

    fun saveFileAdd(DataStr: String) {
        try {
            if (FileUri == null) return
            val Stream = context.getContentResolver().openOutputStream(FileUri!!.getUri(),"wa")
            Stream?.write(DataStr.toByteArray())
        } catch (e: IOException) {
            e.printStackTrace()
        }
    }

    fun fileExist(FileName : String) : Boolean{
        return (FileUri != null)
    }

    fun saveFileDelete(FileName : String) : Boolean{
        try {
            context.getContentResolver().delete(FileUri!!.getUri(),null,null)
        } catch (e: IOException) {
            e.printStackTrace()
        }
        return true
    }

    fun fileLength() : Long{
        if (uri == null) return -1
        val projection =
            arrayOf(MediaStore.MediaColumns.SIZE)
        val cursor: Cursor? =  context.getContentResolver().query(FileUri!!.uri, projection, null, null, null)
        var size: Long = 0
        if (cursor != null) {
            if (cursor.moveToFirst()) {
                size = cursor.getLong(0)
            }
            val close: Any = cursor.close()
        }
        return size
    }
}
```

実装方法

```MainActivity.kt
class MainActivity : AppCompatActivity() {

    private val sdcard = SdcardControl(this,this)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        sdcard.init()
    }

    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        if (resultCode == RESULT_CANCELED) return
        if (requestCode != sdcard.permissioRequestCode) return
        sdcard.resultUri(data)
    }
```