🙄

[Flutter]drift使ってたらDB周りでエラー起きた件

2025/01/18に公開

Daily Blogging28日目

スタバで開発してるよ
※スタバ来たの半年ぶりくらい

Flutterでデータを扱うアプリの開発してる
DB操作はdriftってやつを使用

DBが開けません

ChatGPT協力のもとコードを色々書いてるとselectしているところでエラーが発生してアプリが動かなくなった

SqliteException (SqliteException(14): while opening the database, unable to open database file, unable to open database file (code 14))

DBのファイルが開けない...?
そんなバカな
さっきまではちゃんと開けてたのに....

助けてChatGPT

とりあえずChatGPTに相談。
シミュレータの再起動やアプリの再インストールをお勧めされたのでとりあえずやってみる。。。
でも何も変わらない

こういう時は落ち着いてログを確認しよう

結局信じられるのはログだけ
Flutterの場合、flutter logsでコンソールにログ出せるのでそれで詳細確認
ログにはまた別のエラーが出てた

It looks like you've created the database class DatabaseHelper multiple times. When these two databases use the same QueryExecutor, race conditions will occur and might corrupt the database.

DBを複数イニシャライズしてるらしい
そんなバカなことあるはずない
と思ったけど、アプリの各ページでDB操作を色々書いてるDatabaseHelperクラスを呼んでた
DatabaseHelperはこんな感じ

class DatabaseHelper extends _$DatabaseHelper {
  DatabaseHelper() : super(_openConnection());

  @override
  int get schemaVersion => 1;

  Future<int> addSoul(SoulsCompanion soul) async {
    return await into(souls).insert(soul);
  }

  Future<int> addPhoto(PhotosCompanion photo) async {
    return await into(photos).insert(photo);
  }

  Future<List<Soul>> getAllSouls() async {
    return await select(souls).get();
  }
}

このクラスをいたるところで呼んでいた

// こんな感じで
final dbHelper = DatabaseHelper();

何が問題なの??

名前は同じだけど存在としては異なるDatabaseHelperが複数いると、同じDB操作をするクラスが複数存在することになり、最悪「DB操作の競合」や「DBの破損」なんかにつながったりするらしい。

なので、driftではシングルトンをお勧めしてる。
今回もシングルトンを採用することでエラーを回避してみよう。

class DatabaseHelper extends _$DatabaseHelper {
  // Singleton instance
  static final DatabaseHelper _instance = DatabaseHelper._internal();

  // Factory constructor to return the same instance
  factory DatabaseHelper() {
    return _instance;
  }

  // Private internal constructor
  DatabaseHelper._internal() : super(_openConnection());

  @override
  int get schemaVersion => 1;

  Future<int> addSoul(SoulsCompanion soul) async {
    return await into(souls).insert(soul);
  }

  Future<int> addPhoto(PhotosCompanion photo) async {
    return await into(photos).insert(photo);
  }

  Future<List<Soul>> getAllSouls() async {
    return await select(souls).get();
  }

  Future<List<Photo>> getPhotosBySoulId(int soulId) async {
    return (select(photos)..where((tbl) => tbl.soulId.equals(soulId))).get();
  }
}

factoryを使用して、新しいオブジェクトを作らずに、同じオブジェクトを返すようにして
https://dart.dev/language/constructors#factory-constructors

これで同じクラスのインスタンスが複数作られることはなくなる

実際にアプリを動かしてみると、エラーも発生しなくなったよ。

Discussion