Data RestoreExpo bareReact NativeReact Native Paper

【ReactNative】スマホの機種変更後のデータ引き継ぎ機能を実装する〜アプリ実装済み〜

abundance Data Restore
スポンサーリンク

復号化+リストア版Apollo ClientReactive variables データと SQLite のデータを暗号化し バックアップ ➡︎ リストア する方法をご紹介しています。

開発環境(Expo Bare)

  • “typescript”: “^5.2.2”
  • “expo”: “~49.0.13”,
  • “react”: “18.2.0”,
  • “react-native”: “0.72.6”,
  • “expo-router”: “^2.0.0”,
  • “react-native-paper”: “^5.10.6”,
  • @apollo/client: “^3.8.5”,
  • expo-sqlite”: “~11.3.3”,
  • crypto-js: “^4.2.0”,
  • expo-sharing: “~11.5.0”,
  • expo-file-system”: “~15.4.5”,
  • react-native-restart: “^0.0.27”,

セットアップする

「暗号化+バックアップ版」をご参考ください。

バックアップする

リストアする

全体の流れ:一部の詳細条件は割愛しています
  • リストア関数を作成する

    // onRestore()

    import RNRestart from 'react-native-restart';
    
      const [backupDatas, setBackupDatas] = useState<string | undefined>('');
      const [parsedBkupDatas, setParsedBkupDatas] = useState<BackupListType[]>([]);
    
      const onRestore = () => {
        if (!isStep1Done) {
          onStep1();
        } else if (isStep1Done && !isStep2Done) {
          onStep2();
        } else if (isStep1Done && isStep2Done && !isStep3Done) {
          onStep3();
        } else if (isStep1Done && isStep2Done && isStep3Done && !isStep4Done) {
          onStep4();
        } else onStep5();
      };
    
  • Step1
    バックアップファイルをピックアップする

    // onStep1()

      const onStep1 = async () => {
        const j = await jcommonMapper.pickBackupFile();
        setBackupDatas(j);
      };

    // pickBackupFile()

    import {xor} from 'lodash';
    import * as FileSystem from 'expo-file-system';
    import * as DocumentPicker from 'expo-document-picker';
    
    // 暗号化+バックアップ版
    const BackupListProperties = [
      'rvCommon',
      'preferredTheme',
      'items',
    ];
    
      pickBackupFile: async () => {
        const result = await DocumentPicker.getDocumentAsync({
          type: 'application/json',
        });
        if (result.canceled) return;
        if (result?.assets?.length) {
          const uri = result.assets.map(el => el.uri)[0];
          const fileData = await FileSystem.readAsStringAsync(uri);
          const decryptedData = jcommonMapper.jsonParse(fileData);
          const j = decryptedData.map(el => Object.keys(el)[0]);
          const a = xor(j, BackupListProperties);
          const isTargetDatas = a.length === 0;
          if (isTargetDatas) {
            return fileData;
          } else return undefined;
        }
      },
  • Step2
    対象テーブルをリセットする(SQLiteの場合)

    // onStep2()

    const db = jsqlMapper.opendb();
    const clear = async () => await AsyncStorage.clear();
    
      const onStep2 = async () => {
        await jcommonMapper.rvReset(clear);
        await jcommonMapper.dbReset(db);
      };

    // rvReset()

      rvReset: async (
        storageClear: (callback?: Callback | undefined) => Promise<void>,
      ) => {
        try {
          await storageClear();
          console.log('🥕 AsyncStorageClear(): done');
        } catch (err) {
          return {isRvResetDone: false};
        }
        return {isRvResetDone: true};
      },

    // dbReset()

    dbReset: async (db: SQLiteDatabase) => {
        db.transaction(
          tx => {
            tx.executeSql('DROP TABLE IF EXISTS items');
            tx.executeSql('"CREATE TABLE IF NOT EXISTS items (id INTEGER PRIMARY KEY NOT NULL AUTOINCREMENT, done INT, value TEXT);"');
          },
          err => {
            console.log('!@# dbReset/err:', err);
            jcommonMapper.isDbResetDone(false);
            crashlytics().recordError(new Error(err.toString()), 'dbReset');
          },
          () => jcommonMapper.isDbResetDone(true),
        );
      },
  • Step3
    オブジェクトファイルに変換する

    // onStep3()

      const onStep3 = () => {
        const c = jcommonMapper.jsonParse(backupDatas);
        setParsedBkupDatas(c);
      };

    // jsonParse() – 複合化する

      jsonParse: (fileData: string) => {
        /** Decrypt */
        const ciphertextOfBackuped = fileData;
        const bytes = CryptoJS.AES.decrypt(ciphertextOfBackuped, 'secret key 123');
        const decryptedData = JSON.parse(bytes.toString(CryptoJS.enc.Utf8));
    
        return decryptedData as BackupListType[];
      },
  • Step4
    リストアする

    // onStep4()

      const onStep4 = async () => {
        await jcommonMapper.restoreRvdatas(parsedBkupDatas);
        await jcommonMapper.restoreDbdatas(db, parsedBkupDatas);
      };

    // restoreRvdatas()

      restoreRvdatas: async (parsedBkupDatas: BackupListType[]) => {
        const re = await Promise.all(
          parsedBkupDatas.map(prd => {
            if (prd.rvCommon) {
              const _rvCommon = {
                ...prd.rvCommon,
                userId: rvCommonVar().userId,
                preferredTheme: rvCommonVar().preferredTheme.
                point: rvCommonVar().point,
              };
              rvCommonVar({..._rvCommon});
            }
          }),
        );
        return !!re.length;
      },

    // restoreDbdatas()

      restoreDbdatas: async (
        db: SQLiteDatabase,
        parsedBkupDatas: BackupListType[],
      ) => {
        const items = compact(flatten(parsedBkupDatas.map(el => el.items)));
        db.transaction(
          tx => {
            items.map(h =>
              executeSql.insertItemsFromBkup(
                tx,
                h.done,
                h.value,
              ),
            );
          },
          err => {
            console.log('!@# restoreDbdatas/err:', err);
            jcommonMapper.isRestoreDbDatasDone(false);
          },
          () => jcommonMapper.isRestoreDbDatasDone(true),
        );
      },
  • Step5
    アプリを再起動する

    // onStep5()

      const onStep5 = () => {
        RNRestart.Restart();
      };

動作デモ

Android
iOS

リリースする

※上記の詳細は以下のアプリに実装されています!

コメント

タイトルとURLをコピーしました