How to Build a Flutter App with SQLite Database

Table of Contents

  1. Introduction
  2. Why Use SQLite in Flutter
  3. Project Overview
  4. Prerequisites
  5. Step 1: Create Flutter Project
  6. Step 2: Add Dependencies
  7. Step 3: Create Database Model
  8. Step 4: Database Helper Class
  9. Step 5: Implement CRUD Operations
  10. Step 6: Build UI
  11. Step 7: Connect UI with Database
  12. Step 8: Run and Test App
  13. Best Practices
  14. Conclusion

1. Introduction

In modern mobile app development, storing data locally is crucial for performance and offline access. Flutter provides excellent support for local databases using SQLite.

This guide walks you through building a complete Flutter app with SQLite, including CRUD operations (Create, Read, Update, Delete).

2. Why Use SQLite in Flutter

SQLite is a lightweight, embedded database that works perfectly for mobile apps.

Benefits:

  • Offline data storage
  • Fast performance
  • No server required
  • Easy integration with Flutter via sqflite

3. Project Overview

We will build a simple Task Manager App where users can:

  • Add tasks
  • View tasks
  • Update tasks
  • Delete tasks

4. Prerequisites

Before starting:

  • Install Flutter SDK
  • Install Android Studio / VS Code
  • Basic knowledge of Dart & Flutter

Why Use SQLite in Flutter (With Real-World Examples)

SQLite is a lightweight, embedded relational database that runs directly inside your Flutter app—no internet or external server required.

Let’s break this down with practical, real-world scenarios 👇

Example 1: To-Do / Task Manager App

Imagine you are building a Task Manager App:

  • User adds tasks
  • Updates tasks
  • Deletes tasks

👉 Without SQLite:

  • Data is lost when the app closes

👉 With SQLite:

  • Tasks are saved permanently on device

Conclusion: SQLite ensures data persistence

Example 2: E-Commerce Cart System

In an e-commerce app:

  • Users add items to cart
  • Close app
  • Return later

👉 Without SQLite:

  • Cart resets → bad user experience ❌

👉 With SQLite:

  • Cart remains saved locally ✔

Conclusion: SQLite improves user experience and retention

Example 3: Offline-First Apps

Consider apps like:

  • Note-taking apps
  • Field data collection apps
  • Rural area apps with poor internet

👉 SQLite allows:

  • Store data offline
  • Sync later when internet is available

Conclusion: SQLite enables offline functionality

Example 4: Data-Heavy Applications

Apps that manage structured data:

  • Student records system
  • Inventory management
  • Library systems

👉 SQLite provides:

  • Tables
  • Relationships
  • Structured queries

Conclusion: SQLite is ideal for structured data management

Key Benefits Summary

Feature Benefit
Offline Storage Works without internet
Fast Access Local queries are quick
Lightweight No server needed
Structured Data Uses SQL queries
Persistent Data remains after app restart

When You Should NOT Use SQLite

SQLite is powerful—but not always the best choice.

Avoid SQLite when:

  • You need real-time sync across users
  • You require cloud-based storage
  • Your app is multi-device dependent

👉 Use instead:

  • Firebase Firestore
  • REST APIs + backend

5. Step 1: Create Flutter Project

You may create from prompt or console:

flutter create flutter_sqlite_app
cd flutter_sqlite_app

6. Step 2: Add Dependencies

dependencies:
  flutter:
    sdk: flutter
  cupertino_icons: ^1.0.8
  sqflite: ^2.3.0
  path: ^1.8.3

Step 3: main.dart

import 'package:flutter/material.dart';
import 'screens/home_screen.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'SQLite App',
      home: HomeScreen(),
    );
  }
}

Step 4: Model (models/task.dart)

class Task {
  int? id;
  String title;

  Task({this.id, required this.title});

  Map<String, dynamic> toMap() {
    return {
      'id': id,
      'title': title,
    };
  }

  factory Task.fromMap(Map<String, dynamic> map) {
    return Task(
      id: map['id'],
      title: map['title'],
    );
  }
}

Step 5: Database Helper (database/db_helper.dart)

import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
import '../models/task.dart';

class DBHelper {
  static final DBHelper _instance = DBHelper._internal();
  static Database? _database;

  factory DBHelper() => _instance;

  DBHelper._internal();

  Future<Database> get database async {
    if (_database != null) return _database!;
    _database = await _initDB();
    return _database!;
  }

