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

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

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

長い環境構築を経て、やっと前回ReactNativeを触ることができました✨
これで、アプリは作れそうだってことが分かったので、ここから最低限の機能を付けていきたいと思います~!

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

前回を見る↓
【5】React Nativeでテキストエディタを作ってみる!【テキストエリア作成編】【5】React Nativeでテキストエディタを作ってみる!【テキストエリア作成編】

ブランチを切る

作業ブランチがmasterのままなので、ブランチ切りましたw
今回、WSL2で環境構築した関係なのか、gitが管理者権限必要で、sourcetreeで操作できなくなっちゃいました💦
(環境構築の時に色々いじっておかしくなった可能性も)

ということで、WSL2のubuntuちゃんから、管理者権限でgitコマンド打ってます😨
何か方法あるとも思うけど、コマンド打った方が速いので←
(VScodeから手軽にコマンド打てるからひゃっほい)

基本的なGitコマンドまとめ

データ管理

一応テキストエディタなので、データをどこかに格納しないといけません。
(ボタン作ってて気づきましたw)

今回は、アプリ開発が開始できるのか、ちゃんとコーディングできるのか、あんまり見通しが立ってなかったので、デザイン等していなくてw
データ管理について考えていませんでしたw

データ管理はどうやってするのか、ちょっと調べたいと思います。

端末に保存するケース

[React Native] データ永続化についてAsyncStorageとRealmを調べてみた
↑これを読む感じだと、AsyncStorageで良い気がしますね。

以前、ReactでFirebaseを使ったことがありますが、クラウドでデータ管理するほどでもない気がしてて。
【ReactNative】AsyncStorageを使ってデータを保存・取得する方法
↑これがReact Nativeの元のAsyncStorageっぽいですね。

react-native-storageでデータを保持する
↑簡単そうなので、今回はこちらをベースに実装していけそう。

クラウドに保存するケース

react-native-storageで端末にデータを持たせる
↑より

こんな場合にオススメ
ローカルにRDBを持つほどではないデータ(単一のフラグや日付、文字列など)
ぱっと思いつくのは、アプリの動作に関わる設定値ですかね。
例えばアプリの設定画面からテーマカラーを変更できて、そのテーマカラーはアプリ内でEnumで定義されている場合、端末内に保持していれば大筋問題ないはずです。
※上記の場合も、同一アプリを複数端末にインストールしていて、テーマカラーを共有したい場合等はサーバーサイドにデータを持たせておくべきだとは思います。

むむむ

同一アプリを複数端末にインストールしていて、テーマカラーを共有したい

