Flutter-Todo-App

Flutter To-Do App using SQLite — Learning Sutras (Baby Steps)

🍼 Baby Steps: Flutter To‑Do App with SQLite

By Champak Roy • Updated on

Follow these tiny, exact steps to build a local To‑Do app in Flutter. After each micro-step, do the quick check — then continue. Perfect for beginners and classrooms.

📥 Download Source

Flutter ToDo App header image

Overview

This project stores tasks locally using SQLite via the sqflite package. You'll implement adding, listing, marking done, and deleting tasks. Each section below is split into micro-steps with code and checks.


STEP 0 — Prepare your environment

0.1 Install Flutter (if you haven't)

Follow: https://flutter.dev/docs/get-started/install
✅ Check: Run flutter --version — it prints a Flutter version like Flutter 3.x.x.

0.2 Validate setup

flutter doctor
✅ Check: flutter doctor reports no errors that block running apps. Resolve issues shown before continuing.

STEP 1 — Create the Flutter project (micro)

1.1 Create project folder

flutter create todo_app
✅ Check: A folder named todo_app exists in your current directory.

1.2 Enter the project

cd todo_app
✅ Check: Your terminal prompt shows you're inside todo_app (use pwd to confirm).

1.3 Open in editor

code .   # optional — opens VS Code
✅ Check: You can see project files like lib/, pubspec.yaml in the editor.

STEP 2 — Add required packages

2.1 Add sqflite

flutter pub add sqflite
✅ Check: Command completes and you see Changed 1 dependency or similar output.

2.2 Add path_provider

flutter pub add path_provider
✅ Check: sqflite and path_provider now appear under dependencies in pubspec.yaml.

2.3 Verify packages

flutter pub get
flutter pub deps | grep sqflite
flutter pub deps | grep path_provider
✅ Check: The grep commands show the packages and versions — or on Windows use flutter pub deps and inspect the output manually.

STEP 3 — Create the database helper (very small coding steps)

3.1 Make folder for DB helper

mkdir -p lib/database
✅ Check: lib/database folder exists in the project tree.

3.2 Create the file

touch lib/database/db_helper.dart
✅ Check: db_helper.dart is visible in your editor under lib/database.

3.3 Paste imports

import 'dart:io';
import 'package:path/path.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path_provider/path_provider.dart';
✅ Check: Save the file — no import errors appear (if analyzer complains, run flutter pub get).

3.4 Add DBHelper skeleton

class DBHelper {
  static Database? _database;
  static final DBHelper instance = DBHelper._init();
  DBHelper._init();
}
✅ Check: File has a valid class definition and the analyzer reports no syntax errors.

3.5 Add database getter + init

  Future get database async {
    if (_database != null) return _database!;
    _database = await _initDB('todo.db');
    return _database!;
  }

  Future _initDB(String filePath) async {
    Directory documentsDirectory = await getApplicationDocumentsDirectory();
    final path = join(documentsDirectory.path, filePath);
    return await openDatabase(path, version: 1, onCreate: _createDB);
  }
✅ Check: Save and run flutter analyze — there should be no unresolved references to getApplicationDocumentsDirectory or openDatabase.

3.6 Add table creation SQL

  Future _createDB(Database db, int version) async {
    await db.execute('''
      CREATE TABLE todos(
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        title TEXT NOT NULL,
        isDone INTEGER NOT NULL
      )
    ''');
  }
✅ Check: SQL saved — later when you run the app, DB should be created automatically (see next steps).

3.7 Add CRUD methods

  Future insert(Map row) async {
    final db = await instance.database;
    return await db.insert('todos', row);
  }

  Future>> queryAll() async {
    final db = await instance.database;
    return await db.query('todos', orderBy: 'id DESC');
  }

  Future update(Map row) async {
    final db = await instance.database;
    int id = row['id'];
    return await db.update('todos', row, where: 'id = ?', whereArgs: [id]);
  }

  Future delete(int id) async {
    final db = await instance.database;
    return await db.delete('todos', where: 'id = ?', whereArgs: [id]);
  }
✅ Check: Save file & run flutter analyze — no errors in this file and methods are accessible via DBHelper.instance.

STEP 4 — Build the UI (tiny coding steps)

4.1 Open lib/main.dart

open lib/main.dart
✅ Check: File opens in your editor and is writable.

4.2 Minimal app shell (paste and run)

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(home: Scaffold(body: Center(child: Text('Hello'))));
  }
}
✅ Check: Run flutter run — you should see the word Hello on the device/emulator.

4.3 Add Scaffold + AppBar

Scaffold(
  appBar: AppBar(title: const Text('My To-Do List')),
  body: Column(children: []),
)
✅ Check: AppBar with title My To-Do List is visible.

4.4 Add input row (TextField + Add button)

