SQLite Handler v2.0.0
A modern, lightweight and feature-rich SQLite database handler for Flutter with support for migrations, encryption, and advanced query building.
Features
- 🚀 Modern Flutter Support: Compatible with Flutter 3.10+ and Dart 3.0+
- 🗄️ Advanced Query Building: Fluent API for complex SQL queries
- 🔐 Built-in Encryption: Secure data storage with encryption utilities
- 📊 Rich Data Types: Support for all SQLite data types including JSON, UUID, and geometric types
- 🔄 Laravel-like Migrations: Easy database schema management with rollback support
- 🏗️ Schema Builder: Fluent interface for table creation
- 🔗 Relationships: Support for hasOne, hasMany, belongsTo, and many-to-many relationships
- ⚡ Performance: Optimized for high-performance database operations
- 🛡️ Error Handling: Comprehensive error handling with custom exceptions
- 📱 Cross-Platform: Works on Android, iOS, Windows, macOS, Linux, and Web
- 🛠️ Migration Generator: CLI tools for creating and managing migrations
Getting Started
Installation
Add this to your package's pubspec.yaml file:
dependencies:
sqlite_handler: ^2.0.0
Basic Usage
import 'package:sqlite_handler/sqlite_handler.dart';
// Define your model
class User extends Model {
String? name;
String? email;
DateTime? createdAt;
User() : super('users');
@override
Map<String, Object?> toMap() {
return {
'name': name,
'email': email,
'created_at': createdAt?.toIso8601String(),
};
}
@override
User fromMap(Map<dynamic, dynamic> map) {
return User()
..name = map['name'] as String?
..email = map['email'] as String?
..createdAt = map['created_at'] != null
? DateTime.parse(map['created_at'] as String)
: null;
}
}
// Use the model
void main() async {
final user = User()
..name = 'John Doe'
..email = 'john@example.com'
..createdAt = DateTime.now();
// Insert user
final savedUser = await user.insert();
// Find user by ID
final foundUser = await User().find(savedUser.id!);
// Query users
final users = await User()
.where('email', value: 'john@example.com')
.orderBy(column: 'created_at', order: DatabaseOrder.descending)
.all();
}
Migrations
The package includes a powerful Laravel-like migration system that makes database schema management easy and reliable.
Creating Migrations
Using the CLI
# Create a new migration
dart run bin/migrate.dart make:migration create_users_table
# Create a table migration
dart run bin/migrate.dart make:table users
# Create a migration for adding columns
dart run bin/migrate.dart make:add_columns users name:text:not_null email:text:not_null:unique age:integer:not_null:18
Manual Creation
import 'package:sqlite_handler/sqlite_handler.dart';
class CreateUsersTable extends Migration {
@override
String get tableName => 'users';
@override
String get description => 'Create users table with basic authentication fields';
@override
Future<void> up(Database db) async {
await db.execute('''
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
password_hash TEXT NOT NULL,
email_verified_at DATETIME,
remember_token TEXT,
is_active INTEGER DEFAULT 1,
created_at DATETIME,
updated_at DATETIME
)
''');
// Create indexes for better performance
await db.execute('CREATE INDEX idx_users_email ON users(email)');
await db.execute('CREATE INDEX idx_users_active ON users(is_active)');
}
@override
Future<void> down(Database db) async {
await db.execute('DROP TABLE users');
}
}
Running Migrations
Using the CLI
# Run all pending migrations
dart run bin/migrate.dart migrate
# Show migration status
dart run bin/migrate.dart status
# Rollback the last batch
dart run bin/migrate.dart rollback
# Rollback to a specific migration
dart run bin/migrate.dart rollback:to 20240101120000_create_users_table
# Reset all migrations
dart run bin/migrate.dart reset
# Refresh (reset and re-run all migrations)
dart run bin/migrate.dart refresh
Programmatically
import 'package:sqlite_handler/sqlite_handler.dart';
void main() async {
final runner = MigrationRunner();
// Add your migrations
runner.addMigration(CreateUsersTable());
runner.addMigration(CreatePostsTable());
// Run migrations
final executed = await runner.migrate();
print('Executed ${executed.length} migrations');
// Show status
final statuses = await runner.status();
for (final status in statuses) {
print('${status['migration']}: ${status['hasRun'] ? '✓' : '○'}');
}
// Rollback if needed
final rolledBack = await runner.rollback();
print('Rolled back ${rolledBack.length} migrations');
await runner.close();
}
Migration Dependencies
Migrations can depend on other migrations to ensure proper execution order:
class CreatePostsTable extends Migration {
@override
String get tableName => 'posts';
@override
List<String> get dependencies => ['20240101120000_CreateUsersTable'];
@override
Future<void> up(Database db) async {
await db.execute('''
CREATE TABLE posts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
title TEXT NOT NULL,
content TEXT,
FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
)
''');
}
@override
Future<void> down(Database db) async {
await db.execute('DROP TABLE posts');
}
}
Advanced Migration Features
Schema Builder Integration
import 'package:sqlite_handler/sqlite_handler.dart';
class CreateProductsTable extends Migration {
@override
String get tableName => 'products';
@override
Future<void> up(Database db) async {
final schema = Schema()
..id()
..text('name').notNull()
..text('description')
..decimal('price', precision: 10, scale: 2).notNull()
..integer('stock').defaultValue(0)
..boolean('is_active').defaultValue(true)
..datetime('created_at')
..datetime('updated_at')
..timestamps();
final createTableQuery = schema.createTable('products');
await db.execute(createTableQuery);
}
@override
Future<void> down(Database db) async {
await db.execute('DROP TABLE products');
}
}
Data Seeding
class SeedUsersTable extends Migration {
@override
String get tableName => 'users';
@override
Future<void> up(Database db) async {
// Insert default users
await db.insert('users', {
'name': 'Admin User',
'email': 'admin@example.com',
'password_hash': 'hashed_password_here',
'is_active': 1,
'created_at': DateTime.now().toIso8601String(),
'updated_at': DateTime.now().toIso8601String(),
});
await db.insert('users', {
'name': 'Demo User',
'email': 'demo@example.com',
'password_hash': 'hashed_password_here',
'is_active': 1,
'created_at': DateTime.now().toIso8601String(),
'updated_at': DateTime.now().toIso8601String(),
});
}
@override
Future<void> down(Database db) async {
await db.delete('users', where: 'email IN (?, ?)', whereArgs: ['admin@example.com', 'demo@example.com']);
}
}
Migration Generator
The package includes a powerful migration generator that can create various types of migrations:
import 'package:sqlite_handler/sqlite_handler.dart';
void main() async {
final generator = MigrationGenerator();
// Create a basic migration
await generator.create('create_orders_table');
// Create a table with columns
await generator.createTable('products', columns: [
{'name': 'name', 'type': 'TEXT', 'nullable': false},
{'name': 'price', 'type': 'DECIMAL(10,2)', 'nullable': false},
{'name': 'stock', 'type': 'INTEGER', 'default': 0},
]);
// Add columns to existing table
await generator.addColumns('users', [
{'name': 'phone', 'type': 'TEXT'},
{'name': 'address', 'type': 'TEXT'},
]);
// Create indexes
await generator.createIndex('users', ['email'], unique: true);
await generator.createIndex('posts', ['user_id', 'status']);
// Rename table
await generator.renameTable('old_table_name', 'new_table_name');
}
Advanced Features
Schema Building
import 'package:sqlite_handler/sqlite_handler.dart';
class UserMigration extends Migrations {
@override
Future<List<String>> get getTables async {
final schema = Schema()
..id()
..text('name').notNull()
..email('email').notNull().unique()
..datetime('created_at')
..datetime('updated_at')
..timestamps();
return [schema.createTable('users')];
}
}
Complex Queries
// Advanced query building
final users = await User()
.select(['id', 'name', 'email'])
.where('age', value: 18, operator: '>=')
.whereIn('status', ['active', 'pending'])
.whereNotNull('email')
.orderBy(column: 'created_at', order: DatabaseOrder.descending)
.limit(10, 0)
.all();
// Aggregations
final userCount = await User().count();
final maxAge = await User().max('age');
final avgAge = await User().avg('age');
final totalScore = await User().sum('score');
Relationships
// One-to-Many relationship
class Post extends Model {
Post() : super('posts');
// Get posts with user information
Future<List<Post>> withUser() async {
return await belongsTo('users', 'user_id').all();
}
}
// Many-to-Many relationship
class User extends Model {
User() : super('users');
// Get users with their roles
Future<List<User>> withRoles() async {
return await belongsToMany(
'roles',
'user_roles',
tableId: 'user_id',
relatedId: 'role_id',
).all();
}
}
Transactions
// Execute operations within a transaction
await User().transaction((txn) async {
final user1 = User()..name = 'User 1';
final user2 = User()..name = 'User 2';
await user1.insert();
await user2.insert();
return 'Transaction completed';
});
Encryption
import 'package:sqlite_handler/sqlite_handler.dart';
final encryption = Encryption();
// Hash passwords
final passwordData = encryption.hashPassword('myPassword123');
final isMatch = encryption.verifyPassword(
'myPassword123',
passwordData['hash']!,
passwordData['salt']!,
int.parse(passwordData['iterations']!),
int.parse(passwordData['keyLength']!),
);
// Encrypt sensitive data
final encrypted = encryption.encrypt('sensitive data', 'secretKey');
final decrypted = encryption.decrypt(encrypted, 'secretKey');
// Generate secure tokens
final token = encryption.generateToken();
final uuid = encryption.generateUuid();
Data Types
The package supports a wide range of SQLite data types:
Basic Types
integer()- Integer numbersreal()- Floating point numberstext()- Text datablob()- Binary databoolean()- Boolean values (stored as integers)
Specialized Types
email()- Email addresses (VARCHAR(255))url()- URLs (VARCHAR(2048))phone()- Phone numbers (VARCHAR(20))hash()- Hash values (VARCHAR(64))uuid()- UUID stringsjson()- JSON datacurrency()- Currency amounts (DECIMAL(10,2))percentage()- Percentage values (DECIMAL(5,2))
Geometric Types
coordinates()- Geographic coordinatespoint()- Point dataline()- Line datapolygon()- Polygon datacircle()- Circle datarectangle()- Rectangle data
API Reference
Model Methods
insert()- Insert a new recordupdate(id)- Update an existing recorddelete(id)- Delete a record by IDfind(id)- Find a record by IDall()- Get all recordsfirst()- Get the first recordlast()- Get the last recordcount()- Count recordsexists()- Check if record existssave()- Insert or update based on IDdestroy()- Delete the current recordrefresh()- Refresh from databasecopy()- Create a copy without ID
Query Builder Methods
where(column, value, operator)- Add WHERE conditionorWhere(column, value, operator)- Add OR WHERE conditionwhereIn(column, values)- Add WHERE IN conditionwhereNotIn(column, values)- Add WHERE NOT IN conditionwhereNull(column)- Add WHERE NULL conditionwhereNotNull(column)- Add WHERE NOT NULL conditionwhereLike(column, pattern)- Add WHERE LIKE conditionwhereBetween(column, start, end)- Add WHERE BETWEEN conditionorderBy(column, order)- Add ORDER BY clausegroupBy(column)- Add GROUP BY clausehaving(condition)- Add HAVING clauselimit(count, offset)- Add LIMIT and OFFSETdistinct()- Add DISTINCT modifierselect(columns)- Select specific columns
Schema Builder Methods
id()- Primary key columninteger(name)- Integer columnreal(name)- Real columntext(name)- Text columnblob(name)- Blob columnboolean(name)- Boolean columndatetime(name)- Datetime columndecimal(name, precision, scale)- Decimal columnvarchar(name, length)- Varchar columntimestamps()- Add created_at and updated_atsoftDeletes()- Add deleted_at columnauditTrail()- Add created_by and updated_by
Constraint Methods
notNull()- NOT NULL constraintunique()- UNIQUE constraintprimaryKey()- PRIMARY KEY constraintautoIncrement()- AUTOINCREMENT modifierdefaultValue(value)- DEFAULT valuecheck(condition)- CHECK constraintforeignKey(table, column)- FOREIGN KEY constraintindexed()- INDEX hint
Migration Methods
up(Database db)- Execute the migrationdown(Database db)- Rollback the migrationhasRun(Database db)- Check if migration has been runmarkAsRun(Database db)- Mark migration as executedmarkAsRolledBack(Database db)- Mark migration as rolled backcanRun(Database db)- Check if migration can rungetStatus(Database db)- Get migration status
Migration Runner Methods
addMigration(Migration)- Add a migration to the runneraddMigrations(List<Migration>)- Add multiple migrationsmigrate()- Run all pending migrationsrollback()- Rollback the last batchrollbackTo(String)- Rollback to a specific migrationreset()- Rollback all migrationsrefresh()- Reset and re-run all migrationsstatus()- Get migration statusgetPendingMigrations()- Get pending migrationsgetRanMigrations()- Get executed migrations
Migration Generator Methods
create(String name)- Create a new migrationcreateTable(String tableName)- Create a table migrationaddColumns(String tableName, List columns)- Create add columns migrationdropColumns(String tableName, List columns)- Create drop columns migrationrenameTable(String oldName, String newName)- Create rename table migrationcreateIndex(String tableName, List columns, bool unique)- Create index migration
Error Handling
The package provides comprehensive error handling:
try {
final user = await User().find(999);
if (user == null) {
print('User not found');
}
} on DatabaseException catch (e) {
print('Database error: ${e.message}');
} catch (e) {
print('Unexpected error: $e');
}
Performance Tips
- Use Indexes: Add indexes on frequently queried columns
- Limit Results: Use
limit()to avoid loading large datasets - Select Specific Columns: Use
select()instead ofSELECT * - Batch Operations: Use transactions for multiple operations
- Avoid N+1 Queries: Use relationships efficiently
- Migration Batching: Group related migrations together
Migration Guide from v0.0.4
Breaking Changes
- Enum Names:
SqlTypes→SqliteDataType,DatabaseOredrs→DatabaseOrder - Method Names:
order()→orderBy() - SDK Requirements: Flutter 3.10+ and Dart 3.0+
Update Steps
-
Update your
pubspec.yaml:dependencies: sqlite_handler: ^2.0.0 -
Update enum imports:
// Old import 'package:sqlite_handler/core/enums/sqlite_data_type.dart'; // New import 'package:sqlite_handler/sqlite_handler.dart'; -
Update enum usage:
// Old DatabaseOredrs.asc // New DatabaseOrder.ascending -
Update method calls:
// Old .order(column: 'name', order: DatabaseOredrs.asc) // New .orderBy(column: 'name', order: DatabaseOrder.ascending) -
Review error handling:
// Old try { // operations } catch (e) { // generic error handling } // New try { // operations } on DatabaseException catch (e) { // specific database error handling }
Benefits of Upgrading
- Better Performance: Improved query execution and memory management
- Enhanced Security: Built-in encryption and better security features
- Modern API: Fluent API design with better developer experience
- Type Safety: Enhanced type safety throughout the codebase
- Future Proof: Support for latest Flutter and Dart versions
- Rich Features: Advanced query building, relationships, and schema management
- Migration System: Laravel-like migration system with rollback support
Contributing
We welcome contributions! Please see our Contributing Guide for details.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Support
Changelog
See CHANGELOG.md for a detailed list of changes.