【Android】 DB上に保存しているレコードをCSV形式（カンマ区切り）で保存する。


## ご挨拶
かなり久々の投稿になります。少し雑談から。急ぎの方は横の目次からジャンプしてください。
2020年は今まで生きてきた中である意味衝撃的でエキセントリックな1年でした。
皆さんも環境の変化やコロナへの不安などで、心が落ち着かない毎日を過ごされたかと思います。

自分も働く環境など、いろんなものが今までからガラッと180度変わった1年でした。
まさか、BTSにハマるなんて思いもしなかったし。（この投稿を書きながら今も聞いています。）

そんな中でもこの投稿を（ひいてはQiitaを）見に来られた方は、何かしらのシステム（アプリ）を自分の手で作り出すことに夢中になっている方かとお見受けします。

自分が嫌になることが多いこの職業ですが、「自分はできる！」と信じて頑張って欲しいです。

自分が投稿しているものは深い技術のものではなく、素人程度の技術をシンプルに応用しているものが
多いです。
まだまだ勉強中の身ですが、アプリ開発をされている方の少しでもお役に立てれば幸いです。

## 今回の目的
DB上に保存しているレコードをCSV形式（カンマ区切り）で保存して、そのデータを取り込むまでの
一連の流れを実装したい。

色々方法を模索したが、SAF（StorageAccessFramework）を今回採用した。
というよりも、これからのバージョンはこの方法が主流になるらしい。（Android4.4以降）

→　[Open files using storage access framework -developer.android-](https://developer.android.com/guide/topics/providers/document-provider)

## 使用する環境
- AndroidStudio Ver 3.4.1
- Android 10.0 (Q) API 29

## Permisson（アクセス権限）
注意しないといけないのは、Android6.X(API 23)以降からPermission（アクセス権限許可）をユーザに通知しないといけないこと。
今回はストレージにアクセスするので、ストレージへのアクセス権限をユーザに許可してもらわないといけません。
ストレージにアクセス出来ないというのは致命的なので、この機能を実装する前にその部分を実装してください。
詳細は以下のサイトを参考にしてください。（時間が空けばこの部分も記事にします。）

[権限の概要-developer.android-](https://developer.android.com/guide/topics/permissions/overview?hl=ja)

## エクスポート編
では「エクスポート」から。
下記は別アプリケーションで保存先を指定するためのクラス。

```MainActivity.java

public void fileopen＿for_SAF() {
  try {
        //ファイル名を作成。
        StringBuilder fileName = new StringBuilder();
        fileName.append(/* 好きな名前 */);
        fileName.append("_");
        fileName.append(getToday()); //ここで作成日時を取得。
        fileName.append(".csv");

        //「Intent.ACTION_CREATE_DOCUMENT」は、ファイルを選択するためのIntent。
        Intent it = new Intent(Intent.ACTION_CREATE_DOCUMENT);

        //ここで取得するファイルの種類を制限する。（今回はALLタイプ）
        it.setType("*/*");

        //ファイル名をセットして、Intentを起動。
        it.putExtra(Intent.EXTRA_TITLE, fileName.toString());

        /* 「CREATE_DOCUMENT_REQUEST」は、Private intで予め定義しておく。 */
        startActivityForResult(it, CREATE_DOCUMENT_REQUEST);

      } catch (Exception e) {
          e.printStackTrace();
      }

```

作成日付取得クラス。（Calendarクラスをインポートする必要あり。）
作成結果は「**（好きな名前）__2021_01_05_00_15.csv**」みたいに作成されます。

```MainActivity.java

private String getToday() {
   try {
       StringBuilder today = new StringBuilder();
       today.setLength(0);

       //日付の設定
       dd = Calendar.getInstance();
       today.append(dd.get(Calendar.YEAR));
       today.append("_");
       today.append(dd.get(Calendar.MONTH) + 1);
       today.append("_");
       today.append(dd.get(Calendar.DAY_OF_MONTH));
       today.append("_");
       today.append(dd.get(Calendar.HOUR));
       today.append("_");
       today.append(dd.get(Calendar.MINUTE));

       return today.toString();
   } catch (Exception e) {
       e.printStackTrace();
   }
   return null;
}
```

インテントの結果は、「onActivityResult」に返ってくる。
他にインテントの結果が返ってくるようなものがあった場合のことを考えて、requestCodeは、エクスポート用に変数を分けておく。


```MainActivity.java

protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {

    /* 「CREATE_DOCUMENT_REQUEST」は、Privateで予め定義しておく。 */

    if (requestCode == CREATE_DOCUMENT_REQUEST) {
         //エクスポート
         if (resultCode == RESULT_OK) {
             Uri create_file = data.getData();  //取得した保存先のパスなど。

             //出力処理を実行。その際の引数に上記のUri変数をセットする。
             if (exportCsv_for_SAF(create_file)) {
                 //出力に成功した時の処理。
             } else {
                 //出力に失敗した時の処理。
             }
         } else if (resultCode == RESULT_FAILED) {
             //そもそもアクセスに失敗したなど、保存処理の前に失敗した時の処理。
         }
     }

     //リストの再読み込み
     this.LoadData();

     super.onActivityResult(requestCode, resultCode, data);
 }

```

実際に出力を行うクラス。
DB部分はご自分の環境に合わせて作成してください。

```MainActivity.java
public Boolean exportCsv_for_SAF(Uri openFile) {

    try {
        OutputStream os = getContentResolver().openOutputStream(openFile);
        OutputStreamWriter os_write = new OutputStreamWriter(os, Charset.forName("Shift_JIS")); //後々インポートする際に困るのでエンコードを指定しておく。
        PrintWriter pw = new PrintWriter(os_write);

        StringBuilder rec = new StringBuilder();　//Export_data用

        //Read Data

        /* ここにExportするデータを取得するためのDBインスタンスなどを記述する。*/

        StringBuilder sql = new StringBuilder();
        sql.setLength(0);
        sql.append(/* 取得用のSQL */)

        c = db.rawQuery(sql.toString(), null);
        while (c.moveToNext()) {
            rec.setLength(0);

            rec.append(/* 取得カラムデータをExport用Builderにセット。 */);
          　/* ↑をExportするカラム分呼び出す。*/

            pw.println(rec.toString()); //Export

        }

        //基本的に開けたら閉める。
        c.close();
        pw.close();
        os_write.close();
        os.close();

    } catch (SQLiteException e) {
        e.printStackTrace();
        return false;
    } catch (FileNotFoundException e) {
        e.printStackTrace();
        return false;
    } catch (IOException e) {
        e.printStackTrace();
        return false;
    } finally {
        if (db.isOpen()) {
            db.endTransaction();
        }
    }
    return true;
}

```

とりあえずここまで。


## インポート編

- 制作中


## 終わりに
ここまでお疲れ様でした。というより疲れた。
データのインポート/エクスポートをするという目的なだけで色々調べる事になったので、勉強になりました。
一番シンプルに実装するとこの記事のようにできますので、ご自分で工夫してぜひお試しください。
他にも「アプリケーションが独自のストレージを提供すること」などができるみたいなので、時間がある時に試してみます。

P.S.
インポート編は時間がある時に追記します。

## 参考サイト

- [第 3 回・Storage Access Framework を使用してみよう！](https://gaprot.jp/2014/01/31/storage-access-framework/)