Floor

Floor is "a type-safe, reactive, and lightweight SQLite abstraction library for Flutter applications" developed as a popular ORM library in the Flutter ecosystem. Inspired by Android's Room persistence library, it provides complex SQLite operations through a simple and intuitive API using an annotation-based approach. With automatic migrations, reactive streams, null safety support, and maximum utilization of modern Dart features, Floor enables the construction of robust data persistence layers for Flutter applications.

ORMFlutterDartSQLiteDatabaseReactive

GitHub Overview

pinchbv/floor

The typesafe, reactive, and lightweight SQLite abstraction for your Flutter applications

Stars1,016
Watchers14
Forks207
Created:January 20, 2019
Language:Dart
License:Apache License 2.0

Topics

dartfluttersqlite

Star History

pinchbv/floor Star History
Data as of: 7/17/2025, 06:57 AM

Library

Floor

Overview

Floor is "a type-safe, reactive, and lightweight SQLite abstraction library for Flutter applications" developed as a popular ORM library in the Flutter ecosystem. Inspired by Android's Room persistence library, it provides complex SQLite operations through a simple and intuitive API using an annotation-based approach. With automatic migrations, reactive streams, null safety support, and maximum utilization of modern Dart features, Floor enables the construction of robust data persistence layers for Flutter applications.

Details

Floor 2025 edition boasts mature APIs and excellent stability as the definitive solution for Flutter SQLite database operations. Through annotation-driven code generation, it eliminates raw SQL and achieves clean, maintainable code. The three-layer architecture of Entity, DAO, and Database provides simple design, making complex data models intuitively manageable. Deep integration with Dart's Stream API provides standard support for reactive data updates needed for real-time UIs. Its lightweight design is simpler than Drift and optimized for lean applications.

Key Features

  • Annotation-Based Approach: Replaces raw SQL with clean annotation-driven code
  • Automatic Migrations: Automatic database schema updates as apps evolve
  • Reactive Streams: Real-time UI support through Dart's Stream API
  • Lightweight Design: Lighter than Drift, perfect for simple applications
  • Modern Dart Support: Complete support for null safety and latest Dart features
  • Room-Style API: Familiar API design for Android developers

Pros and Cons

Pros

  • Annotation-driven approach allows SQL error detection at compile time
  • Deep integration with Dart ecosystem and reactive programming support
  • Low learning cost for developers familiar with Android Room
  • Lightweight and simple, avoids complexity compared to Drift
  • Improved maintainability and reduced boilerplate code through automatic code generation
  • Significant reduction in runtime errors through null safety support

Cons

  • Requires understanding of SQL and SQLite, presenting learning barriers for beginners
  • Complex queries may require writing raw SQL in some cases
  • Dependence on generated code increases build time
  • Limited advanced features compared to Drift or Moor
  • Limited relationship functionality, unsuitable for complex ER designs
  • Limited web and desktop support, primarily mobile-focused

Reference Pages

Code Examples

Setup

# pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  floor: ^1.4.2

dev_dependencies:
  floor_generator: ^1.4.2
  build_runner: ^2.4.6
// Entity definition
import 'package:floor/floor.dart';

@entity
class Person {
  @primaryKey
  final int? id;

  final String firstName;
  final String lastName;

  @ColumnInfo(name: 'custom_name')
  final String? nickname;

  Person(this.id, this.firstName, this.lastName, this.nickname);
}

Basic Usage

// DAO (Data Access Object) definition
import 'package:floor/floor.dart';

@dao
abstract class PersonDao {
  @Query('SELECT * FROM Person')
  Future<List<Person>> findAllPersons();

  @Query('SELECT * FROM Person WHERE id = :id')
  Stream<Person?> findPersonById(int id);

  @Query('SELECT * FROM Person WHERE firstName LIKE :name')
  Future<List<Person>> findPersonsByFirstName(String name);

  @insert
  Future<void> insertPerson(Person person);

  @update
  Future<void> updatePerson(Person person);

  @delete
  Future<void> deletePerson(Person person);
}

Query Execution

// Database definition
import 'dart:async';
import 'package:floor/floor.dart';
import 'package:sqflite/sqflite.dart' as sqflite;

part 'app_database.g.dart'; // Code-generated file

@Database(version: 1, entities: [Person])
abstract class AppDatabase extends FloorDatabase {
  PersonDao get personDao;
}

// Database initialization
class DatabaseHelper {
  static late AppDatabase database;

  static Future<void> initDatabase() async {
    database = await $FloorAppDatabase.databaseBuilder('app_database.db').build();
  }

  static AppDatabase get instance => database;
}

// Database operation execution
class PersonRepository {
  final PersonDao _personDao = DatabaseHelper.instance.personDao;

