Hive #

Hive adalah database NoSQL yang ringan dan sangat cepat, ditulis murni dalam Dart. Ia menyimpan data dalam format binary yang efisien menggunakan konsep Box — mirip tabel, tapi lebih fleksibel. Hive sangat cocok sebagai cache layer untuk data dari API: lebih cepat dari SharedPreferences untuk data kompleks, lebih mudah dari ObjectBox atau Drift untuk kasus tanpa relasi antar objek.

Catatan: Hive pernah ditinggalkan oleh pembuatnya dan kini dilanjutkan oleh komunitas sebagai Hive CE (Community Edition). Package yang aktif dirawat adalah hive_ce dan hive_ce_flutter. Gunakan package ini untuk proyek baru.

Instalasi #

# pubspec.yaml
dependencies:
  hive_ce: ^2.9.0
  hive_ce_flutter: ^2.2.0   # integrasi Flutter (init, path provider)

dev_dependencies:
  build_runner: ^2.4.13
  hive_ce_generator: ^1.8.0  # code generation untuk TypeAdapter

Inisialisasi #

// main.dart
import 'package:hive_ce_flutter/hive_flutter.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // Inisialisasi Hive dengan direktori aplikasi
  await Hive.initFlutter();

  // Daftarkan adapter untuk custom class (sebelum openBox)
  Hive.registerAdapter(ProdukAdapter());
  Hive.registerAdapter(KategoriAdapter());

  // Buka box yang sering digunakan saat startup
  await Hive.openBox('settings');
  await Hive.openBox<Produk>('produk');

  runApp(const MyApp());
}

Box — Konsep Dasar Hive #

Box adalah wadah data di Hive — mirip tabel tapi berbasis key-value. Setiap nilai di Box diakses via key (String atau int):

// Buka box (jika sudah dibuka sebelumnya, langsung return instance)
final settingsBox = await Hive.openBox('settings');
final produkBox = await Hive.openBox<Produk>('produk');

// TULIS
await settingsBox.put('tema', 'gelap');           // key String
await settingsBox.put('versi_app', 2);
await produkBox.put('produk-123', produk);        // key String

// Auto-increment key (seperti ID database)
await produkBox.add(produkBaru);                  // key int yang auto-increment

// BACA
final tema = settingsBox.get('tema');             // dynamic
final tema2 = settingsBox.get('tema', defaultValue: 'terang');  // dengan default
final produk = produkBox.get('produk-123');       // Produk?

// BACA SEMUA
final semuaProduk = produkBox.values.toList();    // List<Produk>
final semuaKey = produkBox.keys.toList();         // List<dynamic>

// UPDATE (sama dengan put -- replace value yang ada)
await produkBox.put('produk-123', produkUpdated);

// HAPUS
await produkBox.delete('produk-123');             // hapus satu
await produkBox.deleteAll(['produk-1', 'produk-2']); // hapus banyak
await produkBox.clear();                          // hapus semua

// QUERY SEDERHANA
final tersedia = produkBox.values
    .where((p) => p.tersedia)
    .toList();

// CEK
final ada = produkBox.containsKey('produk-123'); // bool
final panjang = produkBox.length;                 // int

// AKSES BOX YANG SUDAH TERBUKA (dari mana saja, tanpa await)
final box = Hive.box<Produk>('produk');

TypeAdapter — Simpan Custom Class #

Untuk menyimpan objek Dart kustom, Hive butuh TypeAdapter yang mengajarkan cara serialize/deserialize objek:

Dengan Code Generation (Direkomendasikan) #

// models/produk.dart
import 'package:hive_ce/hive.dart';

part 'produk.g.dart';

@HiveType(typeId: 0)  // typeId unik (0-223), tidak boleh berubah!
class Produk extends HiveObject {
  @HiveField(0)
  late String id;

  @HiveField(1)
  late String nama;

  @HiveField(2)
  late double harga;

  @HiveField(3)
  late bool tersedia;

  @HiveField(4)
  late String kategori;

