はじめに
データ入力のアプリを作成する機会があり、入力したデータをCSVにエクスポートする機能の実装に手間取ったため、ログとして残します。
ExpoはAPIが多く用意されており大変便利ですが、現在は推奨されていないAPIも多く残されており、どれを使って実装すればいいのか初学者的には迷いどころです。
全体の流れ
似たような質問と回答がRedditにありました。
全体の流れはこちらの回答と大きく変わりません。
FileSystemAPIを用いてcsvファイルをcacheディレクトリに書き込んで、sharingAPIでシステムピッカ―を開き、ファイルとして保存します。
ただドキュメントに従って実装するだけでは味気ないので、個人的に当初目指していたことや試行錯誤についても載せておきます。
実装
使用言語はTypeScript
テストはAndroid端末にExpoGOを導入して行います。
かんたんであることは良いコトです。
フィールドに入力された「名前」と「年齢」の情報を取得し、CSVとして出力する部分を作成します。
データは先頭に項目名の入った多次元配列で渡されるものとします。
例)
[["Name","Age"],["Tanaka","20"],["Nakata","40"], ...]
多次元配列をcsv形式に変換
// 配列データをCSV形式に成形
const formatListToCsv = (data: Array<string[]>):string => {
const csvContent = data.map((row) => row.join(',')).join('\n');
// 文字化け対策でBOM追加
const bom = '\ufeff';
const fullContent = bom + csvContent;
return fullContent;
}
csvファイルを保存
ライブラリをプロジェクトにインストール。
ファイル書き込みのexpo-file-systemとファイル共有の為にexpo-sharing
> npx expo install expo-file-system
> npx expo install expo-sharing
import { File, Paths } from 'expo-file-system';
import * as Sharing from 'expo-sharing';
const saveFile = async(data: Array<string[]>) => {
try {
// ファイルに書き込む中身
const content = formatListToCsv(csvData);
// ファイル作成
const file = new File(Paths.cache, 'example.csv');
file.create({ intermediates: true, overwrite:true });
file.write(content);
// 動作確認用
console.log(file.textSync());
// ファイル共有システム
await Sharing.shareAsync(file.uri);
alert('Created File!');
} catch (error) {
console.error(error);
}
}
適当なデータを作成し、saveFile関数を保存ボタンに渡して動作を確認します。
export default function SaveCSV() {
const testData=[
["Name","Age"],
["Tanaka","20"],
["Nakata","40"]
];
~上記関数~
return (
<View style={styles.container}>
<Button title='Save' onPress={() => saveFile(testData)} />
</View>
);
}
CSVファイルが保存されていることを確認できればOKです。
手間取った点
当初考えていたもの
ユーザー視点からすれば、保存ボタンを押すたびに一々システムピッカーが開かれて「ファイルを保存」と選択する必要があるのは手間です。
保存ボタン一つでファイルまで作ってくれよと感じるのが人情ってもの。
そこでExpoのFileSystemのドキュメントを確認したところ、Pathsの項目にてdocumentなるものを発見。
document
タイプ: Directory
ドキュメント ディレクトリ (システムによって削除されない安全なファイルを保存する場所) を含むプロパティ。
まさに今求めているもの!早速PathsをPaths.cacheからPaths.documentに切り替えて実装してみたところ、無事にファイル保存完了の応答を確認。
うきうきでファイルシステムを開いて確認したところ...
「おや?example.csvファイルが見当たらない?」
ファイルが保存された場所は何処?
ファイル保存は出来ているのですが、ファイルシステムから確認できるところには保存されていない様子でした。
では何処に保存されているのか?
saveFile 関数内に console.log(file.uri); を追加してパスを確認した結果、
file:///data/user/0/host.exp.exponent/files/example.csvとのこと。
ファイルはExpoGOアプリの 内部ストレージ に保存されていました。
expo-file-systemは「アプリ固有のサンドボックス領域」への書き込みを行っており、外部ストレージへ書き出す機能やAPIを有していません。
ファイルシステムから参照できる場所ではなく、 アプリ自身からしか直接参照できない場所 に保存されていた、という訳です。
システムピッカーを介さないファイル保存にはreact-native-fsなどのライブラリを用いることで実装が可能となるようですが、これはExpo GOにはないネイティブモジュールとなります。
ネイティブモジュールを使用する為には、Expo GOから巣立ち、自らネイティブアプリを構築する必要があります。
なんだかたいへんそうなので、今回の記事では取り扱いません。
また明日がんばろう。
まとめ
全体としては、
- csvファイルをcacheディレクトリに書き込む(FileSystemAPI)
- システムピッカ―を開く(sharingAPI)
- ユーザーが手動で保存場所を選択
の手順で処理することで、ローカルファイルへの保存が実装できました。
ExpoのAPIでは、 Expoが持つストレージ領域(Paths.cacheやPaths.document) への書き込みしか許可されておらず、 外部ストレージ(ユーザーが触る領域) への書き込みには、ファイルピッカーを経由する必要がありました。
react-native-fsといったネイティブモジュールを利用すれば、ファイルピッカーを経由せずファイルの保存が行えるそうですが、ネイティブモジュールを利用するには 自らネイティブアプリを構築する 必要があります。
感想
便利だからとExpoGOに頼り、ネイティブアプリっぽいものが簡単にテストできるだなんだと無邪気に喜んでいましたが、自分が使っているものが何者なのかは把握していないとダメですね。
せっかくなので近々、既存プロジェクトの開発ビルドへの移行もやってみようと思います。ExpoGOから巣立ちのとき。