Floor — ORM (Object-Relational Mapping) لـ Flutter، يوفر طبقة مكتوبة فوق SQLite. على عكس استعلامات SQLite الخام، يقوم Floor بتوليد فئات DAO من نماذج Dart المُعلَّمة. وفقًا لـ Pub.dev، 2024، يتم استخدام Floor في أكثر من 3500 مشروع Flutter وهو من بين أشهر ثلاث حلول ORM للتخزين المحلي للبيانات إلى جانب drift و hive.
أهم النقاط
Floor هي مكتبة ORM لـ Flutter و Dart مبنية على SQLite. تستخدم التعليقات التوضيحية لوصف الكيانات (Entity) وكائنات الوصول إلى البيانات (DAO) وقاعدة البيانات (Database). يتم توليد الكود عبر build_runner و floor_generator — حيث يقوم المترجم بإنشاء تطبيقات DAO وفئة إدارة قاعدة البيانات. على عكس sqflite الخام، يلغي Floor تمامًا الحاجة إلى التحويل اليدوي من ResultSet إلى كائنات Dart، عن طريق تعيين الأعمدة تلقائيًا إلى حقول Entity عبر انعكاس الأنواع.
يتبع Floor نمط Repository + DAO المألوف لمطوري Android من Room. يتم تمثيل كل جدول بفئة Dart مع التعليق التوضيحي @Entity، ويتم تجميع استعلامات SQL في واجهات مع التعليق التوضيحي @dao، ويتم تجميع قاعدة البيانات في فئة مجردة مع @Database. يفصل هذا النهج بدقة نموذج البيانات عن منطق الاستعلامات.
وفقًا لـ Flutter Pulse (2023)، يتم اختيار Floor في 28% من مشاريع Flutter التي تتطلب قاعدة بيانات محلية. الأسباب الرئيسية لاختياره هي الألفة مع SQL (لا حاجة لتعلم لغة استعلام جديدة) والتحقق من الاستعلامات في وقت الترجمة. بالإضافة إلى ذلك، يولد Floor كودًا قابلًا للقراءة يسهل تصحيحه مقارنة بـ ORM الأكثر تجريدًا مع DSL مخصص، مما يخفض حاجز الدخول للمطورين الجدد في الفريق.
يتكون Floor من ثلاث طبقات: Entity (نموذج الجدول)، DAO (واجهة الاستعلامات) و Database (نقطة الدخول). يقوم المولد بإنشاء تطبيقات _$_Entity لتعيين الحقول و _$_Dao لتنفيذ SQL. عندما يتغير Entity أو DAO، يكفي إعادة تشغيل build_runner — يتم تحديث الكود تلقائيًا. للترحيل بين إصدارات المخطط، يستخدم Floor أرقام إصدارات متسلسلة، مما يضمن سلامة البيانات عند تحديث التطبيق على أجهزة المستخدمين.
يستخدم Floor SQLite من خلال حزمة sqflite للإصدارات المنصية و sqlite3 لسطح المكتب والويب. عند بدء التطبيق، يقوم Floor بإنشاء أو فتح ملف SQLite، وتطبيق الترحيلات وإعداد طرق DAO لتنفيذ الاستعلامات. يتم تنفيذ جميع العمليات بشكل غير متزامن عبر Future و Stream.
يعمل توليد الكود في Floor على النحو التالي: يقرأ المحلل التعليقات التوضيحية من الملفات المصدر، وينشئ AST (شجرة بناء الجمل المجردة) للنماذج والاستعلامات، ثم يولد ملفات Dart بالبادئة _$. يتضمن الكود المولد مهام تعيين ResultSet → Entity والعكس.
يعمل Floor مع SQLite في isolate واحد. يتم تنفيذ جميع الاستعلامات بشكل غير متزامن، ولكن تتم قفل الكتابات المتزامنة على مستوى SQLite. للمعاملات، يتم استخدام التعليق التوضيحي @transaction الذي يضمن ذرية مجموعة الاستعلامات والتراجع عند الخطأ.
كل من Floor و Drift هما ORM فوق SQLite، لكنهما يختلفان في الفلسفة. Floor أقرب إلى Room من Android، بينما Drift أكثر تفاعلية مع API Stream مدمج وتجميع الاستعلامات عبر ملفات SQL. يعتمد الاختيار بينهما على خبرة الفريق والتفاعلية المطلوبة.
| الخاصية | Floor | Drift |
|---|---|---|
| نوع الاستعلامات | سلاسل SQL في @Query | طرق Dart + ملفات sql |
| توليد الكود | floor_generator (build_runner) | drift_dev (build_runner) |
| التفاعلية | Stream من DAO | API Stream مدمج + تحديث تلقائي |
| التعقيد | منخفض (SQL مألوف) | متوسط (DSL خاص) |
| الترحيل | نصوص SQL يدوية | تلقائي + يدوي |
| التوافق | Android، iOS، macOS | Android، iOS، Web، macOS، Linux |
Floor هو الخيار للفرق المألوفة بالفعل مع SQL و Android Room. إذا كان المطورون معتادين على كتابة استعلامات SQL يدويًا ويريدون غلافًا بسيطًا فوق SQLite — فإن Floor يوفر كتابة الأنواع دون تعلم DSL جديد. كما أنه أسهل في التصحيح لأن الكود المولد قابل للقراءة ومتوقع.
يوفر Drift تفاعلية أكثر قوة ويدعم منصات أكثر. إذا كان التطبيق يستخدم Stream بشكل نشط لتحديث واجهة المستخدم، أو يتطلب استعلامات معقدة مع JOIN والاستعلامات الفرعية، أو يستهدف الويب — فإن Drift أفضل. ومع ذلك، فإن حاجز الدخول أعلى بسبب الحاجة لتعلم DSL الخاص به.
يتم بناء Floor حول التعليقات التوضيحية. فيما يلي مثال كامل لـ Entity و DAO و Database لتطبيق قائمة المهام. بعد تشغيل build_runner، تكون الفئات المولدة جاهزة للاستخدام.
فئة TaskEntity مع التعليق التوضيحي @Entity يتم تعيينها إلى جدول المهام. حقل مع @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 إرجاع Stream من طرق DAO. عند أي تغييرات في الجدول، يطلق Stream قائمة جديدة. يتكامل هذا مع StreamBuilder في Flutter — يتم تحديث واجهة المستخدم تلقائيًا عند إضافة أو تعديل أو حذف السجلات.
@Query('SELECT * FROM TaskEntity ORDER BY priority DESC')
Stream<List<TaskEntity>> watchAllTasks();
// في Flutter widget
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 إصدار قاعدة البيانات عبر معامل version في التعليق التوضيحي @Database. عندما يتغير Entity (إضافة أو إزالة حقول)، تحتاج إلى زيادة الإصدار وإضافة ترحيل. الترحيل هو دالة Dart تستقبل معاملة وتنفذ استعلامات SQL ALTER TABLE.
لنفترض أنه في الإصدار 2 أضفنا حقل dueDate إلى TaskEntity. يتم تنفيذ الترحيل عبر استعلام SQL ALTER TABLE. إذا لم يتم تحديد ترحيل، يستدعي Floor MigrationStrategy حيث يمكنك تعيين fallback (على سبيل المثال، إعادة إنشاء الجدول مع فقدان البيانات).
لا يوفر Floor إطار عمل mock مدمج، ولكن يمكن استبدال قاعدة البيانات بسهولة في الاختبارات. أنشئ inMemoryDatabaseBuilder — ينشئ قاعدة بيانات SQLite في الذاكرة مطابقة في المخطط للإنتاج. بعد كل اختبار، امسح البيانات عبر deleteDatabase لعزل سيناريوهات الاختبار.
يدعم Floor المعاملات عبر التعليق التوضيحي @transaction على طرق DAO. داخل المعاملة، يتم تنفيذ استعلامات متعددة بشكل متسلسل مع ضمان التراجع عند الخطأ. الإدراج المجمع عبر @Insert مع معامل 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 ليس لديه تعليقات توضيحية مدمجة للعلاقات (ForeignKey، @Relation) مثل Room. يتم تنفيذ العلاقات عبر استعلامات SQL JOIN يدوية في @Query. للمخططات العلائقية المعقدة، فكر في Drift مع دعمه المدمج للعلاقات.
يسمح Floor بتمكين callback عند إنشاء DatabaseBuilder — يتم تمرير مثيل sqflite.Database إليه يمكنك ربط مسجل به. بدلاً من ذلك، استخدم floor_doctor لتصور المخطط والبيانات في وضع التطوير.
Floor يستخدم sqflite الذي لا يعمل في بيئة الويب. للويب، يلزم إصدار منفصل مع sqlite3 عبر WASM. في الإصدار الحالي، يدعم Floor رسميًا Android و iOS و macOS. للويب، استخدم Drift مع محول sqlite3.
Floor ليس لديه تخزين مؤقت مدمج — كل استعلام يتم تنفيذه على SQLite. لتخزين الاستعلامات المتكررة مؤقتًا، استخدم طبقة Repository مع ذاكرة تخزين مؤقت في الذاكرة (على سبيل المثال، dart_cache). Floor فقط يولد كودًا للعمل مع SQLite دون إضافة أي تحميل زائد فوقه.
الخلاصة
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.