Flutter SQLite CRUD Example — Learn Local Database with Ease
Social preview image generated by GitHub
Introduction
When building Flutter apps, sometimes you don’t need a cloud database like Firebase.
Instead, a local lightweight database is enough — for example, when storing notes, tasks, or offline data.
Flutter provides an easy way to work with SQLite using the sqflite plugin.
In this post, we’ll walk through a working SQLite CRUD (Create, Read, Update, Delete) example taken from my GitHub repo:
SQL-Lite .
Keywords
Flutter SQLite, Flutter local database, sqflite CRUD, SQLite in Flutter, DatabaseHandler Flutter
Importance
Works offline — perfect for apps that don’t always have internet.
Lightweight yet powerful.
Easy to integrate with Flutter using the sqflite package.
Supports structured queries, making it great for CRUD-based apps.
👉 Contact on WhatsApp
Explanation
1. The Model — book.dart
class Book {
int? id;
String name;
double price;
Book({this.id, required this.name, required this.price});
Map<String, dynamic> toMap() {
return {
'id': id,
'name': name,
'price': price,
};
}
@override
String toString() {
return 'Book{id: $id, name: $name, price: $price}';
}
}
Copy
2. Database Handler — databasehandler.dart
class DatabaseHandler {
static Future<Database> initialize() async {
final dbPath = await getDatabasesPath();
return openDatabase(
join(dbPath, 'books.db'),
onCreate: (db, version) {
return db.execute(
'CREATE TABLE books(id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, price REAL)',
);
},
version: 1,
);
}
Future<int> insertBook(Book book) async {
final db = await initialize();
return await db.insert('books', book.toMap());
}
Future<List<Book>> books() async {
final db = await initialize();
final List<Map<String, dynamic>> maps = await db.query('books');
return List.generate(maps.length, (i) {
return Book(
id: maps[i]['id'],
name: maps[i]['name'],
price: maps[i]['price'],
);
});
}
}
Copy
3. The UI — vsjsqlite.dart
The app uses a simple Flutter UI with buttons and text fields. You can add a book, view the list, update, or delete it.
4. Entry Point — main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await DatabaseHandler.initialize();
runApp(MyApp());
}
Copy
New: DartPad (web) version — using localStorage
This single-file Flutter app runs in DartPad (Flutter) and uses the browser localStorage
for persistence.
Copy the code below and paste it into DartPad (choose Flutter) , then press Run.
// Paste this entire file into DartPad (Flutter) and run.
// Uses browser localStorage to store a list of books as JSON.
// Works only in Flutter web (DartPad Flutter).
import 'dart:convert';
import 'dart:html' as html; // for localStorage
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
/// Keys used in localStorage
const String storageKey = 'vsj_books';
/// Simple Book model
class Book {
int? id; // generated client-side
String name;
double price;
Book({this.id, required this.name, required this.price});
Map toMap() => {
'id': id,
'name': name,
'price': price,
};
factory Book.fromMap(Map m) => Book(
id: m['id'] as int?,
name: m['name'] as String? ?? '',
price: (m['price'] is int)
? (m['price'] as int).toDouble()
: (m['price'] as double? ?? 0.0),
);
@override
String toString() => 'Book{id: $id, name: $name, price: $price}';
}
/// Simple storage wrapper using browser localStorage
class LocalBookStorage {
/// Read all books
static List readAll() {
final raw = html.window.localStorage[storageKey];
if (raw == null) return [];
try {
final List list = jsonDecode(raw) as List;
final books = list
.map((e) => Book.fromMap(Map.from(e)))
.toList();
return books;
} catch (e) {
// If parsing fails, return empty and reset storage to avoid repeated errors
html.window.localStorage.remove(storageKey);
return [];
}
}
/// Write the full list
static void writeAll(List books) {
final List maps =
books.map((b) => b.toMap()).toList();
html.window.localStorage[storageKey] = jsonEncode(maps);
}
/// Insert book (generates id)
static Book insert(Book book) {
final books = readAll();
final nextId = (books.isEmpty) ? 1 : (books.map((b) => b.id ?? 0).reduce((a, b) => a > b ? a : b) + 1);
final toSave = Book(id: nextId, name: book.name, price: book.price);
books.add(toSave);
writeAll(books);
return toSave;
}
/// Update book by id
static bool update(Book book) {
final books = readAll();
final idx = books.indexWhere((b) => b.id == book.id);
if (idx == -1) return false;
books[idx] = book;
writeAll(books);
return true;
}
/// Delete by id
static bool deleteById(int id) {
final books = readAll();
final initialLen = books.length;
books.removeWhere((b) => b.id == id);
if (books.length == initialLen) return false;
writeAll(books);
return true;
}
/// Clear all (for convenience)
static void clearAll() {
html.window.localStorage.remove(storageKey);
}
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter localStorage CRUD',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const BookHomePage(),
debugShowCheckedModeBanner: false,
);
}
}
class BookHomePage extends StatefulWidget {
const BookHomePage({super.key});
@override
State createState() => _BookHomePageState();
}
class _BookHomePageState extends State {
List books = [];
final TextEditingController nameController = TextEditingController();
final TextEditingController priceController = TextEditingController();
@override
void initState() {
super.initState();
_loadBooks();
}
void _loadBooks() {
setState(() {
books = LocalBookStorage.readAll();
});
}
void _addBook() {
final name = nameController.text.trim();
final priceText = priceController.text.trim();
if (name.isEmpty || priceText.isEmpty) {
_showSnack('Please enter name and price');
return;
}
final price = double.tryParse(priceText);
if (price == null) {
_showSnack('Invalid price');
return;
}
final saved = LocalBookStorage.insert(Book(name: name, price: price));
nameController.clear();
priceController.clear();
_showSnack('Added "${saved.name}" (id:${saved.id})');
_loadBooks();
}
void _editBook(Book b) async {
final edited = await showDialog(
context: context,
builder: (_) => _EditDialog(book: b),
);
if (edited != null) {
final ok = LocalBookStorage.update(edited);
if (ok) {
_showSnack('Updated "${edited.name}"');
_loadBooks();
} else {
_showSnack('Update failed (id not found)');
}
}
}
void _deleteBook(Book b) {
final ok = LocalBookStorage.deleteById(b.id ?? -1);
if (ok) {
_showSnack('Deleted "${b.name}"');
_loadBooks();
} else {
_showSnack('Delete failed');
}
}
void _clearAll() {
LocalBookStorage.clearAll();
_loadBooks();
_showSnack('All books cleared');
}
void _showSnack(String msg) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(msg)));
}
Widget _buildList() {
if (books.isEmpty) {
return const Center(
child: Text('No books saved yet. Add one!'),
);
}
return ListView.separated(
itemCount: books.length,
separatorBuilder: (_, __) => const Divider(height: 1),
itemBuilder: (context, index) {
final b = books[index];
return ListTile(
leading: CircleAvatar(child: Text('${b.id}')),
title: Text(b.name),
subtitle: Text('Price: ₹${b.price.toStringAsFixed(2)}'),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Icons.edit),
tooltip: 'Edit',
onPressed: () => _editBook(b),
),
IconButton(
icon: const Icon(Icons.delete),
tooltip: 'Delete',
onPressed: () => _confirmDelete(b),
),
],
),
);
},
);
}
void _confirmDelete(Book b) {
showDialog(
context: context,
builder: (_) => AlertDialog(
title: const Text('Delete book'),
content: Text('Delete "${b.name}" (id: ${b.id})?'),
actions: [
TextButton(onPressed: () => Navigator.pop(context, false), child: const Text('Cancel')),
TextButton(
onPressed: () {
Navigator.pop(context, true);
},
child: const Text('Delete')),
],
),
).then((confirmed) {
if (confirmed == true) _deleteBook(b);
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flutter localStorage CRUD'),
actions: [
IconButton(
icon: const Icon(Icons.download),
tooltip: 'Print JSON',
onPressed: () {
final json = html.window.localStorage[storageKey] ?? '[]';
showDialog(
context: context,
builder: (_) => AlertDialog(
title: const Text('Stored JSON'),
content: SingleChildScrollView(child: SelectableText(json)),
actions: [TextButton(onPressed: () => Navigator.pop(context), child: const Text('Close'))],
),
);
},
),
IconButton(
icon: const Icon(Icons.delete_forever),
tooltip: 'Clear all',
onPressed: () {
showDialog(
context: context,
builder: (_) => AlertDialog(
title: const Text('Clear all data'),
content: const Text('This will remove all saved books from localStorage. Continue?'),
actions: [
TextButton(onPressed: () => Navigator.pop(context, false), child: const Text('Cancel')),
TextButton(onPressed: () => Navigator.pop(context, true), child: const Text('Clear')),
],
),
).then((ok) {
if (ok == true) _clearAll();
});
},
),
],
),
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(12.0),
child: _buildAddCard(),
),
const Divider(height: 1),
Expanded(child: _buildList()),
],
),
);
}
Widget _buildAddCard() {
return Card(
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
children: [
const Align(
alignment: Alignment.centerLeft,
child: Text('Add a book', style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
),
const SizedBox(height: 8),
TextField(
controller: nameController,
decoration: const InputDecoration(labelText: 'Book name', border: OutlineInputBorder()),
),
const SizedBox(height: 8),
TextField(
controller: priceController,
decoration: const InputDecoration(labelText: 'Price', border: OutlineInputBorder()),
keyboardType: const TextInputType.numberWithOptions(decimal: true),
),
const SizedBox(height: 8),
Row(
children: [
ElevatedButton.icon(
icon: const Icon(Icons.add),
label: const Text('Add Book'),
onPressed: _addBook,
),
const SizedBox(width: 12),
ElevatedButton.icon(
icon: const Icon(Icons.refresh),
label: const Text('Reload'),
style: ElevatedButton.styleFrom(backgroundColor: Colors.grey[600]),
onPressed: _loadBooks,
)
],
),
],
),
),
);
}
}
/// Dialog to edit an existing book
class _EditDialog extends StatefulWidget {
final Book book;
const _EditDialog({required this.book});
@override
State<_EditDialog> createState() => _EditDialogState();
}
class _EditDialogState extends State<_EditDialog> {
late TextEditingController nameC;
late TextEditingController priceC;
@override
void initState() {
super.initState();
nameC = TextEditingController(text: widget.book.name);
priceC = TextEditingController(text: widget.book.price.toString());
}
@override
void dispose() {
nameC.dispose();
priceC.dispose();
super.dispose();
}
void _submit() {
final name = nameC.text.trim();
final price = double.tryParse(priceC.text.trim());
if (name.isEmpty || price == null) {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Please enter valid name and price')));
return;
}
final edited = Book(id: widget.book.id, name: name, price: price);
Navigator.pop(context, edited);
}
@override
Widget build(BuildContext context) {
return AlertDialog(
title: const Text('Edit book'),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextField(controller: nameC, decoration: const InputDecoration(labelText: 'Book name')),
const SizedBox(height: 8),
TextField(controller: priceC, decoration: const InputDecoration(labelText: 'Price')),
],
),
actions: [
TextButton(onPressed: () => Navigator.pop(context, null), child: const Text('Cancel')),
TextButton(onPressed: _submit, child: const Text('Save')),
],
);
}
}
Copy
Tip: After copying, open DartPad (choose the Flutter pad), paste the code into main.dart
, and press Run.
Complexity
The DartPad/localStorage example uses simple JSON read/write:
Insert / Update / Delete — O(1) average per item
Read all — O(n) where n is number of books
For small datasets and demos this is fine.
How to Run
git clone https://github.com/Varanasi-Software-Junction/SQL-Lite.git
cd SQL-Lite
flutter pub get
flutter run
Conclusion
SQLite is great for mobile apps; for quick web demos DartPad + localStorage
is simpler. Both approaches show the same CRUD flow and help you get a working app fast.
Project on Dartpad
0 Comments