Working with SQLite Database
1. Using sqflite Package
| Property | Description | Example |
|---|---|---|
| openDatabase | Opens or creates a SQLite database file | openDatabase(path, version: 1, onCreate: onCreate) |
| query | Executes a SELECT query on the database | await db.query('users', where: 'id = ?', whereArgs: [1]) |
| insert | Inserts a new row into the database | await db.insert('users', {'name': 'John', 'age': 30}) |
| update | Updates existing rows in the database | await db.update('users', {'age': 31}, where: 'id = ?', whereArgs: [1]) |
| delete | Deletes rows from the database | await db.delete('users', where: 'id = ?', whereArgs: [1]) |
Example: Initialize and Open Database
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
Future<Database> initDatabase() async {
final dbPath = await getDatabasesPath();
final path = join(dbPath, 'my_database.db');
return await openDatabase(
path,
version: 1,
onCreate: (db, version) {
db.execute('''
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
age INTEGER
)
''');
},
);
}
Example: Insert Data
Future<void> insertUser(Database db, String name, int age) async {
await db.insert('users', {
'name': name,
'age': age,
});
}
Example: Query Data
Future<List<Map<String, dynamic>>> getUsers(Database db) async {
return await db.query('users');
}
sqflite and path to your pubspec.yaml dependencies.
2. Opening Database
| Property | Description | Example |
|---|---|---|
| openDatabase() | Opens or creates a SQLite database file | openDatabase('my_database.db'); |
| path | File path to the database file | '/data/user/0/com.example.myapp/databases/mydb.db' |
| version | Database version for migration handling | 1 |
| onCreate | Callback when database is created | (Database db, int version) async { ... } |
| onUpgrade | Callback when database version is upgraded | (Database db, int oldVersion, int newVersion) async { ... } |
Example: Opening a Database
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
Future<Database> openDatabase() async {
final dbPath = await getDatabasesPath();
final path = join(dbPath, 'my_database.db');
return await openDatabase(
path,
version: 1,
onCreate: (db, version) async {
await db.execute('''
CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL
)
''');
},
);
}
sqflite package is the most popular choice for SQLite in Flutter.
3. Creating Database Tables
Example: Creating a Users Table
import 'package:sqflite/sqflite.dart';
Future<void> createUsersTable(Database db) async {
await db.execute("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT UNIQUE)");
}
| Property | Type | Description | Notes |
|---|---|---|---|
| id | INTEGER PRIMARY KEY |
Unique identifier for each user record | Auto-incremented if not specified |
| name | TEXT |
User's full name | Required field |
TEXT UNIQUE |
User's email address | Must be unique across records |
UNIQUE constraint ensures no two users can have the same email address.
Example: Creating a Products Table with Foreign Key
Future<void> createProductsTable(Database db) async {
await db.execute("CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT, price REAL, category_id INTEGER, FOREIGN KEY(category_id) REFERENCES categories(id))");
}
| Property | Type | Description | Notes |
|---|---|---|---|
| id | INTEGER PRIMARY KEY |
Unique identifier for each product | Auto-incremented if not specified |
| name | TEXT |
Name of the product | Required field |
| price | REAL |
Price of the product in USD | Decimal value |
| category_id | INTEGER REFERENCES categories(id) |
Foreign key to the categories table | Ensures referential integrity |
4. Inserting Data
Inserting data into a SQLite database in Flutter is typically done using the insert method provided by the sqflite package. This operation adds a new row to a specified table.
| Parameter | Type | Description |
|---|---|---|
| table | String |
Name of the table where data will be inserted. |
| values | Map<String, dynamic> |
Map containing column names as keys and their corresponding values. |
| conflictAlgorithm | ConflictAlgorithm |
Specifies how to handle conflicts (e.g., ConflictAlgorithm.replace). |
Example: Inserting a new record
import 'package:sqflite/sqflite.dart';
Future<void> insertUser(int id, String name, String email) async {
final db = await openDatabase(
'my_database.db',
version: 1,
);
await db.insert(
'users',
{
'id': id,
'name': name,
'email': email,
},
);
}
insert method returns the row ID of the newly inserted record. If you need to use this ID later, store the result in a variable.
Example: Inserting with conflict resolution
await db.insert(
'users',
{
'id': 1,
'name': 'John Doe',
'email': 'john@example.com',
},
conflictAlgorithm: ConflictAlgorithm.replace,
);
5. Querying Data
Example: Basic SELECT Query
import 'package:sqflite/sqflite.dart';
Future<void> queryData() async {
var db = await openDatabase('my_database.db');
var results = await db.query('users');
for (var row in results) {
print('ID: ${row['id']}, Name: ${row['name']}');
}
}
| Method | Description | Parameters | Return Type |
|---|---|---|---|
| query | Executes a SELECT query on the database | tableName, columns, where, whereArgs |
Future<List<Map<String, dynamic>>> |
| rawQuery | Executes a raw SQL SELECT query | sql, whereArgs |
Future<List<Map<String, dynamic>>> |
Example: Query with WHERE Clause
Future<void> queryWithCondition() async {
var db = await openDatabase('my_database.db');
var results = await db.query(
'users',
where: 'age > ?',
whereArgs: [18],
);
print('Adult users: ${results.length}');
}
Example: Query with ORDER BY and LIMIT
Future<void> queryOrdered() async {
var db = await openDatabase('my_database.db');
var results = await db.query(
'users',
orderBy: 'name ASC',
limit: 5,
);
for (var row in results) {
print('Name: ${row['name']}');
}
}
6. Updating Data
Updating data in SQLite with Flutter involves using the update method provided by the sqflite package. This operation modifies existing records in a database table based on specified conditions.
| Method | Description | Parameters | Return Type |
|---|---|---|---|
| update | Updates records in a table based on a WHERE clause | table, values, where, whereArgs |
Future<int> |
Example: Updating a User's Email
import 'package:sqflite/sqflite.dart';
// Assume a table named 'users' with columns: id, name, email
Future<int> updateUserEmail(int userId, String newEmail) async {
final db = await openDatabase(path);
return await db.update(
'users',
{'email': newEmail},
where: 'id = ?',
whereArgs: [userId],
);
}
update method returns the number of affected rows, which can be used to verify if the update was successful.
Example: Updating Multiple Fields
Future<int> updateUserInfo(int userId, String newName, String newEmail) async {
final db = await openDatabase(path);
return await db.update(
'users',
{
'name': newName,
'email': newEmail,
},
where: 'id = ?',
whereArgs: [userId],
);
}
where and whereArgs to avoid updating all records in the table.
Key Points for Updating Data
- The
updatemethod modifies existing records in a table. - Use
whereandwhereArgsto target specific records. - The method returns the number of affected rows.
- Updating without a WHERE clause affects all records in the table.
7. Deleting Data
Deleting data in SQLite with Flutter is performed using the delete method on a Database object. This operation removes rows that match a specified condition.
| Method | Description | Parameters | Return Type |
|---|---|---|---|
| delete | Deletes rows from a table based on a WHERE condition. | table (string), where (string), whereArgs (list) |
int (number of rows deleted) |
Example: Delete a Single Row
import 'package:sqflite/sqflite.dart';
Future<int> deleteSingleUser(int userId) async {
final db = await openDatabase('my_database.db');
final deletedRows = await db.delete(
'users',
where: 'id = ?',
whereArgs: [userId],
);
return deletedRows;
}
Example: Delete All Rows
import 'package:sqflite/sqflite.dart';
Future<int> deleteAllUsers() async {
final db = await openDatabase('my_database.db');
final deletedRows = await db.delete('users');
return deletedRows;
}
where clause is provided, all rows in the table will be deleted.
Key Points
- The
deletemethod returns the number of affected rows. - Use
whereandwhereArgsto safely delete specific records. - Be cautious when deleting all rows without a condition.
8. Using Transactions
Example: Basic Transaction
import 'package:sqflite/sqflite.dart';
Future<void> useTransaction() async {
var db = await openDatabase('my_database.db');
await db.transaction((txn) async {
await txn.insert('users', {
'name': 'John Doe',
'email': 'john@example.com',
});
await txn.update('users', {
'name': 'Jane Doe',
}, where: 'name = ?', whereArgs: ['John Doe']);
});
}
| Method | Description | Notes |
|---|---|---|
| db.transaction() | Executes a series of operations as a single atomic unit | Rolls back if any operation fails |
9. Using Raw SQL Queries
When working with SQLite in Flutter, you can execute raw SQL queries using the rawQuery and rawUpdate methods provided by the database object. These methods allow you to run SQL statements directly without using the ORM-style APIs.
| Method | Description | Return Type |
|---|---|---|
| rawQuery | Executes a SELECT query and returns a list of maps. | List<Map<String, dynamic>> |
| rawUpdate | Executes an UPDATE, INSERT, or DELETE query and returns the number of affected rows. | int |
Example: Executing a Raw SQL Query
import 'package:sqflite/sqflite.dart';
Future<List<Map<String, dynamic>>> fetchUsers() async {
final db = await openDatabase('my_database.db');
final result = await db.rawQuery('SELECT * FROM users');
return result;
}
Example: Executing a Raw SQL Update
import 'package:sqflite/sqflite.dart';
Future<int> updateUserAge(int userId, int newAge) async {
final db = await openDatabase('my_database.db');
final result = await db.rawUpdate(
'UPDATE users SET age = ? WHERE id = ?',
[newAge, userId]
);
return result;
}
rawQuery and rawUpdate methods support parameterized queries using placeholders like ?.
10. Managing Database Migrations
| Migration Type | Description | Implementation | Notes |
|---|---|---|---|
| Version-Based | Database schema changes tracked by version numbers | sqflite with onUpgrade callback |
Recommended for production apps |
| Schema Hash | Migration based on schema hash comparison | Custom logic to compute and compare schema hashes | More robust than version tracking |
| Manual | Developer-controlled migration steps | Direct SQL statements in onUpgrade |
Full control but more error-prone |
Example: Version-Based Migration
import 'package:sqflite/sqflite.dart';
Future<void> migrateDatabase(Database db, int oldVersion, int newVersion) async {
if (oldVersion < 2) {
await db.execute("CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)");
}
if (oldVersion < 3) {
await db.execute("ALTER TABLE users ADD COLUMN email TEXT");
}
}
Migration Best Practices
- Always test migrations on a copy of production data
- Use version numbers to track schema changes
- Implement
onDowngradefor rollback scenarios - Avoid destructive operations in migrations
11. Implementing Repository Pattern with SQLite
Example: Basic Repository Implementation
// Define a model
class User {
final int id;
final String name;
final String email;
User({required this.id, required this.name, required this.email});
}
// Define a repository interface
abstract class UserRepository {
Future<List<User>> getAllUsers();
Future<User?> getUserById(int id);
Future<void> insertUser(User user);
Future<void> updateUser(User user);
Future<void> deleteUser(int id);
}
// Implement the repository with SQLite
class UserRepositoryImpl implements UserRepository {
late Database _db;
@override
Future<void> init() async {
final dbPath = await getDatabasesPath();
_db = await openDatabase(
'$dbPath/users.db',
version: 1,
onCreate: (db, version) {
db.execute('''
CREATE TABLE users (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
email TEXT NOT NULL
)
''');
},
);
}
@override
Future<List<User>> getAllUsers() async {
final List<Map<String, dynamic>> maps = await _db.query('users');
return List.generate(maps.length, (i) {
return User(
id: maps[i]['id'],
name: maps[i]['name'],
email: maps[i]['email'],
);
});
}
@override
Future<User?> getUserById(int id) async {
final List<Map<String, dynamic>> maps = await _db.query(
'users',
where: 'id = ?',
whereArgs: [id],
);
if (maps.isNotEmpty) {
final map = maps.first;
return User(
id: map['id'],
name: map['name'],
email: map['email'],
);
}
return null;
}
@override
Future<void> insertUser(User user) async {
await _db.insert('users', {
'id': user.id,
'name': user.name,
'email': user.email,
});
}
@override
Future<void> updateUser(User user) async {
await _db.update(
'users',
{
'name': user.name,
'email': user.email,
},
where: 'id = ?',
whereArgs: [user.id],
);
}
@override
Future<void> deleteUser(int id) async {
await _db.delete('users', where: 'id = ?', whereArgs: [id]);
}
}
| Method | Description | Notes |
|---|---|---|
| getAllUsers() | Fetches all users from the database | Returns a list of User objects |
| getUserById(int id) | Fetches a user by ID | Returns a User object or null if not found |
| insertUser(User user) | Inserts a new user into the database | Uses the provided User object's properties |
| updateUser(User user) | Updates an existing user in the database | Uses the provided User object's properties |
| deleteUser(int id) | Deletes a user by ID | Removes the user with the specified ID |
init() method must be called before any database operations to ensure the database is opened.