Floor — Flutter用のORM(Object-Relational Mapping)であり、SQLiteの上に型付けされたレイヤーを提供します。生のSQLiteクエリとは異なり、Floorはアノテーション付きのDartモデルからDAOクラスを生成します。Pub.dev(2024年)によると、Floorは3500以上のFlutterプロジェクトで使用されており、driftやhiveと並んでローカルデータストレージのための最も人気のあるORMソリューションのトップ3に入っています。
重要なポイント
FloorはSQLite上に構築されたFlutterおよびDart用のORMライブラリです。エンティティ(Entity)、Data Access Objects(DAO)、データベース(Database)を記述するためにアノテーションを使用します。コード生成はbuild_runnerとfloor_generatorを介して実行されます — コンパイラがDAO実装と管理データベースクラスを作成します。生のsqfliteとは異なり、Floorは型リフレクションを介してカラムをEntityフィールドに自動的にマッピングすることにより、ResultSetからDartオブジェクトへの手動変換の必要性を完全に排除します。
FloorはAndroid開発者にはRoomでおなじみのRepository + DAOパターンに従います。各テーブルは@Entityアノテーションが付いたDartクラスで表現され、SQLクエリは@daoアノテーションが付いたインターフェースにグループ化され、データベースは@Databaseが付いた抽象クラスで組み立てられます。このアプローチはデータモデルをクエリロジックから厳密に分離します。
Flutter Pulse(2023年)によると、ローカルデータベースを必要とするFlutterプロジェクトの28%でFloorが選択されています。選択する主な理由は、SQLへの親しみやすさ(新しいクエリ言語を学ぶ必要がない)とコンパイル時のクエリチェックです。さらに、FloorはカスタムDSLを持つより抽象的なORMと比較してデバッグが容易な読みやすいコードを生成するため、チームの新しい開発者にとっての参入障壁を低くします。
Floorは3つの層で構成されています:Entity(テーブルモデル)、DAO(クエリインターフェース)、Database(エントリポイント)。ジェネレータはフィールドマッピング用の_$_Entity実装とSQL実行用の_$_Daoを作成します。EntityまたはDAOが変更された場合、build_runnerを再起動するだけでコードが自動的に更新されます。スキーマバージョン間のマイグレーションのために、Floorはシーケンシャルなバージョン番号を使用し、ユーザーのデバイスでアプリを更新する際のデータ整合性を保証します。
Floorはプラットフォームビルドにはsqfliteパッケージを、デスクトップとウェブにはsqlite3を介してSQLiteを使用します。アプリの起動時に、FloorはSQLiteファイルを作成または開き、マイグレーションを適用し、クエリ実行のためのDAOメソッドを準備します。すべての操作はFutureとStreamを介して非同期に実行されます。
Floorでのコード生成は次のように機能します:パーサーがソースファイルからアノテーションを読み取り、モデルとクエリのAST(抽象構文木)を作成し、_$プレフィックスを持つDartファイルを生成します。生成されたコードにはResultSet → Entityマッパーとその逆が含まれます。
Floorは単一のアイソレートでSQLiteと連携します。すべてのクエリは非同期に実行されますが、同時書き込みはSQLiteレベルでロックされます。トランザクションには@transactionアノテーションが使用され、クエリグループの原子性とエラー時のロールバックを保証します。
FloorとDriftはどちらもSQLite上のORMですが、哲学が異なります。FloorはAndroidのRoomに近く、Driftは組み込みのStream APIとSQLファイルを介したクエリコンパイルを備えよりリアクティブです。どちらを選択するかは、チームの経験と必要なリアクティビティに依存します。
| 特性 | Floor | Drift |
|---|---|---|
| クエリタイプ | @Query内のSQL文字列 | Dartメソッド + sqlファイル |
| コード生成 | floor_generator(build_runner) | drift_dev(build_runner) |
| リアクティビティ | DAOからのStream | 組み込みStream API + 自動更新 |
| 複雑さ | 低い(使い慣れたSQL) | 中程度(独自DSL) |
| マイグレーション | 手動SQLスクリプト | 自動 + 手動 |
| 互換性 | Android、iOS、macOS | Android、iOS、Web、macOS、Linux |
Floorは、すでにSQLとAndroid Roomに精通しているチームにとっての選択肢です。開発者が手動でSQLクエリを書くことに慣れており、SQLiteの上に最小限のラッパーを求めている場合 — Floorは新しいDSLを学ばずに型付けを提供します。生成されたコードは読みやすく予測可能であるため、デバッグも容易です。
Driftはより強力なリアクティビティを提供し、より多くのプラットフォームをサポートします。アプリがUI更新にStreamを積極的に使用し、JOINやサブクエリを含む複雑なクエリを必要とする場合、またはウェブをターゲットにする場合 — Driftが適しています。ただし、独自のDSLを学ぶ必要があるため、参入障壁は高くなります。
Floorはアノテーションを中心に構築されています。以下は、タスクリストアプリのためのEntity、DAO、Databaseの完全な例です。build_runnerを実行した後、生成されたクラスはすぐに使用できます。
TaskEntityクラスは@Entityアノテーションでtaskテーブルにマッピングされます。@primaryKeyが付いたフィールドが主キーになります。TaskDaoインターフェースにはテーブル操作用のメソッドが含まれています — 各メソッドは@Query、@Insert、@Update、または@Deleteでアノテーションされています。
@entity
class TaskEntity {
@PrimaryKey(autoGenerate: true)
final int id;
final String title;
final bool isCompleted;
final int priority;
TaskEntity({this.id, required this.title,
this.isCompleted = false, this.priority = 0});
}
@dao
abstract class TaskDao {
@Query('SELECT * FROM TaskEntity ORDER BY priority DESC')
Future<List<TaskEntity>> getAllTasks();
@Insert
Future<int> insertTask(TaskEntity task);
@Update
Future<void> updateTask(TaskEntity task);
@Query('SELECT * FROM TaskEntity WHERE isCompleted = :status')
Stream<List<TaskEntity>> watchTasks(bool status);
}
@Databaseアノテーションが付いた抽象クラスがEntityとDAOを結び付けます。databaseBuilderメソッドがデータベースインスタンスを作成します。buildを呼び出した後、データベースの準備が整います:FloorがSQLiteファイルを開き、マイグレーションを適用し、作業用のDAOを返します。
@Database(version: 1, entities: [TaskEntity])
abstract class AppDatabase extends FloorDatabase {
TaskDao get taskDao;
}
// 使用
final database = await $FloorAppDatabase.databaseBuilder('app.db').build();
final taskDao = database.taskDao;
final tasks = await taskDao.getAllTasks();
FloorはDAOメソッドからのStreamの返却をサポートしています。テーブルに変更があると、Streamが新しいリストを発行します。これはFlutterのStreamBuilderと統合され — レコードが追加、変更、または削除されるとUIが自動的に更新されます。
@Query('SELECT * FROM TaskEntity ORDER BY priority DESC')
Stream<List<TaskEntity>> watchAllTasks();
// Flutterウィジェット内
StreamBuilder<List<TaskEntity>>(
stream: taskDao.watchAllTasks(),
builder: (context, snapshot) {
final tasks = snapshot.data ?? [];
return ListView.builder(
itemCount: tasks.length,
itemBuilder: (_, i) => TaskTile(tasks[i]),
);
},
)
Floorは@Databaseアノテーションのversionパラメータを介したデータベースのバージョン管理をサポートします。Entityが変更された場合(フィールドの追加や削除)、バージョンを上げてマイグレーションを追加する必要があります。マイグレーションはトランザクションを受け取り、ALTER TABLE SQLクエリを実行するDart関数です。
バージョン2でTaskEntityにdueDateフィールドを追加したとします。マイグレーションはALTER TABLESQLクエリを介して実行されます。マイグレーションが指定されていない場合、FloorはMigrationStrategyを呼び出し、そこでフォールバックを設定できます(例:データ損失を伴うテーブルの再作成)。
Floorは組み込みのモックフレームワークを提供していませんが、テストでデータベースを簡単に置き換えることができます。inMemoryDatabaseBuilderを作成すると — 本番環境と同じスキーマのインメモリSQLiteデータベースが作成されます。各テストの後、deleteDatabaseを介してデータをクリアし、テストシナリオを分離します。
FloorはDAOメソッドの@transactionアノテーションを介したトランザクションをサポートします。トランザクション内では、複数のクエリがエラー時のロールバック保証付きで順次実行されます。List
final migration1to2 = Migration(1, 2, (database) async {
await database.execute(
'ALTER TABLE TaskEntity ADD COLUMN dueDate TEXT'
);
});
final database = await $FloorAppDatabase.databaseBuilder('app.db')
.addMigrations([migration1to2])
.build();
よくある質問
sqfliteは手動でのSQLクエリ作成とResultSetからオブジェクトへのマッピングが必要です。Floorはこのコードを自動的に生成します:EntityとDAOを記述すると、型付けされたメソッドが使用可能なDartオブジェクトを返します。Floorはアノテーションを介してコンパイル時にSQLクエリもチェックします。
FloorにはRoomのようなリレーションシップ用の組み込みアノテーション(ForeignKey、@Relation)はありません。リレーションシップは@Query内の手動SQL JOINクエリを介して実装されます。複雑なリレーショナルスキーマの場合は、リレーションシップの組み込みサポートを備えたDriftを検討してください。
FloorではDatabaseBuilder作成時にcallbackを有効にできます — これにはsqflite.Databaseのインスタンスが渡され、ロガーをアタッチできます。代替として、devモードでスキーマとデータを可視化するためにfloor_doctorを使用してください。
Floorはsqfliteを使用しており、ウェブ環境では動作しません。ウェブの場合は、WASMを介したsqlite3を使用した別のビルドが必要です。現在のバージョンでは、Floorは公式にAndroid、iOS、macOSをサポートしています。ウェブの場合は、sqlite3アダプターと共にDriftを使用してください。
Floorには組み込みのキャッシュはありません — 各クエリはSQLiteに対して実行されます。繰り返しのクエリをキャッシュするには、インメモリキャッシュ(例:dart_cache)を備えたRepositoryレイヤーを使用してください。FloorはSQLiteで動作するコードを生成するだけで、その上にオーバーヘッドを追加しません。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。