SQLite with sqflite in Flutter: solved CRUD exercise

SQLite with sqflite in Flutter: solved exercise

If you are looking for SQLite with sqflite in Flutter, this solved exercise gives you a practical implementation pattern you can reuse in real projects.

Problem statement

Build a screen with:

  • create local tasks table
  • insert records
  • list records from local database

Flutter solution

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import 'package:flutter/material.dart';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';

Future<Database> openDb() async {
  return openDatabase(
    join(await getDatabasesPath(), 'tasks.db'),
    version: 1,
    onCreate: (db, _) {
      return db.execute('CREATE TABLE tasks(id INTEGER PRIMARY KEY, title TEXT)');
    },
  );
}

void main() => runApp(const MaterialApp(home: TasksPage()));

class TasksPage extends StatefulWidget {
  const TasksPage({super.key});

  @override
  State<TasksPage> createState() => _TasksPageState();
}

class _TasksPageState extends State<TasksPage> {
  final ctrl = TextEditingController();
  List<Map<String, dynamic>> tasks = [];

  Future<void> refreshTasks() async {
    final db = await openDb();
    tasks = await db.query('tasks', orderBy: 'id DESC');
    setState(() {});
  }

  Future<void> addTask() async {
    final db = await openDb();
    await db.insert('tasks', {'title': ctrl.text.trim()});
    ctrl.clear();
    await refreshTasks();
  }

  @override
  void initState() {
    super.initState();
    refreshTasks();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('sqflite CRUD')),
      body: Column(
        children: [
          Padding(
            padding: const EdgeInsets.all(12),
            child: Row(
              children: [
                Expanded(child: TextField(controller: ctrl, decoration: const InputDecoration(labelText: 'Task'))),
                IconButton(onPressed: addTask, icon: const Icon(Icons.add)),
              ],
            ),
          ),
          Expanded(
            child: ListView.builder(
              itemCount: tasks.length,
              itemBuilder: (_, i) => ListTile(title: Text(tasks[i]['title'] as String)),
            ),
          ),
        ],
      ),
    );
  }
}

Expected result

Tasks are stored locally and persist between app sessions.

Common mistakes

  • Recreating the DB layer repeatedly.
  • Missing await on write operations.
  • Mixing DB logic and UI rendering.

Practical use

A standard base for offline-first flows like notes, trackers, and local catalogs.

Guided practice and next step

FAQ

Is sqflite enough for real apps?

Yes, for many local persistence scenarios.

Do I need an ORM?

Not always. For simple apps, direct queries can be enough.

SQLite or remote backend?

Many apps use both: local cache plus remote source of truth.