On this page12 sections
  1. Prerequisites
  2. Connection With Previous Lectures
  3. Learning Objectives
  4. 1. Column Name Constants
  5. 2. The Model Class
  6. Code Examples
  7. Common Beginner Mistakes
  8. Best Practices
  9. Mini Challenge
  10. Practice Questions
  11. Practice Answers
  12. Key Takeaways

Lecture 002 — The User Model & Table Columns

Category1 — Sqflite Basics
Lecture002 of 004
Estimated video length16 min
TargetFlutter 3.44 · Dart 3.12 · sqflite 2.3
LevelBeginner

A table row is just a Map<String, Object?> — sqflite has no idea about your User class. We write a small model with toMap() and fromMap() 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

dart
// 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

dart
// 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,
    );
  }
}
  • id is nullable — a brand-new user has no row yet, so no id. INTEGER PRIMARY KEY fills one in once the row is inserted (Lecture 003).
  • toMap() only writes id when it is not null — handing sqflite's insert a columnId: null entry works too, but omitting it is clearer about intent: "let SQLite choose the id".
  • fromMap() is a factory constructor — 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 String casts — Map<String, Object?> loses the original column types at compile time, so fromMap must cast every field back to what the column actually holds.
  • copyWith makes the model immutable (every field final) 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

dart
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 (final fields, copyWith) so a row in memory can't silently drift from what's on disk.
  • Cast every field explicitly in fromMap — never leave it as dynamic.

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

  1. Why is id nullable on the model? 2. What four Dart types can a sqflite column actually hold? 3. Why use a factory constructor for fromMap? 4. What goes wrong if two different query strings spell a column name two different ways?

Practice Answers

  1. A new, unsaved user has no row id yet; null represents "not saved."
  2. int, double, String, Uint8List (plus null) — no DateTime or bool.
  3. It builds the finished object directly from the map in one expression instead of needing a separate constructor and no-op fields.
  4. 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, Uint8List and null — dates and booleans are encoded, not stored natively.