【7】React Nativeでテキストエディタを作ってみる!【エクスポート・バックアップ編】

こんにちは!かたつむり(@Katatumuri_nyan)です!

Reactを触ってみて、サイト的なものは作れるようになりました(*´ω`)
そこで、次はReactNativeを触ってみようと思い、簡単なテキストエディタを作成しようと企んでおります(笑)

データの読み書きはできたので、今回はファイルとしてエクスポートしたいと思います!
バックアップもとれるようにしていけたらな~!

これができたら、見た目がすごくダサいので、デザインも変えていきたい…

よっしゃ!やっていきます!

最初から見る↓
【1】React Nativeでテキストエディタを作ってみる!【下調べ編】【1】React Nativeでテキストエディタを作ってみる!【下調べ編】

前回を見る↓
【6】React Nativeでテキストエディタを作ってみる!【データ管理編】【6】React Nativeでテキストエディタを作ってみる!【データ管理編】

今回やりたいこと

  • マークダウン・HTMLファイルとしてデータをエクスポート
  • ファイルを端末内のファイルに保存できるようにする
  • ファイルをGoogleドライブなどに共有できるようにする
  • バックアップを取れるようにする

ファイルとしてエクスポートして保存

データはJSONとして保存されているみたいです。
これ、メモ帳だけのアプリならいいですが、Markdownテキストエディタとしては不十分なので、ファイルとしてエクスポートしたいと思います(*´ω`)

Expo公式ドキュメントを参考にファイルシステムを導入していきます!

(エクスポートは拡張子指定して保存したら自動でしてくれるんだろうか?)

とりあえず、公式ドキュメント難しいので、やってみます!

documentDirectorycacheDirectoryが選べるみたいです。
一時ファイルではないので、documentDirectoryに保存するようにやっていきたいと思います。

import * as FileSystem from 'expo-file-system';でインポート。
よし、いけるぞ!

ディレクトリ・ファイルを作成する

makeDirectoryAsyncでディレクトリを作成できそうですね。
ディレクトリがない時は作成、あるときは作成しないにもoptionsでできそう。

ファイルを保存するメソッドがない…
androidあるけど…?
reactjs – React native(expo)でテキストファイルを作成する方法は?
↑これより、MediaLibraryでできそうな気がしてきました。
expo install expo-media-libraryします。

createAssetAsyncでいけるのかな?書いてみます。

とりあえず、ファイルも作成できるのか試してみます。

import * as FileSystem from 'expo-file-system';

function saveMdFile(){
  const fileUri = FileSystem.documentDirectory + 'SimpleMarkdown/myFile.md'
  FileSystem.makeDirectoryAsync(fileUri, true)

}
Possible Unhandled Promise Rejection

エラーが出ました!どういう意味…?
Promise Rejectionが拒否されたってことですかね。
とりあえず、処理はできなかったw

MediaLibrary.requestPermissionsAsync()
↑これでメディアライブラリへのアクセスの許可を撮ってからやってみます。

export default async function saveMdFile(){
  await MediaLibrary.requestPermissionsAsync()
  const fileUri = await FileSystem.documentDirectory + 'SimpleMarkdown'
  FileSystem.makeDirectoryAsync(fileUri, true)
}

↑とりあえず、ディレクトリを作成してみます。
(゜-゜)できない。
試行錯誤します!

export default async function saveMdFile(){
  await MediaLibrary.requestPermissionsAsync()
  const fileUri = await FileSystem.documentDirectory + 'SimpleMarkdown'
  await FileSystem.makeDirectoryAsync(fileUri, true).then(e=>{
    console.log(e);
  }).catch(err =>{
    console.error(err);
  })
}

↑こうしてみると↓のエラーが出ます。

ExceptionsManager.js:179 Error: An exception was thrown while calling `ExponentFileSystem.makeDirectoryAsync` with arguments `(
    "file://…省略",
    1
)`
FileSystem.makeDirectoryAsync(fileUri)

もしかして引数の書き方が違う?
↑に変えてみました。

> null

引数書き間違えみたいですね!ww
{ intermediates: true }こうでした!公式ドキュメントはよく読みましょう…

await MediaLibrary.requestPermissionsAsync()

↑ちなみに、これはいりませんでした。

さて、保存されているっぽいんですが、どこにあるんだろう…

FileSystem.readAsStringAsync(fileUri, options)

これで確認してみます。

Error: File 'file://…省略' could not be read.

ファイルないみたいですね!

FileSystem.readDirectoryAsync(fileUri)

これでディレクトリがあるか確認してみます。

picture 2
いるwwwww

FileSystem.makeDirectoryAsync()これでファイルも保存できるっぽいですね。

でもFileSystem.readAsStringAsync()で読み取れません。

中身がないからかもですね。
FileSystem.writeAsStringAsync()これで書き込んでみよう!

だめでしたw
書き込めません。

FileSystem.deleteAsync()これで消してみます。

あ、消せた。

