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 adalahhive_cedanhive_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
awaitviaHive.box<T>('nama').- Untuk custom class, buat
TypeAdapterdengan anotasi@HiveTypedan@HiveField— jalankanbuild_runneruntuk generate kode. Jangan pernah mengubahtypeIdatau indeks field setelah data disimpan.HiveObjectmemberikan methodsave()dandelete()langsung pada objek — tidak perlu menyimpan key secara terpisah.- Gunakan
LazyBoxuntuk 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.