sqflite: Database Lokal SQLite
Simpan data terstruktur secara offline dengan sqflite: CRUD produk lengkap dengan model dan helper.
Kapan butuh database lokal?
Aplikasi kasir butuh menyimpan ribuan transaksi dan tetap jalan saat internet mati. SharedPreferences tidak cocok untuk ini. sqflite memberi database SQLite asli di HP dengan query SQL penuh.
Setup
dependencies:
sqflite: ^2.3.0
path: ^1.8.0Database helper: pola standar
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';
class DatabaseHelper {
static final DatabaseHelper instance = DatabaseHelper._init();
static Database? _database;
DatabaseHelper._init();
Future<Database> get database async {
if (_database != null) return _database!;
_database = await _initDB('kasir.db');
return _database!;
}
Future<Database> _initDB(String namaFile) async {
final pathFolder = await getDatabasesPath();
final path = join(pathFolder, namaFile);
return await openDatabase(
path,
version: 1,
onCreate: (db, version) async {
await db.execute('''
CREATE TABLE produk (
id TEXT PRIMARY KEY,
nama TEXT NOT NULL,
harga INTEGER NOT NULL,
stok INTEGER NOT NULL DEFAULT 0
)
''');
},
);
}
Future<void> tambahProduk(Produk p) async {
final db = await database;
await db.insert(
'produk',
p.toJson(),
conflictAlgorithm: ConflictAlgorithm.replace,
);
}
Future<List<Produk>> ambilSemuaProduk() async {
final db = await database;
final hasil = await db.query('produk', orderBy: 'nama ASC');
return hasil.map((e) => Produk.fromJson(e)).toList();
}
Future<int> ubahProduk(Produk p) async {
final db = await database;
return await db.update(
'produk',
p.toJson(),
where: 'id = ?',
whereArgs: [p.id],
);
}
Future<int> hapusProduk(String id) async {
final db = await database;
return await db.delete('produk', where: 'id = ?', whereArgs: [id]);
}
Future<void> tutup() async {
final db = await database;
await db.close();
}
}Pola singleton memastikan hanya satu koneksi database. Method database memakai lazy init: database dibuat saat pertama dipakai.
Keamanan query
Selalu pakai whereArgs seperti contoh di atas, jangan menggabungkan string ke query. Ini mencegah SQL injection, bahkan di aplikasi lokal. Kebiasaan baik tidak mengenal skala project.
Migrasi skema
Saat menambah kolom di versi aplikasi berikutnya, naikkan version dan isi onUpgrade:
return await openDatabase(
path,
version: 2,
onCreate: _buatTabelV2,
onUpgrade: (db, oldVersion, newVersion) async {
if (oldVersion < 2) {
await db.execute('ALTER TABLE produk ADD COLUMN kategori TEXT');
}
},
);Tanpa migrasi, user yang update aplikasi akan crash atau kehilangan data.
Catatan teknis: Operasi database berjalan di background thread tapi tetap async, jadi selalu
await. Jangan menyimpan instance Database di widget, simpan di helper atau repository yang diakses lewat provider. Dan untuk id, pakai UUID string atau integer autoincrement yang konsisten, jangan mengandalkan index list karena index berubah saat item dihapus.