  // HiveField baru bisa ditambahkan di indeks berikutnya (5, 6, dst.)
  // JANGAN ubah indeks yang sudah ada -- akan merusak data yang tersimpan!
  @HiveField(5, defaultValue: 0)
  late int stok;
}
# Generate TypeAdapter
flutter pub run build_runner build --delete-conflicting-outputs

Ini menghasilkan produk.g.dart berisi ProdukAdapter.

Aturan Penting TypeAdapter #

typeId (0-223):
  ✓ Unik per class -- tidak ada dua class dengan typeId yang sama
  ✓ Tidak pernah berubah setelah data disimpan
  ✗ Jangan reuse typeId dari class yang sudah dihapus

HiveField indeks:
  ✓ Unik per field dalam satu class
  ✓ Tidak pernah berubah setelah data disimpan
  ✓ Field baru selalu gunakan indeks yang belum dipakai
  ✗ Jangan ubah indeks existing -- data lama tidak bisa dibaca
  ✓ Field yang dihapus: biarkan indeks-nya "reserved", jangan reuse

HiveObject — Model yang Lebih Powerful #

HiveObject memberikan method tambahan pada setiap objek yang disimpan:

@HiveType(typeId: 1)
class Catatan extends HiveObject {
  @HiveField(0)
  late String judul;

  @HiveField(1)
  late String isi;

  @HiveField(2)
  late DateTime createdAt;
}

// Penggunaan
final box = Hive.box<Catatan>('catatan');
final catatan = Catatan()
  ..judul = 'Belanja'
  ..isi = 'Susu, telur, roti'
  ..createdAt = DateTime.now();

// Simpan dan langsung punya key
await box.add(catatan);

// Update langsung dari objek -- tidak perlu tahu key-nya
catatan.judul = 'Belanja Mingguan';
await catatan.save();  // HiveObject method!

// Hapus langsung dari objek
await catatan.delete();  // HiveObject method!

// Cek apakah masih ada di box
final masihAda = catatan.isInBox;  // bool

Lazy Box — Efisien untuk Data Besar #

LazyBox tidak memuat semua data ke memory saat dibuka — ia hanya membaca nilai saat diminta:

// Buka sebagai lazy box
final lazyBox = await Hive.openLazyBox<Artikel>('artikel');

// Baca -- async karena dibaca dari disk saat diperlukan
final artikel = await lazyBox.get('artikel-123');  // Artikel?

// Sangat cocok untuk:
// - Box dengan ratusan/ribuan item
// - Item yang berukuran besar (artikel panjang, gambar binary)
// - Tidak perlu semua item sekaligus

Enkripsi AES-256 #

Hive mendukung enkripsi built-in menggunakan AES-256-CBC:

import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:hive_ce_flutter/hive_flutter.dart';

Future<Box> openEncryptedBox(String boxName) async {
  const secureStorage = FlutterSecureStorage();

  // Ambil atau buat encryption key
  var encryptionKeyBase64 = await secureStorage.read(key: '${boxName}_key');
  if (encryptionKeyBase64 == null) {
    // Buat key baru dan simpan di secure storage
    final key = Hive.generateSecureKey();
    encryptionKeyBase64 = base64UrlEncode(key);
    await secureStorage.write(key: '${boxName}_key', value: encryptionKeyBase64);
  }

  final encryptionKey = base64Url.decode(encryptionKeyBase64);

  return Hive.openBox(
    boxName,
    encryptionCipher: HiveAesCipher(encryptionKey),
  );
}

// Penggunaan
final sensitiveBox = await openEncryptedBox('sensitive_data');
await sensitiveBox.put('pin', '123456');

Hive sebagai Cache Layer #

Pola yang paling umum: cache data dari API di Hive, tampilkan cache saat offline atau saat data masih fresh:

class ProdukCache {
  static const _boxName = 'produk_cache';
  static const _ttlKey = 'produk_cached_at';
  static const _ttlDuration = Duration(minutes: 30);

  Future<Box<Produk>> get _box async => Hive.box<Produk>(_boxName);
  Future<Box> get _metaBox async => Hive.box('cache_meta');