export default async function saveMdFile(){
  const directoryUri = FileSystem.documentDirectory + 'SimpleMarkdown/'
  const fileUri = directoryUri + 'test.md'
  FileSystem.writeAsStringAsync(fileUri, "Hello World", { encoding: FileSystem.EncodingType.UTF8 })
    .then(e => {
      console.log("writeAsStringAsync >>" + e);
    }).catch(err => {
      console.error(err);
    })

  FileSystem.readAsStringAsync(fileUri, { encoding: FileSystem.EncodingType.UTF8 })
    .then(e => {
      console.log("readAsStringAsync >>" + e);
  }).catch(err => {
    console.error(err);
  })
  FileSystem.readDirectoryAsync(directoryUri)
    .then(e => {
      console.log("readDirectoryAsync >>"+ e);
    }).catch(err => {
      console.error(err);
    })

}

↑の実行結果が↓です。
picture 3

ファイル作成までできちゃった。
でもどこにいるのかイマイチ分からないですね…。

reactjs – React native(expo)でテキストファイルを作成する方法は?
↑より。
MediaLibrary.createAssetAsync()MediaLibrary.createAlbumAsync()でユーザーから見えるところにファイル作れそうですね。
作ってみます。

MEDIA_LIBRARY permission is required to do this operation.
↑エラーが出た!w

await MediaLibrary.requestPermissionsAsync()

権限の許可が必要なので、再び↑追加します。
(権限の再要求とかの機能も必要そうですね。)

うーん。

Error: This file type is not supported yet

↑のように出てしまいました。
MediaLibraryって、メディアライブラリへの保存だし、そもそも使うものが違うんでしょうね。

FileSystem.getContentUriAsync(fileUri)share(content, options)をしてみて、ファイルアプリで開いてみます。

picture 4
picture 5
picture 6

できたー!!やった(*´ω`)
普段はアプリ内に保存して、取り出したいときはエクスポートできる感じになりましたね!やった!

これならキャッシュフォルダ使えばよさそう。
うまいこと整形して以下の関数になりました~!

export async function exportMdFile(filename,content){
  const directoryUri = FileSystem.cacheDirectory + 'SimpleMarkdown/'
  const fileUri = directoryUri + filename

  await FileSystem.makeDirectoryAsync(directoryUri, { intermediates: true })
    .then(e=>{
      console.log("makeDirectoryAsync" + e);
  }).catch(err =>{
    console.error(err);
  })

  await FileSystem.writeAsStringAsync(fileUri, content, { encoding: FileSystem.EncodingType.UTF8 })
    .then(e => {
      console.log("writeAsStringAsync >>" + e);
    }).catch(err => {
      console.error(err);
    })

  await FileSystem.readAsStringAsync(fileUri, { encoding: FileSystem.EncodingType.UTF8 })
    .then(e => {
      console.log("readAsStringAsync >>" + e);
  }).catch(err => {
    console.error(err);
  })
  await FileSystem.readDirectoryAsync(directoryUri)
    .then(e => {
      console.log("readDirectoryAsync >>"+ e);
    }).catch(err => {
      console.error(err);
    })

  const shareUrl =await FileSystem.getContentUriAsync(fileUri)
  console.log(shareUrl);
  Share.share({url:shareUrl})
    .then(e => {
      console.log(Share.sharedAction);
    }).catch(err => {
      console.error(err);
    })

}

log吐き多いですが、デバッグのためにご容赦くださいw

日本語ファイル名などで保存できるようにする

今は、冒頭行のテキストがファイル名になるようになっています。
日本語だったりスペースあったりしますよね…。

そこで日本語でも”/”こういうのはいってても保存できるようにします。
文字列をURIエンコード(エスケープ)・デコードする
これで行ってみます!

encodeURIComponent(removeMarks(filename))

JavaScript | ファイル名に使えない記号を削除
String.prototype.replaceAll()
“/”に関しては↑を参考にしました。

function removeMarks(filename) {
  const marks = ["\\", '/', ':', '*', '?', 'a', "<", ">", '|', /^ */g, /^ */g];
  let filename_removeMarks = filename;
  for (const i in marks) {
    filename_removeMarks = filename_removeMarks.replaceAll(marks[i], '')
    }
  return(filename_removeMarks);
  }

ファイルを読み込めるようにする

次は、ファイルをほかのアプリ(ファイルアプリなど)から読み込めるようにしたいと思います!

prscX/react-native-file-selector
↑が簡単そうなので、使っていきます!

npm install react-native-file-selector --save
import RNFileSelector from 'react-native-file-selector';

使えないので、Undefined is not an object RNFileSelector.Show を見てみると、linkする必要がありそうです。

react-native: command not foundとでてlinkできない!
“react-native link” は何をするか

公式ドキュメントより、npx react-native link react-native-file-selectorしてみます。

warn Calling react-native link [packageName] is deprecated in favor of autolinking. It will be removed in the next major release.
Autolinking documentation: https://github.com/react-native-community/cli/blob/master/docs/autolinking.md

このリンク先によると、linkは非推奨らしい…

Expoでreact-native linkが認識されない
↑あ、察し…
npm uninstall react-native-file-selectorしました。

Expo公式ドキュメントにあったので、こちらを使います。

expo install expo-document-picker
import * as DocumentPicker from 'expo-document-picker';

export async function fileSelect(){
  const data = await DocumentPicker.getDocumentAsync()
  console.log(data);
  }