Flutter: リストア(インポート)したはずのsqfliteの「.db」ファイルからデータを読み込めない
結論:リストア前に「deleteDatabase」で旧DBを削除し、リストア後に「openDatabase」で新DBを再オープンする
2022/6/12 Flutter エラー・バグ日記
ローカルDBに「sqflite」を用いているアプリで、データベースファイル(以下、「.db」ファイル)のバックアップと、リストア/インポート(以下、リストア)の機能を実装しているが、実は、リストアが正常動作していないことが発覚。
非常に大まかだが、リストアの処理は、以下の方法で行っていた。
- Googleドライブ上にバックアップされている「.db」ファイルを、「googleapis」パッケージの「files.get」メソッドを使い、アプリ内に読み込む
- リストア先のパス(既にDBが存在する場合は、そのDBのパスと同じ)を指定した「File」クラスのインスタンスを作成
- 上記1.で読み込んだデータを、上記2.のインスタンスに「writeAsBytes」メソッドで書き込む(既存のDBが存在する場合は、同じファイル名「〜.db」で上書きされる)
この状態でリストアを実行後、「.db」ファイルから読み込んだデータを表示する画面に遷移すると、データが表示されるときと、表示されないときがあることが判明(アプリを再起動すれば、表示される)。
「Device File Explorer」で確認すると、データが表示されるとき・されないときのいずれも、リストアした「.db」ファイルはきちんと存在しており、「DB Brower for SQLite」を使って中身を見ても、期待どおりのデータが格納されている。
DBファイル自体は、きちんとリストアできている。なぜ?と詰まる。
色々なパターンで検証した結果、アプリのインストール後に、データベースを開く「openDatabase」メソッドを1回以上実行した後にリストアすると、「.db」ファイルからデータを読み込めないと分かった。
逆に、アプリのインストール後、「openDatabase」メソッドを1回も実行しない状態でリストアしたときは、問題なく読み込めていた。
よく考えると、「sqflite」のリストア方法をきちんと調査していなかった。。そこで、改めて調べてみると、こちらの情報を見つけた。
上記記事では、更に以下の「sqflite」の公式説明ページが参照されていた。
この情報によると、「.db」ファイルを差し替えるときは、
- 旧ファイルを、「deleteDatabase」メソッドで削除しておく
- 新ファイルを、「openDatabase」メソッドで再オープンする
という処理が必要らしい。
恐らく、この処理をやらずに、アプリインストール後に、いったん「openDatabase」をしてしまうと、その後で新しい「.db」ファイルをリストアしても、データベースのインスタンスが古い状態のまま更新されないので、データを読み込めなくなるのだろう、、、と理解。
てっきり「.db」ファイルを上書きする(差し替える)だけで、新しいデータを読み込めると思い込んでいたが、そうではなかった。。。
よく考えると、別のローカルデータベースである「hive」のバックアップ・リストアの処理を実装したときも、同様の理由でリストアがうまく行かなかったことを思い出した。
(↓こちらご参考)
結果、リストアの処理を、以下のように修正したところ、どのような条件下でも、リストア後の「.db」ファイルからデータを読み込きるようになった。
// クラス・メソッド(関数)・プロパティ(変数)について、筆者が作成したものには、末尾に大文字のオー「O」をつけて表記 // ※パッケージで予め決められているものと区別しやすくするため // CRUD処理の記述に必要なDatabase?型のインスタンス作成 static Database? databaseO; // ① // リストア前にDBファイルをクローズする処理 // DBファイルをアプリ内専用フォルダに保存していた場合の例 Directory documentsDirectoryO = await getApplicationDocumentsDirectory(); String fullDbPathO = join(documentsDirectoryO.path, "databaseFile.db"); await deleteDatabase(fullDbPathO); // ② // 外部から読み込んだデータを、リストア前と同じパスで保存 // ここではdataStoreOは、外部から読み込んだList<int>型のデータ ※詳細は省略 final importFileO = File(fullDbPathO); await importFileO.writeAsBytes(dataStoreO); // ③ // 上記パスのDBファイルをオープンする処理 databaseO = await openDatabase( fullDbPathO, version: databaseVersionO, // データベースのバージョン ※詳細は省略 onCreate: onCreateO, // テーブルを作成するメソッド ※詳細は省略 );
リリースしたアプリ(全てFlutterで開発)
個人アプリ開発で役立ったもの
おすすめの学習教材
\超初心者向けでオススメな元Udemyの講座/
\キャンペーン時を狙えば安価で網羅的な内容が学べる(日本語訳あり)/
\Gitの基礎について無料で学べる/
おすすめの学習書籍
\実用的。image_pickerに関してかなり助けられた/
\Dartの基礎文法を素早くインプットできる/