How I Used SQLite in My Flutter App with sqflite
FlutterPulseThis article was translated specially for the channel FlutterPulseYou'll find lots of interesting things related to Flutter on this channel. Don't hesitate to subscribe!π
Working with local storage in Flutter? If you're looking to save data like notes or tasks without using the internet, SQLite is a greatβ¦
Working with local storage in Flutter? If you're looking to save data like notes or tasks without using the internet, SQLite is a great option.
This is actually my first post on Medium, and I wanted to share something simple but useful. In this guide, I'll show you how to use the sqflite package to set up a local database and perform basic CRUD operations. Let's get into it!
Project Setup
Before jumping into code, we need to add the required packages and set up the structure.
Add Dependencies
Open your pubspec.yaml and add the following:
dependencies:
flutter:
sdk: flutter
sqflite: ^2.3.2
path: ^1.8.3
Then run:
flutter pub get in terminal
Suggested Folder Structure
You can organize your project like this to keep things clean:
lib/
βββ db/
β βββ database_helper.dart
βββ models/
β βββ note.dart
βββ screens/
β βββ home_screen.dart
βββ main.dart
Creating the Note Model
Let's keep it simple with 3 fields: id, title, and content.
File: lib/models/note.dart
class Note {
final int? id;
final String title;
final String content;
Note({this.id, required this.title, required this.content});
// Convert Note object to Map
Map<String, dynamic> toMap() {
return {
'id': id,
'title': title,
'content': content,
};
}
// Convert Map to Note object
factory Note.fromMap(Map<String, dynamic> map) {
return Note(
id: map['id'],
title: map['title'],
content: map['content'],
);
}
}This model gives us two handy methods:
toMap()β for saving to SQLite.fromMap()β for reading from SQLite.
Setting Up SQLite with DatabaseHelper
This class will handle:
- Creating the database
- Defining the table
- Inserting, updating, deleting, and fetching notes
File: lib/db/database_helper.dart
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
import '../models/note.dart';
class DatabaseHelper {
static final DatabaseHelper _instance = DatabaseHelper._internal();
factory DatabaseHelper() => _instance;
DatabaseHelper._internal();
static Database? _database;
Future<Database> get database async {
_database ??= await _initDB();
return _database!;
}
Future<Database> _initDB() async {
final dbPath = await getDatabasesPath();
final path = join(dbPath, 'notes.db');
return await openDatabase(
path,
version: 1,
onCreate: _createDB,
);
}
Future _createDB(Database db, int version) async {
await db.execute('''
CREATE TABLE notes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
content TEXT NOT NULL
)
''');
}
// Insert a note
Future<int> insertNote(Note note) async {
final db = await database;
return await db.insert('notes', note.toMap(), conflictAlgorithm: ConflictAlgorithm.replace);
}
// Get all notes
Future<List<Note>> getNotes() async {
final db = await database;
final List<Map<String, dynamic>> maps = await db.query('notes');
return maps.map((map) => Note.fromMap(map)).toList();
}
// Update a note
Future<int> updateNote(Note note) async {
final db = await database;
return await db.update(
'notes',
note.toMap(),
where: 'id = ?',
whereArgs: [note.id],
);
}
// Delete a note
Future<int> deleteNote(int id) async {
final db = await database;
return await db.delete(
'notes',
where: 'id = ?',
whereArgs: [id],
);
}
}
Quick Notes:
- We used a singleton pattern for a single database instance.
openDatabase()initializes and creates thenotestable on first run.- All CRUD methods are async and return standard
intorList<Note>.
UI Integration β Displaying and Adding Notes
We'll build a minimal app with:
- A
ListViewto show notes - A FAB to add new notes
- A simple screen to add or edit a note
File: lib/main.dart
import 'package:flutter/material.dart';
import 'screens/home_screen.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'SQLite Notes',
theme: ThemeData(primarySwatch: Colors.indigo),
home: const HomeScreen(),
);
}
}
File: lib/screens/home_screen.dart
import 'package:flutter/material.dart';
import '../db/database_helper.dart';
import '../models/note.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
List<Note> notes = [];
@override
void initState() {
super.initState();
_loadNotes();
}
Future<void> _loadNotes() async {
final data = await DatabaseHelper().getNotes();
setState(() {
notes = data;
});
}
void _addNoteDialog() {
final titleController = TextEditingController();
final contentController = TextEditingController();
showDialog(
context: context,
builder: (_) => AlertDialog(
title: const Text('Add Note'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(controller: titleController, decoration: const InputDecoration(hintText: 'Title')),
TextField(controller: contentController, decoration: const InputDecoration(hintText: 'Content')),
],
),
actions: [
TextButton(
onPressed: () async {
final note = Note(title: titleController.text, content: contentController.text);
await DatabaseHelper().insertNote(note);
Navigator.of(context).pop();
_loadNotes();
},
child: const Text('Save'),
),
],
),
);
}
Future<void> _deleteNote(int id) async {
await DatabaseHelper().deleteNote(id);
_loadNotes();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('My Notes')),
body: ListView.builder(
itemCount: notes.length,
itemBuilder: (_, index) {
final note = notes[index];
return ListTile(
title: Text(note.title),
subtitle: Text(note.content),
trailing: IconButton(
icon: const Icon(Icons.delete, color: Colors.red),
onPressed: () => _deleteNote(note.id!),
),
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: _addNoteDialog,
child: const Icon(Icons.add),
),
);
}
}
Bonus Tips & Gotchas
Here are a few practical tips from my own experience working with SQLite in Flutter:
- Use async/await properly
Always await DB calls to avoid unexpected behavior or race conditions. - Database is persisted
Once the database is created, it stays until you uninstall the app or manually delete it. Handy for testing! - Error handling matters
Wrap your DB operations intry-catchblocks, especially for update and delete functions. - Use TextEditingControllers wisely
Don't forget to dispose them if you're using multiple input screens.
Conclusion
That's it! Thanks for reading β and by the way, this is my first post on Medium.
If you found it helpful or have any feedback, feel free to leave a comment or connect. I'd love to hear your thoughts!