したい!(`・ω・´)

テーマを実装するかしないかはおいておいて、メモデータの共有はしたいですね…。

React Nativeにおけるローカルデータベースの考察
↑すごい!

クラウドでのデータの同期にはアカウント作成が必要ですよね。
アカウント作成機能までつけるのはちょっとハードルが高いですね~。
今回は、共有機能を使ってクラウド(Googleドライブとか)に保存できるくらいにとどめたいと思います。
(バックアップではなく、単純に共有にします。)

React Native + Expo アプリでunstatedのデータを永続化

データの保存方法決定

  • ローカルストレージに保存する
  • 共有機能で外部ストレージにも保存できる
  • アプリを閉じた時の未保存のデータはローカルストレージに保持

で行こうと思います!

データの読み書きをする

React Nativeで遊んでみよう – 其ノ弐:react native storage でデータを保存
↑を参考に実装していきます!

とりあえずnpm install react-native-storageしてExpo再起動

実装手順としては、以下のようにできたらなと。

  1. 保存・開くボタンを作る
  2. データを保存できるようにする
  3. データを開けるようにする
  4. データ一覧を取得する
  5. データ一覧から開けるようにする

よっしゃ!

1. 保存・開くボタンを作る

パネルの上にメニューバーを作って、そこに保存・開くボタンを作ります。

picture 10

// panelコンポーネントの上の方
<View style={styles.panelBody}>
   <View style={styles.panelMenu}>
     <Pressable style={styles.button}><Text style={styles.buttonText}>開く</Text></Pressable>
     <Pressable style={styles.button}><Text style={styles.buttonText}>保存</Text></Pressable>
   </View>
 </View>

2. データを保存できるようにする

とりあえず保存していきます!w
React Nativeで遊んでみよう – 其ノ弐:react native storage でデータを保存
↑を参考に初期設定と、保存の設定をしました。

【ReactNative】現在の日付、時間表示をする方法
保存時間も設定

データの設定

export function fileData(filetitle, filetext) {
  const filename = filetitle + '.md'
  const date = new Date().toLocaleString()
  return ({
    key: 'loginState',
    id: 'id',
    data: {
      name: filename,
      date: date,
      text: filetext
    }
  })
}

↑テストデータはこんな感じ

データの保存関数の作成

picture 11
あれ!AsyncStorageが非推奨だ!www

sunnylqm/react-native-storageによるとnpm install @react-native-community/async-storageこれも必要みたいです。
いれます!

- remove @react-native-community/async-storage from package.json
- run "expo install @react-native-async-storage/async-storage"
- update your imports manually, or run "npx expo-codemod sdk41-async-storage './**/*'".

expo install 使ってないからワーニングでちゃったw
package.jsonから@react-native-community/async-storageを消して、
expo install expo install @react-native-async-storage/async-storageします!

importも右に変えます。import AsyncStorage from '@react-native-async-storage/async-storage'

新たにStorage.jsってファイルを作って、とりあえず以下のコードにしました。

import AsyncStorage from '@react-native-async-storage/async-storage';
import Storage from 'react-native-storage';

//ストレージの設定
var storage = new Storage({
  // 最大容量, 1000がデフォルト 
  size: 1000,

  // AsyncStorageを使う(WEBでもRNでも)。
  // セットしないとリロードでデータが消えるよ。
  storageBackend: AsyncStorage,

  // (たぶん)キャッシュの期限。デフォルトは一日(1000 * 3600 * 24 milliseconds).
  // nullにも設定できて、期限なしの意味になるよ。
  defaultExpires: 1000 * 3600 * 24,

  // メモリにキャッシュするかどうか。デフォルトは true。
  enableCache: true,

  // リモートシンクの設定(だと思う。)
  sync: {
    // これについては後述
  }
})

export function fileData(filetitle, filetext) {
  const filename = filetitle + '.md'
  const date = new Date().toLocaleString()
  return ({
    key: 'mdfile',
    id: 'id',
    data: {
      name: filename,
      date: date,
      text: filetext
    }
  })
}

export function saveFileData(fileData) {
  storage.save(fileData);
}

export function loadFileData(fileData) {
  storage.load(fileData).then(ret => {
    // ロードに成功したら
    console.log(ret.name + ' is ' + ret.text);
  }).catch(err => {
    // ロードに失敗したら
    console.warn(err.message);
    switch (err.name) {
      case 'NotFoundError':
        // 見つかんなかった場合の処理を書こう
        break;
      case 'ExpiredError':
        // キャッシュ切れの場合の処理を書こう
        break;
    }
  });
}

テキストの冒頭行からファイル名を取得

さっきの関数を使ってみます。
先頭の行をファイル名にするようにします!

テキストを配列にして、空文字じゃない先頭の行を返すようにします。

JavaScript配列の空文字を削除

// App.js
function saveFileData(){
  const firstRowEndPos = textInput.split('\n');
  const filetitle = firstRowEndPos.filter(Boolean)[0]
  S.saveFileData(S.fileData(filetitle, textInput))
}
// App.js
<MyPanel
  saveFileData={saveFileData}
/>

パネルの保存ボタンに渡してみました。
保存できたっぽいんですが、できているのかわかりませんw

3. データを開けるようにする

とりあえず、保存したものをロードしてみますw

picture 12
開けている!!!

どこに保存しているんだろうw
後で保存しているところを突き止めますw
あと、保存できたかどうかのモーダルかポップアップも出したいな。

4. データ一覧を取得する

データ一覧の取得ってできるんかいな…?

getallkeysこれかな?
picture 13

取得はできたっぽいですね。

hoge: [object Object]の対処法

// App.js コンポーネント
function GetAllData() {
  const [data, setData] = useState('')

  useEffect(() => {
    S.GetAllData().then(e => {
      console.log('App.js 4>>'+e);
    })
  }, []);
  console.log(data);
  return(
    <Text>a</Text>
  )
}
// Storage.js 情報取得関数
export async function loadFileData(fileData) {
  const data = await storage.load(fileData).then(ret => {
    return ret
  }).catch(err => {
    console.warn('S 54>>'+ JSON.stringify(fileData.key) + ">>>>" + err.message + ">>>>" +err);
    switch (err.name) {
      case 'NotFoundError':
        break;
      case 'ExpiredError':
        break;
    }
  });
  return data
}

export async function GetAllData(){
    let data = []
    keys = await AsyncStorage.getAllKeys()

  for(let i in keys){
    const key = keys[i];
    const json = await loadFileData({ key: key }); 
    if(!json===false){
      data.push(await loadFileData({ key: key }))
    }
  }
  return data
}

データ一覧を表示

picture 14
こんな感じでデータを取得できたので、表示してみたいと思います(*´ω`)