  Future<List<Person>> getAllPersons() async {
    return await _personDao.findAllPersons();
  }

  Stream<Person?> getPersonById(int id) {
    return _personDao.findPersonById(id);
  }

  Future<void> addPerson(String firstName, String lastName, {String? nickname}) async {
    final person = Person(null, firstName, lastName, nickname);
    await _personDao.insertPerson(person);
  }

  Future<void> updatePersonNickname(Person person, String newNickname) async {
    final updatedPerson = Person(person.id, person.firstName, person.lastName, newNickname);
    await _personDao.updatePerson(updatedPerson);
  }

  Future<void> removePerson(Person person) async {
    await _personDao.deletePerson(person);
  }
}

Data Operations

// Practical usage example
class PersonScreen extends StatefulWidget {
  @override
  _PersonScreenState createState() => _PersonScreenState();
}

class _PersonScreenState extends State<PersonScreen> {
  final PersonRepository _repository = PersonRepository();
  List<Person> _persons = [];

  @override
  void initState() {
    super.initState();
    _loadPersons();
  }

  Future<void> _loadPersons() async {
    final persons = await _repository.getAllPersons();
    setState(() {
      _persons = persons;
    });
  }

  Future<void> _addRandomPerson() async {
    await _repository.addPerson(
      'John${DateTime.now().millisecond}',
      'Doe',
      nickname: 'JD',
    );
    _loadPersons(); // Update list
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Floor Demo')),
      body: ListView.builder(
        itemCount: _persons.length,
        itemBuilder: (context, index) {
          final person = _persons[index];
          return ListTile(
            title: Text('${person.firstName} ${person.lastName}'),
            subtitle: Text(person.nickname ?? 'No nickname'),
            trailing: IconButton(
              icon: Icon(Icons.delete),
              onPressed: () async {
                await _repository.removePerson(person);
                _loadPersons();
              },
            ),
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _addRandomPerson,
        child: Icon(Icons.add),
      ),
    );
  }
}

Configuration and Customization

// Advanced Entity definition
@Entity(tableName: 'users', indices: [Index(value: ['first_name', 'last_name'])])
class User {
  @PrimaryKey(autoGenerate: true)
  final int? id;

  @ColumnInfo(name: 'first_name')
  final String firstName;

  @ColumnInfo(name: 'last_name')
  final String lastName;

  final String email;

  @ColumnInfo(name: 'created_at')
  final DateTime createdAt;

  @ignore
  String get fullName => '$firstName $lastName';

  User(this.id, this.firstName, this.lastName, this.email, this.createdAt);
}

// Complex query examples
@dao
abstract class UserDao {
  @Query('SELECT * FROM users WHERE first_name LIKE :pattern OR last_name LIKE :pattern ORDER BY created_at DESC')
  Future<List<User>> searchUsersByName(String pattern);

  @Query('SELECT COUNT(*) FROM users WHERE created_at > :date')
  Future<int?> countUsersCreatedAfter(DateTime date);

  @Query('SELECT * FROM users WHERE email = :email LIMIT 1')
  Future<User?> findUserByEmail(String email);

  @Insert(onConflict: OnConflictStrategy.replace)
  Future<void> insertOrUpdateUser(User user);

  @transaction
  Future<void> insertMultipleUsers(List<User> users) async {
    for (final user in users) {
      await insertOrUpdateUser(user);
    }
  }
}

Error Handling

// Error handling and transactions
class UserService {
  final UserDao _userDao = DatabaseHelper.instance.userDao;

  Future<Result<User>> createUser(String firstName, String lastName, String email) async {
    try {
      // Check for email duplication
      final existingUser = await _userDao.findUserByEmail(email);
      if (existingUser != null) {
        return Result.error('Email already exists');
      }

      final user = User(
        null,
        firstName,
        lastName,
        email,
        DateTime.now(),
      );

      await _userDao.insertOrUpdateUser(user);
      return Result.success(user);
    } catch (e) {
      return Result.error('Failed to create user: $e');
    }
  }

  Future<Result<void>> bulkInsertUsers(List<Map<String, String>> userData) async {
    try {
      final users = userData.map((data) => User(
        null,
        data['firstName']!,
        data['lastName']!,
        data['email']!,
        DateTime.now(),
      )).toList();

      await _userDao.insertMultipleUsers(users);
      return Result.success(null);
    } catch (e) {
      return Result.error('Failed to insert users: $e');
    }
  }
}

// Result class
sealed class Result<T> {
  const Result();
  
  factory Result.success(T data) = Success<T>;
  factory Result.error(String message) = Error<T>;
}

class Success<T> extends Result<T> {
  final T data;
  const Success(this.data);
}

class Error<T> extends Result<T> {
  final String message;
  const Error(this.message);
}