sqflitedatabasecrudMahir5 mnt baca

sqflite: CRUD Dasar

Database lokal SQLite: buka, buat tabel, insert, baca, update, hapus.

Kenapa sqflite Ada: Masalah yang Diselesaikan

SharedPreferences hanya cocok untuk key-value sederhana. Bagaimana kalau aplikasimu butuh menyimpan ribuan baris data terstruktur yang bisa dicari, difilter, dan diurutkan, misalnya daftar produk, riwayat transaksi kasir, atau catatan, dan semuanya harus tetap bisa diakses offline?

Di sinilah sqflite masuk: plugin Flutter yang membungkus SQLite, database relasional ringan yang berjalan langsung di perangkat. Kamu dapat SQL lengkap (query, join, transaksi) tanpa server dan tanpa koneksi internet.

Contoh Dasar: Buka Database dan CRUD

bash
flutter pub add sqflite path
dart
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';

Future<Database> bukaDb() async {
  final pathDb = join(await getDatabasesPath(), 'warkop.db');
  return openDatabase(
    pathDb,
    version: 1,
    onCreate: (db, version) {
      return db.execute(
        'CREATE TABLE produk(id INTEGER PRIMARY KEY, nama TEXT, harga INTEGER)',
      );
    },
  );
}

// CREATE:
await db.insert('produk', {'nama': 'Kopi', 'harga': 15000});

// READ:
final semua = await db.query('produk');
final murah = await db.query(
  'produk',
  where: 'harga < ?',
  whereArgs: [10000],
);

// UPDATE:
await db.update(
  'produk',
  {'harga': 16000},
  where: 'id = ?',
  whereArgs: [1],
);

// DELETE:
await db.delete('produk', where: 'id = ?', whereArgs: [1]);

Contoh Nyata: DatabaseHelper Singleton dengan Migrasi

Aplikasi nyata butuh satu instance database (bukan buka-tutup berkali-kali) dan strategi migrasi saat skema berubah:

dart
class DatabaseHelper {
  DatabaseHelper._();
  static final DatabaseHelper instance = DatabaseHelper._();
  static Database? _db;

  Future<Database> get database async {
    _db ??= await _init();
    return _db!;
  }

  Future<Database> _init() async {
    final pathDb = join(await getDatabasesPath(), 'warkop.db');
    return openDatabase(
      pathDb,
      version: 2,
      onCreate: _onCreate,
      onUpgrade: _onUpgrade,
    );
  }

  Future<void> _onCreate(Database db, int version) async {
    await db.execute(
      'CREATE TABLE produk(id INTEGER PRIMARY KEY, nama TEXT, harga INTEGER)',
    );
    await db.execute(
      'CREATE TABLE transaksi(id INTEGER PRIMARY KEY, total INTEGER, waktu TEXT)',
    );
  }

  Future<void> _onUpgrade(Database db, int oldV, int newV) async {
    // user yang update dari v1 ke v2 dapat kolom baru tanpa kehilangan data:
    if (oldV < 2) {
      await db.execute('ALTER TABLE produk ADD COLUMN stok INTEGER DEFAULT 0');
    }
  }

  Future<int> tambahProduk(String nama, int harga) async {
    final db = await database;
    return db.insert('produk', {'nama': nama, 'harga': harga});
  }

  Future<List<Map<String, Object?>>> semuaProduk() async {
    final db = await database;
    return db.query('produk', orderBy: 'nama ASC');
  }
}

Edge Case yang Sering Menjebak

  • Lupa menaikkan version setelah mengubah skema: onCreate hanya jalan sekali saat database pertama dibuat. Perubahan skema untuk user lama harus lewat onUpgrade dengan version yang naik.
  • Buka database berkali-kali: setiap openDatabase itu mahal. Pakai pola singleton seperti di atas supaya hanya satu koneksi.
  • Query berat di thread UI: sqflite berjalan di thread utama. Query ribuan baris dengan join kompleks bisa menyebabkan jank. Untuk kasus ekstrem, pindahkan ke isolate atau sederhanakan query-nya.
  • Transaksi untuk operasi ganda: kalau insert ke dua tabel harus sukses bersamaan (misalnya transaksi kasir dan pengurangan stok), bungkus dalam db.transaction supaya tidak ada data setengah jadi saat aplikasi crash di tengah jalan.
  • conflictAlgorithm: insert dengan id yang sudah ada akan error. Pakai conflictAlgorithm: ConflictAlgorithm.replace kalau memang ingin menimpa.

Kapan JANGAN Pakai sqflite

  • Hanya butuh simpan beberapa nilai sederhana: SharedPreferences jauh lebih ringan.
  • Data murni dari cloud yang selalu butuh internet: sinkronisasi dua arah itu kompleks; pertimbangkan backend-as-a-service seperti Supabase atau Firebase.
  • Butuh query reaktif yang kompleks: package seperti drift memberi type-safety dan stream query yang lebih nyaman untuk skema besar.
  • File atau blob besar (foto, video): simpan file-nya di direktori aplikasi (path_provider) dan simpan path-nya saja di database.

Catatan teknis: Selalu pakai whereArgs (parameterized query), jangan pernah menggabungkan string ke query. Ini mencegah SQL injection sekaligus menangani escaping otomatis, termasuk untuk nama produk yang mengandung tanda kutip seperti "Kopi 'Spesial'".

Tantangan

CRUD catatan

Buat tabel catatan(id, isi TEXT), lalu tulis fungsi tambahCatatan(isi), ambilSemua(), dan hapusCatatan(id).