FlutterのローカルDBにDriftを使用する

はじめに

エキサイト株式会社の髙野です。
今回はFlutterのライブラリであるDriftを使用しましたので紹介します。

そもそもDriftとは

DriftとはiOSではRealm, AndroidではRoomのようにオブジェクトをアプリがアンインストール等されない限り端末内に残しておくためのデータベースライブラリです。
Roomを参考にしているようでもともとはMoorという名前でした。それから名前が変更され、Driftという名前になったそうです。

環境

flutter: 3.3.8
drift: 2.4.2
sqlite3_flutter_libs: 0.5.0
path_provider: 2.0.0
path: 1.8.2

インストール

上記に環境としてライブラリのバージョンを記載しましたが、以下URLを参考に pubspec.yaml に記述してください。

drift.simonbinder.eu

実装

今回僕が実装した方法としてはrepository→dataSource→dao→databaseという形にしました。
databaseから遡るように紹介します。

database

@DriftDatabase(
  tables: [
    Users,
  ],
  daos: [
    UserDao,
  ]
)
class AppDatabase extends _$AppDatabase {
  AppDatabase() : super(_openConnection());

  @override
  int get schemaVersion => 1;
}

LazyDatabase _openConnection() {
  return LazyDatabase(() async {
    final dbFolder = await getApplicationDocumentsDirectory();
    final file = File(join(dbFolder.path, 'db.sqlite'));
    return NativeDatabase(file);
  });
}

まずは大元のデータベースを作成し、管理するクラスを作成します。
作成しましたら、 build_runner を使用してコード生成を行います。

@DataClassName('UserTable')
class Users extends Table {
  @override
  String? get tableName => 'Users';

  @override
  List<Set<Column>> get uniqueKeys => [
        {userCode}
      ];

  IntColumn get id => integer().autoIncrement()();

  TextColumn get userCode => text()();

  TextColumn get name => text()();

  IntColumn get age => integer()();
}

次にTableClassの作成をします。
@DataClassName で設定した名前がModel名となります。
uniqueKey を設定することもでき、これで重複しないようにすることができます。

dao

@DriftAccessor(tables: [Users])
class UserDao extends DatabaseAccessor<AppDatabase>
    with _$UserDaoMixin {
  UserDao(AppDatabase db) : super(db);

  Future<List<UserTable>> getAllUsers() {
    return select(users).get();
  }

  Future<void> insertUser({
    required Insertable<UserTable> user,
  }) async {
    await batch((batch) {
      batch.insert(
        users,
        user,
        onConflict: DoUpdate(
          (_) => user,
          target: [
            user.name,
            user.age,
          ],
        ),
      );
    });
  }
}

以上がDaoになります。今回記載したのは全件取得とInsertですがこちらを紹介します。
データを取得する時には select を使用し、挿入したい時には batch.insert を使用します。作成した後 build_runner でコード生成をします。

dataSource

class UserDatabaseDataSource {
  const UserDatabaseDataSource(this._database);

  final AppDatabase _database;

  Future<List<User>> getAllUsers() async {
    final localUsers =
        await _database.userDao.getAllUsers();
    return localUsers.map((e) => User(
          userCode: e.userCode,
          name: e.name,
          age: e.age,
        )).toList();
  }

  Future<void> addUser(User user) async {
    final insertableUser = UsersCompanion(
      userCode: Value(user.userCode),
      name: Value(user.name),
      age: Value(user.age),
    );
    return await _database.userDao.insertUser(user: insertableUser);
  }
}

以上がDataSourceになります。ここでTableModelと実際に使用するModelの変換を行います。
insert時には生成された UsersCompanion クラスを使用し、変換をします。そのマッピング時に各値に対してValueで括って上げる必要があるのでご注意ください。

repository

class UserRepository {
  const UserRepository(this._dataSource);

  final UserDatabaseDataSource _dataSource;

  Future<List<User>> getAllUsers() async {
    return await _dataSource.getAllUsers();
  }

  Future<void> addUser({
    required User user,
  }) async {
    return _dataSource.addUser(user);
  }
}

特に変わった処理はしていませんが、dataSourceから必要な関数を呼び出しています。

最後に

エキサイトではフロントエンジニア、バックエンドエンジニア、アプリエンジニアを随時募集しております。募集にはありませんが、長期インターンも歓迎していますので、興味があれば連絡いただければと思います。

カジュアル面談はこちらになります! meety.net

募集職種一覧はこちらになります!(カジュアルからもOK) www.wantedly.com