On this page14 sections
Lecture 004 — The Database Manager: Delete & Query
| Category | 2 — The Database Manager |
| Lecture | 004 of 004 |
| Estimated video length | 20 min |
| Target | Flutter 3.44 · Dart 3.12 · sqflite 2.3 |
| Level | Beginner |
insertandupdatechange rows; now the manager needs to remove them and, just as importantly, read them back asModelUserobjects a screen can display.
Contents
Prerequisites · Connection · Objectives · Lesson · Code Examples · Common Mistakes · Best Practices · Mini Challenge · Practice Questions · Answers · Takeaways
Prerequisites
Lecture 003: the DatabaseUser singleton, addRecord and updateRecord.
Connection With Previous Lectures
The manager can create and change rows. A dashboard also needs to remove a user, load one specific user, load the whole list, and show a count — the four remaining jobs of a CRUD manager.
Learning Objectives
- Delete one row and all rows with
db.delete - Fetch rows with
db.queryand map each one throughModelUser.fromMap - Return
null(not a crash) when a single-record lookup finds nothing - Explain why a
countquery should not just fetch every row and measure the list
1. Deleting Rows
Future<int> deleteRecord(int id) async {
final db = await database;
return db.delete(tableUser, where: '$columnId = ?', whereArgs: [id]);
}
Future<int> deleteAllRecords() async {
final db = await database;
return db.delete(tableUser);
}deleteRecordtakes anint id— the same type the column actually stores. The original pasted snippet this lecture is built from took aString idand compared it against anINTEGERcolumn; SQLite's loose typing makes that appear to work, but it's a type mismatch waiting to misbehave the moment the comparison logic changes. Keep the Dart type matching the column type.db.delete(tableUser)with nowheredeletes every row — exactly like aDELETE FROM table_user;with noWHEREclause. That's deliberate fordeleteAllRecords, and exactly the trap described in Lecture 003 if it happens by accident indeleteRecord.- Both return the number of rows deleted —
0fromdeleteRecordmeans "no row had that id," which the caller can check instead of assuming success.
2. Reading One Row
Future<ModelUser?> getSingleRecord(int id) async {
final db = await database;
final rows = await db.query(
tableUser,
where: '$columnId = ?',
whereArgs: [id],
limit: 1,
);
if (rows.isEmpty) return null;
return ModelUser.fromMap(rows.first);
}- Return type is
ModelUser?, notModelUser. "No user with this id" is a normal, expected outcome — not an error — so it's modeled withnull, not with a thrown exception or a force-unwrap. limit: 1stops SQLite as soon as it finds a match instead of scanning the whole table.rowsisList<Map<String, Object?>>— exactly the shapeModelUser.fromMapexpects.
3. Reading All Rows
Future<List<ModelUser>> getAllRecords() async {
final db = await database;
final rows = await db.query(tableUser, orderBy: '$columnCreatedAt DESC');
return rows.map(ModelUser.fromMap).toList();
}orderBy: '$columnCreatedAt DESC'— newest first, done by SQLite itself rather than sorting the list in Dart after loading it.rows.map(ModelUser.fromMap).toList()— one line replaces a hand-written loop that builds a list and appends to it one row at a time.- An empty table naturally returns
[]here — there is no special "found nothing" case to handle separately, unlikegetSingleRecord.
4. Counting Rows
Future<int> getCount() async {
final db = await database;
final result = await db.rawQuery('SELECT COUNT(*) FROM $tableUser');
return Sqflite.firstIntValue(result) ?? 0;
}SELECT COUNT(*)asks SQLite to compute the count — it never loads the actual rows into memory. CallinggetAllRecords()and reading.lengthwould work for a few hundred users and quietly get slower as the table grows, since every column of every row has to be read and mapped just to be thrown away.Sqflite.firstIntValue(result)pulls the single integer out of the single-row, single-column resultrawQueryreturns.
Code Examples
Example 1 — Using the Full Manager
final db = DatabaseUser();
final id = await db.addRecord(ModelUser(
name: 'Zayan',
email: 'zayan@example.com',
phoneNumber: '03001234567',
avatar: '',
bearer: '',
createdAt: DateTime.now().toIso8601String(),
updatedAt: DateTime.now().toIso8601String(),
));
final user = await db.getSingleRecord(id); // ModelUser
final everyone = await db.getAllRecords(); // List<ModelUser>
final total = await db.getCount(); // 1
await db.deleteRecord(id);
final afterDelete = await db.getSingleRecord(id); // nullCommon Beginner Mistakes
1. Returning null! to satisfy a non-nullable return type — forcing a null into a
type that claims it can't be null crashes the instant a "not found" case actually happens.
If a lookup can legitimately find nothing, its return type must say so with ?.
2. Computing a count by loading everything ((await getAllRecords()).length) — correct,
but it does far more work than SELECT COUNT(*) for the same answer.
3. Deleting with the wrong type in whereArgs — passing a String id against an
INTEGER column usually still matches today, because SQLite's type affinity coerces it, but
it's relying on a coercion rather than a guarantee. Match the column's declared type.
4. Using query's where for "all rows" by passing an empty string (where: '') instead
of simply omitting where — an empty clause is not the same as no clause to every database
engine, and sqflite expects omission, not an empty string.
Best Practices
- Model "might not exist" with a nullable return type, not a sentinel value or a forced unwrap.
- Let SQLite do filtering, sorting and counting (
WHERE,ORDER BY,COUNT(*)) instead of loading everything and processing it in Dart. - Keep the Dart parameter type for a column's id matching the column's declared SQL type.
Mini Challenge
Add Future<List<ModelUser>> searchByName(String query) async using where: '$columnName LIKE ?' and whereArgs: ['%$query%'] — a case-sensitive "contains" search over the name
column.
Practice Questions
- Why does
getSingleRecordreturnModelUser?instead ofModelUser? 2. What doesdb.delete(tableUser)with nowheredo? 3. Why useSELECT COUNT(*)instead ofgetAllRecords().length? 4. What doesSqflite.firstIntValuedo?
Practice Answers
- "No row with this id" is an expected, normal outcome, not an error —
nullmodels that without a crash or a fake value. - Deletes every row in the table.
- SQLite computes the count without reading every row's data into Dart memory.
- Extracts the single integer from the one-row, one-column result a
rawQuerylikeSELECT COUNT(*)returns.
Key Takeaways
deletewith nowhereclears the whole table — exactly like SQL's ownDELETE FROM.- A lookup that might find nothing returns a nullable model, never a forced, fake value.
- Filtering, sorting and counting belong in the SQL statement, not in a Dart loop after loading every row.