picture 15

お!表示できました。
今はTextコンポーネントで表示しているので、ボタンにしたりなんやかんやします。
あと、App.jsに書いているので、パネルコンポーネントの方に移植します。

picture 16

パネルに移植できました(*´ω`)
色が絶賛やばいですが、何も考えないことにしましょうw

5. データ一覧から開けるようにする

つぎは、データ一覧のボタンをおしたら開くようにします。
親子コンポーネント間のデータの受け渡し難しい💦

とりあえず、keyでとってこれるみたいなので、データ名を押されたときにkeyを親コンポーネントにとばしたいと思います。

picture 17
とりあえず、プレビューの方には反映できました!
テキストエリアにも反映したい🤔

picture 18

できました~!
とりあえずこれで、データを保存して開くことはできますね。

データを編集したりするのはもうちょっと手順が必要そうです。

あと、開くボタンが必要なさそうなので←
新規作成ボタンに変えます。

回線エラー出てきた

Cannot connect to the Metro server.

Try the following to fix the issue:
- Ensure that the Metro server is running and available on the same network
- Ensure that your device/emulator is connected to your machine and has USB debugging enabled - run 'adb devices' to see a list of connected devices
- If you're on a physical device connected to the same machine, run 'adb reverse tcp:8081 tcp:8081' to forward requests from your device
- If your device is on the same Wi-Fi network, set 'Debug server host & port for device' in 'Dev settings' to your machine's IP address and the port of the local dev server - e.g. 10.0.1.1:8081

URL: packager.mp-kiu.anonymous.reactnative-texteditor.exp.direct:80

手順に従って直してみます。
これでLAN接続できるといいな

adb reverse tcp:8081 tcp:8081

接続できなくなったww
パソコン再起動して事なきを得ましたw
LAN問題はまた今度にしよう。

データのkey設定

データにkeyを持たせようと思います。
データの固有のID的に使えるはず。

const [dataKey, setDataKey] = useState(null)
  • 新規作成時→dataKey=null
  • データ編集時→dataKey=固有のkey

となるようにします。

nullの時に、連番のkeyを発行するようにしよう!
keyには_が使えないので、そこだけ気を付けていきます。

// App.js
function saveFileData(){
  const firstRowEndPos = textInput.split('\n');
  const filetitle = firstRowEndPos.filter(Boolean)[0]
  let key = dataKey

  if (!key){
    key = 'SimpleMD' + data.length + 1
  }

  if (!textInput===false){
    S.saveFileData(S.fileData(key, filetitle, textInput))
  }else{
    console.log('テキストが入力されていません');
  }
}

できました~!

保存後にデータ一覧に反映する

新規作成して保存した時に、パネルのデータ一覧に反映するようにしました。

// App.js
function saveFileData(){
  const firstRowEndPos = textInput.split('\n');
  const filetitle = firstRowEndPos.filter(Boolean)[0]
  let key = dataKey

  if (!key){
    key = 'SimpleMD' + data.length + 1
  }

  if (!textInput===false){
    S.saveFileData(S.fileData(key, filetitle, textInput))
  }else{
    console.log('テキストが入力されていません');
  }
  getAllData()
}

保存時にモーダルを出す

保存できたかどうか分かり難いので、モーダルを出そうと思います!
公式ドキュメントにモーダルコンポーネントがあるので、使っていきます。

アラートって言うコンポーネントもあるんですが、モーダルの方が可愛いので、モーダルにしますw
picture 1

// App.js
  <Modal
    animationType={'slide'}
    presentationStyle={false}
    transparent={true}
    visible={isModalOpen}
    onRequestClose={()=>{
      console.log('とじるんるん');
    }}
  >
    <View style={styles.centeredView}>
      <View style={styles.modal}>
        <Text>{isSubmit?"保存できました!":"保存できませんでした!"}</Text>
        <Pressable
        style={styles.modalBtn}
        onPress={closeModal}
        >
          <Text style={styles.modalText}>閉じる</Text>
        </Pressable>
      </View>
    </View>
  </Modal>

React Nativeはコンポーネントが用意されているから楽ですね~(*´ω`)

今日はこの辺にしたいと思います!
続きをお楽しみに(*´ω`)

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

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です