  Future<Database> _initDB() async {
    String path = join(await getDatabasesPath(), 'tasks.db');

    return await openDatabase(
      path,
      version: 1,
      onCreate: (db, version) async {
        await db.execute('''
          CREATE TABLE tasks(
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            title TEXT
          )
        ''');
      },
    );
  }

  // INSERT
  Future<int> insertTask(Task task) async {
    final db = await database;
    return await db.insert('tasks', task.toMap());
  }

  // READ
  Future<List<Task>> getTasks() async {
    final db = await database;
    final List<Map<String, dynamic>> maps = await db.query('tasks');

    return maps.map((e) => Task.fromMap(e)).toList();
  }

  // UPDATE
  Future<int> updateTask(Task task) async {
    final db = await database;
    return await db.update(
      'tasks',
      task.toMap(),
      where: 'id = ?',
      whereArgs: [task.id],
    );
  }

  // DELETE
  Future<int> deleteTask(int id) async {
    final db = await database;
    return await db.delete(
      'tasks',
      where: 'id = ?',
      whereArgs: [id],
    );
  }
}

Step 6: UI Screen (screens/home_screen.dart)

import 'package:flutter/material.dart';
import '../database/db_helper.dart';
import '../models/task.dart';
import '../widgets/task_tile.dart';

class HomeScreen extends StatefulWidget {
  @override
  _HomeScreenState createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  final DBHelper dbHelper = DBHelper();
  List<Task> tasks = [];

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

  void loadTasks() async {
    final data = await dbHelper.getTasks();
    setState(() {
      tasks = data;
    });
  }

  void addTask(String title) async {
    await dbHelper.insertTask(Task(title: title));
    loadTasks();
  }

  void updateTask(Task task) async {
    await dbHelper.updateTask(task);
    loadTasks();
  }

  void deleteTask(int id) async {
    await dbHelper.deleteTask(id);
    loadTasks();
  }

  void showDialogBox({Task? task}) {
    TextEditingController controller =
    TextEditingController(text: task?.title ?? "");

    showDialog(
      context: context,
      builder: (_) => AlertDialog(
        title: Text(task == null ? "Add Task" : "Update Task"),
        content: TextField(
          controller: controller,
          decoration: InputDecoration(hintText: "Enter task"),
        ),
        actions: [
          TextButton(
            onPressed: () {
              if (task == null) {
                addTask(controller.text);
              } else {
                task.title = controller.text;
                updateTask(task);
              }
              Navigator.pop(context);
            },
            child: Text("Save"),
          ),
        ],
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("SQLite Task Manager"),
      ),
      body: ListView.builder(
        itemCount: tasks.length,
        itemBuilder: (context, index) {
          return TaskTile(
            task: tasks[index],
            onEdit: () => showDialogBox(task: tasks[index]),
            onDelete: () => deleteTask(tasks[index].id!),
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => showDialogBox(),
        child: Icon(Icons.add),
      ),
    );
  }
}

Step 7: Widget (widgets/task_tile.dart)

import 'package:flutter/material.dart';
import '../models/task.dart';

class TaskTile extends StatelessWidget {
  final Task task;
  final VoidCallback onEdit;
  final VoidCallback onDelete;

  TaskTile({
    required this.task,
    required this.onEdit,
    required this.onDelete,
  });

  @override
  Widget build(BuildContext context) {
    return Card(
      child: ListTile(
        title: Text(task.title),
        trailing: Row(
          mainAxisSize: MainAxisSize.min,
          children: [
            IconButton(
              icon: Icon(Icons.edit, color: Colors.blue),
              onPressed: onEdit,
            ),
            IconButton(
              icon: Icon(Icons.delete, color: Colors.red),
              onPressed: onDelete,
            ),
          ],
        ),
      ),
    );
  }
}

Best Practices

  • Use Singleton pattern for DBHelper
  • Avoid blocking UI (use async/await)
  • Handle exceptions properly
  • Use State Management (Provider, GetX, Bloc) for scalability
  • Normalize database schema

Conclusion

Building a Flutter app with SQLite is essential for offline-first applications. With sqflite, you can efficiently manage structured data locally.

This step-by-step guide covered:

  • Database setup
  • CRUD operations
  • UI integration

You now have a complete foundation to build scalable Flutter apps with local storage.

 

 

Write A Comment