機種変更時のデータ引き継ぎ機能を作った

「引き継ぎコード」を使ってデータを新しい端末に移す機能の実装について書いていきます。


そもそも何を引き継ぐのか


アプリ内には次のようなデータが保存されています。


過去問・模擬試験の正答(◯)/誤答(✕)の記録

各回の状態(□未答/△途中/■終了)

集計表(未解答数・正答数・誤答数)

AIに質問した回答の履歴


これらはすべて端末のローカル保存領域(UnityではPlayerPrefsという仕組み)に入っています。PlayerPrefsは「キー」と「値」のペアでデータを保存する、アプリ内の小さな引き出しのようなものです。機種変更するとこの引き出しごと消えてしまうため、引き継ぎ機能が必要になります。


① データを送り出す側(旧端末)

1. 送るべきキーを一覧化する


PlayerPrefsには数百個のキーが保存されていますが、全部を無差別に送るわけにはいきません。まず「今回の試験構成で使われているキー名」を規則的に組み立てて、リストアップします。


2. 値をJSON形式にまとめる


集めたキーと値を、ひとつの大きな文字列(JSON)にまとめます。JSONとは、異なるシステム間でデータをやりとりするための世界共通の書き方のようなものです。


json

{

  "sid": "シートID",

  "pp": { "キー1": "値1", "キー2": "値2" },

  "ai": { "問題ID": "AI回答" }

}


このとき注意が必要なのが「エスケープ処理」です。保存されている値の中には改行や引用符(")が含まれることがあり、そのままJSONに入れると壊れてしまいます。そこで \n や \" のような特別な記号に置き換えてから埋め込みます。


3. サーバーに送信してコードを発行してもらう


まとめたJSONを、サーバー側の仕組みに送信します。サーバー側はこれを一時的に保管し、6桁の「引き継ぎコード」を発行して返してくれます。このコードには有効期限があり、期限が切れると自動的に消えます。


通信は失敗することもあるため、最大3回まで自動でリトライする仕組みも入れています。


サーバー側の裏側:GASからCloudflareへ切り替えた理由


当初はGoogle Apps Script(GAS)でこのサーバー役を作っていました。GASは無料で手軽に使える一方、1回のリクエストで送れるデータ量や、保存できるデータサイズに制限があり、試験データが多いユーザーだと容量的に不安が残る作りでした。


そこで、保管場所をCloudflare R2、受け口をCloudflare Workerに切り替えました。それぞれ簡単に言うと次のような役割です。


Cloudflare Worker:スマホからのリクエストを受け取る窓口。「保存して」「読み出して」という指示を処理するプログラムが動いている場所

Cloudflare R2:実際にデータを置いておく倉庫。ファイルをそのまま保存できるストレージサービス


仕組みはシンプルで、アプリからJSONが送られてくると、Workerがランダムな6桁コードを発行し、そのコードを「ファイル名」としてR2にJSONをそのまま保存します。新しい端末からはそのコードを指定してWorkerに問い合わせ、R2から該当ファイルを取り出して返す、という流れです。


このR2には「置いてから24時間経ったら自動的に削除する」という設定(ライフサイクルルール)をCloudflareの管理画面上で組んでいます。これにより、期限切れデータを消すための処理をコード側で自作する必要がなくなりました。あわせて、Worker側でも以下のような安全対策を入れています。


コードとアプリの組み合わせが一致しない場合は失敗回数をカウントし、一定回数(5回)を超えたらそのデータごと削除

復元に成功したら、そのコードのデータは即座に削除(1回限りの使い切り)

事前に共有しておいた合言葉(シークレット)が一致しないリクエストは拒否


GASに比べて、容量面の余裕と削除処理の自動化という点で運用が楽になりました。


② データを受け取る側(新端末)

1. コードを入力してサーバーに問い合わせる


ユーザーが6桁のコードを入力すると、そのコードを使ってサーバーに「このコードのデータをください」とリクエストします。


2. 受け取ったJSONを分解する


サーバーから返ってきたJSONを解析し、元の「キーと値」の形に戻します。UnityにはJSONを標準で扱う機能もありますが、辞書(キーと値の組み合わせ)をそのまま扱うのが苦手なため、今回は簡易的な自作のJSON解析処理を用意しました。


3. 端末IDが一致するか確認する


引き継ぎ元と引き継ぎ先で同じ問題を収録しているかを判別するための「ID」を比較します。IDは、9回までの試験を収録しているバージョンと10回まで収録しているバージョンが混じらないようにするためのものです。これが一致していれば、正誤記録(◯✕)も含めて完全に復元します。一致していない場合は、復元しません。これは、試験の並び順がアプリのバージョンによって変わることがあり、順番だけを頼りに復元すると別の問題の記録に上書きしてしまう危険があるためです。


4. PlayerPrefsに書き戻す


分解したデータを、新しい端末のPlayerPrefsに一つずつ書き込みます。これで集計表や正誤記録が新しい端末でも表示されるようになります。


実際にあった不具合:腕試し模擬試験だけ引き継げない


「腕試し模擬試験(模擬試験の1回目)の集計表だけ引き継がれない」ということがありました。


原因は、①の「送るべきキーを一覧化する」処理にありました。腕試し模擬試験は内部的には「試験ID=0」という特別な扱いになっており、通常の試験一覧のループでは存在が認識されない構造だったのです。同じ「試験ID=0」を使う「要チェック問題」のキーは生成されていたのに、腕試し模擬試験のキーだけ生成漏れがありました。


対応として、キー一覧を作る処理に「試験ID=0・模擬試験用」の集計表キーを個別に追加しました。ログで確認したところ、修正後は腕試し模擬試験の集計表も正しく送信・復元されるようになりました。


まとめ

・データ引き継ぎは「PlayerPrefsのキーを集める → JSONにまとめる → サーバー経由でコード化 → 新端末でコードから復元」という流れ

・順番に依存するデータ(正誤記録)とIDに依存するデータ(AI回答)で復元条件を分けている

・特殊な試験区分(試験ID=0)は通常のループから漏れやすく、今回のような見落としが起きやすい