On this page12 sections
Lecture 002 — The User Model & Table Columns
| Category | 1 — Sqflite Basics |
| Lecture | 002 of 004 |
| Estimated video length | 16 min |
| Target | Flutter 3.44 · Dart 3.12 · sqflite 2.3 |
| Level | Beginner |
A table row is just a
Map<String, Object?>— sqflite has no idea about yourUserclass. We write a small model withtoMap()andfromMap()so the rest of the app never touches a raw map.
Contents
Prerequisites · Connection · Objectives · Lesson · Code Examples · Common Mistakes · Best Practices · Mini Challenge · Practice Questions · Answers · Takeaways
Prerequisites
Lecture 001 and Dart classes with named constructors and factory constructors.
Connection With Previous Lectures
Lecture 001 created a table with eight columns. Every sqflite method — insert, update,
query — reads and writes Map<String, Object?>, not a Dart object. This lecture writes the
translation layer between the two.
Learning Objectives
- Declare column names as constants instead of repeating string literals
- Write a model class with a
toMap()method - Write a matching
fromMap()factory constructor - Explain why constants catch typos the compiler otherwise can't
1. Column Name Constants
// lib/data_sqflite/model_user.dart
const String tableUser = 'table_user';
const String columnId = 'id';
const String columnName = 'name';
const String columnEmail = 'email';
const String columnPhoneNumber = 'phone_number';
const String columnAvatar = 'avatar';
const String columnBearer = 'bearer';
const String columnCreatedAt = 'created_at';
const String columnUpdatedAt = 'updated_at';Every query in Lectures 003–004 is built from these constants, never from a typed-out string.
Misspell 'phone_numbr' in a raw string and SQLite silently treats it as a new column with no
data; misspell columnPhoneNumbr and the analyzer refuses to compile.
2. The Model Class
// lib/data_sqflite/model_user.dart
class ModelUser {
final int? id;
final String name;
final String email;
final String phoneNumber;
final String avatar;
final String bearer;
final String createdAt;
final String updatedAt;
ModelUser({
this.id,
required this.name,
required this.email,
required this.phoneNumber,
required this.avatar,
required this.bearer,
required this.createdAt,
required this.updatedAt,
});
Map<String, Object?> toMap() {
return {
if (id != null) columnId: id,
columnName: name,
columnEmail: email,
columnPhoneNumber: phoneNumber,
columnAvatar: avatar,
columnBearer: bearer,
columnCreatedAt: createdAt,
columnUpdatedAt: updatedAt,
};
}
factory ModelUser.fromMap(Map<String, Object?> map) {
return ModelUser(
id: map[columnId] as int?,
name: map[columnName] as String,
email: map[columnEmail] as String,
phoneNumber: map[columnPhoneNumber] as String,
avatar: map[columnAvatar] as String,
bearer: map[columnBearer] as String,
createdAt: map[columnCreatedAt] as String,
updatedAt: map[columnUpdatedAt] as String,
);
}
ModelUser copyWith({String? name, String? email, String? bearer}) {
return ModelUser(
id: id,
name: name ?? this.name,
email: email ?? this.email,
phoneNumber: phoneNumber,
avatar: avatar,
bearer: bearer ?? this.bearer,
createdAt: createdAt,
updatedAt: updatedAt,
);
}
}idis nullable — a brand-new user has no row yet, so no id.INTEGER PRIMARY KEYfills one in once the row is inserted (Lecture 003).toMap()only writesidwhen it is not null — handing sqflite'sinsertacolumnId: nullentry works too, but omitting it is clearer about intent: "let SQLite choose the id".fromMap()is afactoryconstructor — it doesn't allocate a new, half-built instance before running logic; it builds the finished object in one step from a map.- The
as int?/as Stringcasts —Map<String, Object?>loses the original column types at compile time, sofromMapmust cast every field back to what the column actually holds. copyWithmakes the model immutable (every fieldfinal) while still letting the manager class build an updated copy to save — useful in Lecture 003.
Code Examples
Example 1 — Building a New User Before Saving
final newUser = ModelUser(
name: 'Zayan',
email: 'zayan@example.com',
phoneNumber: '03001234567',
avatar: '',
bearer: '',
createdAt: DateTime.now().toIso8601String(),
updatedAt: DateTime.now().toIso8601String(),
);
print(newUser.toMap());
// {name: Zayan, email: zayan@example.com, phone_number: 03001234567, ...}Notice there is no id key at all — toMap() leaves it out until the row has been inserted
and a real id comes back from SQLite.
Common Beginner Mistakes
1. Writing raw strings in every query ('phone_numbr') instead of a constant — a typo
compiles fine and fails silently at run time.
2. Making id non-nullable — then there is no legal value to give a user before it is
saved, and code reaches for -1 or 0 as a fake id, which can collide with a real row.
3. Forgetting a cast in fromMap — map[columnId] is Object?; without as int? the
compiler treats id as dynamic everywhere it's used, hiding type errors until run time.
4. Storing dates as DateTime objects directly — sqflite only stores int, double,
String, Uint8List and null. Store DateTime.now().toIso8601String() and parse it back
with DateTime.parse(...) when reading.
Best Practices
- Column names live in one file as constants, shared by the model and the manager.
- Keep the model immutable (
finalfields,copyWith) so a row in memory can't silently drift from what's on disk. - Cast every field explicitly in
fromMap— never leave it asdynamic.
Mini Challenge
Add an isActive boolean column. Store it as INTEGER (0/1, since SQLite has no boolean
type) and convert it to/from bool in toMap()/fromMap().
Practice Questions
- Why is
idnullable on the model? 2. What four Dart types can a sqflite column actually hold? 3. Why use afactoryconstructor forfromMap? 4. What goes wrong if two different query strings spell a column name two different ways?
Practice Answers
- A new, unsaved user has no row id yet;
nullrepresents "not saved." int,double,String,Uint8List(plusnull) — noDateTimeorbool.- It builds the finished object directly from the map in one expression instead of needing a separate constructor and no-op fields.
- SQLite does not error — it just silently has no data under the misspelled name, which looks like a logic bug, not a typo.
Key Takeaways
- Column names are constants shared by the model and the manager — never raw strings.
toMap()turns a model into what sqflite can store;fromMap()turns a row back into a model.- Sqflite only stores
int,double,String,Uint8Listandnull— dates and booleans are encoded, not stored natively.