Padding(
  padding: const EdgeInsets.all(8),
  child: Row(children: [
    Expanded(child: TextField(controller: _controller, decoration: InputDecoration(labelText: 'Enter Task', border: OutlineInputBorder()))),
    IconButton(icon: Icon(Icons.add), onPressed: _addTodo)
  ]),
)
✅ Check: TextField and + button appear at top of screen (functionality will be wired later).

4.5 Add list placeholder

Expanded(child: ListView(children: [Text('task 1'), Text('task 2')]))
✅ Check: Two placeholder tasks render in the list area.

4.6 Paste the full final main.dart (paste and run)

import 'package:flutter/material.dart';
import 'database/db_helper.dart';

void main() { runApp(const TodoApp()); }

class TodoApp extends StatelessWidget { const TodoApp({super.key});
  @override Widget build(BuildContext context) {
    return MaterialApp(title: 'Flutter To-Do', theme: ThemeData(primarySwatch: Colors.blue), home: const TodoHome());
  }
}

class TodoHome extends StatefulWidget { const TodoHome({super.key});
  @override State createState() => _TodoHomeState();
}

class _TodoHomeState extends State {
  final TextEditingController _controller = TextEditingController();
  List> _todos = [];

  void _loadData() async { final data = await DBHelper.instance.queryAll(); setState(() { _todos = data; }); }
  void _addTodo() async { final text = _controller.text.trim(); if (text.isEmpty) return; await DBHelper.instance.insert({'title': text,'isDone': 0,}); _controller.clear(); _loadData(); }
  void _toggleDone(int id, int isDone) async { await DBHelper.instance.update({'id': id, 'isDone': isDone == 0 ? 1 : 0}); _loadData(); }
  void _delete(int id) async { await DBHelper.instance.delete(id); _loadData(); }

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

  @override Widget build(BuildContext context){
    return Scaffold(appBar: AppBar(title: const Text('My To-Do List')),
      body: Column(children:[
        Padding(padding: const EdgeInsets.all(8), child: Row(children:[Expanded(child: TextField(controller: _controller, decoration: const InputDecoration(labelText: 'Enter Task', border: OutlineInputBorder(),), onSubmitted: (_) => _addTodo(),)), IconButton(onPressed: _addTodo, icon: const Icon(Icons.add))])),
        Expanded(child: _todos.isEmpty ? const Center(child: Text('No tasks yet.')) : ListView.builder(itemCount: _todos.length,itemBuilder: (context,index){ final item = _todos[index]; return Dismissible(key: ValueKey(item['id']), background: Container(color: Colors.redAccent), onDismissed: (_) => _delete(item['id']), child: ListTile(title: Text(item['title'], style: TextStyle(decoration: item['isDone'] == 1 ? TextDecoration.lineThrough : TextDecoration.none,),), leading: Checkbox(value: item['isDone'] == 1, onChanged: (val) => _toggleDone(item['id'], item['isDone']),), trailing: IconButton(icon: const Icon(Icons.delete, color: Colors.red), onPressed: () => _delete(item['id']),),),);}),
      ],),);
  }
}
✅ Check: After pasting, run the app. Try adding a task, toggling it, deleting it, and restarting the app — tasks should persist between runs.

STEP 5 — Run & test (tiny steps)

5.1 Run the app

flutter run
✅ Check: App launches on the selected device/emulator and shows the UI.

5.2 Add a task

Type in the field and press the + button
✅ Check: The task appears immediately in the list area.

5.3 Confirm persistence

Stop the app and run flutter run again
✅ Check: Previously added tasks remain listed — persistence confirmed by SQLite.

Diagrams & visuals

Diagrams created for this tutorial (upload them to Blogger and replace the image URLs):

DFD Flowchart ER Diagram UML Class Diagram

Tip: Use the DFD for architecture explanation, Flowchart for control flow, ER for data model, and UML for class details.


Troubleshooting & tips (tiny)

T1 — DB file not found

✅ Check: Look at logs when the app starts — sqflite prints errors if DB creation fails. Ensure path logic uses getApplicationDocumentsDirectory().

T2 — Hot reload not reflecting DB changes

✅ Check: Use full restart or uninstall & reinstall the app when you change DB schema; hot reload won't recreate tables.

T3 — Analyzer & build errors

✅ Check: Run flutter analyze and fix issues reported; often missing imports or typos.

Download / Clone

You can download a ready ZIP or clone a Git repo. I can create the Git repo for you if you want — tell me and I’ll push these files to GitHub (or provide a ZIP).

(Replace these placeholders with your repo or download URLs before publishing.)


Want me to: upload images to Blogger, replace the image URLs, or create the GitHub repo? Say which one and I’ll do it for you.

© Learning Sutras — Champak Roy

0 Comments

Post a Comment

0 Comments