  Future<void> save(List<Produk> produk) async {
    final box = await _box;
    final meta = await _metaBox;

    await box.clear();
    // Simpan dengan key = ID produk
    final map = {for (final p in produk) p.id: p};
    await box.putAll(map);
    await meta.put(_ttlKey, DateTime.now().millisecondsSinceEpoch);
  }

  Future<List<Produk>?> load() async {
    final meta = await _metaBox;
    final cachedAtMs = meta.get(_ttlKey) as int?;
    if (cachedAtMs == null) return null;

    final cachedAt = DateTime.fromMillisecondsSinceEpoch(cachedAtMs);
    if (DateTime.now().difference(cachedAt) > _ttlDuration) {
      return null;  // cache expired
    }

    final box = await _box;
    return box.values.toList();
  }

  Future<void> invalidate() async {
    final box = await _box;
    final meta = await _metaBox;
    await box.clear();
    await meta.delete(_ttlKey);
  }
}

// Repository yang menggunakan cache
class ProdukRepository {
  final ProdukRemoteDataSource _remote;
  final ProdukCache _cache;

  Future<List<Produk>> getProduk() async {
    // Coba dari cache dulu
    final cached = await _cache.load();
    if (cached != null) return cached;

    // Fetch dari API
    final fresh = await _remote.getProduk();
    await _cache.save(fresh);
    return fresh;
  }
}

Integrasi dengan Riverpod #

// Provider untuk box yang sudah terbuka
final produkBoxProvider = Provider<Box<Produk>>((ref) {
  return Hive.box<Produk>('produk');
});

// Notifier yang menggunakan Hive
class ProdukCacheNotifier extends Notifier<List<Produk>> {
  @override
  List<Produk> build() {
    final box = ref.watch(produkBoxProvider);
    return box.values.toList();
  }

  Future<void> refresh() async {
    final produk = await ref.read(produkRemoteProvider).getProduk();
    final box = ref.read(produkBoxProvider);
    await box.clear();
    await box.putAll({for (final p in produk) p.id: p});
    state = box.values.toList();
  }
}

Kapan Beralih ke Database Lain? #

Tetap di Hive jika:
  ✓ Data sederhana tanpa relasi antar objek
  ✓ Cache response API yang bentuknya flat
  ✓ Butuh enkripsi built-in yang mudah
  ✓ Perlu support web (Flutter Web)

Pertimbangkan ObjectBox jika:
  ✗ Data punya relasi (Pesanan → Produk → Kategori)
  ✗ Butuh query yang lebih kompleks (filter, sort, range)
  ✗ Jumlah data sangat besar (ribuan item)
  ✗ Butuh performa baca/tulis yang lebih tinggi

Pertimbangkan Drift jika:
  ✗ Butuh JOIN antar tabel
  ✗ Butuh SQL penuh dengan GROUP BY, aggregasi
  ✗ Schema data sering berevolusi dan butuh migrasi terkelola

Ringkasan #

  • Hive adalah database NoSQL berbasis key-value yang sangat cepat dan ringan — cocok sebagai cache layer untuk data dari API.
  • Gunakan hive_ce (Community Edition) untuk proyek baru — package original Hive tidak lagi aktif dikembangkan.
  • Data disimpan dalam Box — buka sekali saat startup, akses dari mana saja tanpa await via Hive.box<T>('nama').
  • Untuk custom class, buat TypeAdapter dengan anotasi @HiveType dan @HiveField — jalankan build_runner untuk generate kode. Jangan pernah mengubah typeId atau indeks field setelah data disimpan.
  • HiveObject memberikan method save() dan delete() langsung pada objek — tidak perlu menyimpan key secara terpisah.
  • Gunakan LazyBox untuk box dengan banyak item besar — data hanya dibaca dari disk saat benar-benar dibutuhkan.
  • Enkripsi AES-256 built-in — simpan encryption key di flutter_secure_storage, bukan di kode atau SharedPreferences biasa.
  • Hive ideal untuk cache. Jika data punya relasi atau butuh query kompleks, pertimbangkan ObjectBox atau Drift.

← Sebelumnya: SharedPreferences   Berikutnya: ObjectBox →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact