ObjectBox #
ObjectBox adalah database NoSQL berorientasi objek yang didesain untuk performa tinggi di perangkat mobile dan embedded. Ia menyimpan objek Dart secara langsung tanpa lapisan mapping SQL, menghasilkan kecepatan baca/tulis yang jauh melampaui SQLite. ObjectBox unggul ketika kamu butuh performa tinggi dan relasi antar objek tanpa kompleksitas SQL penuh.
Instalasi #
# pubspec.yaml
dependencies:
objectbox: ^4.0.3
objectbox_flutter_libs: any # library native (Android & iOS)
path: ^1.9.0
path_provider: ^2.1.5
dev_dependencies:
build_runner: ^2.4.13
objectbox_generator: any
# Setelah menambahkan dependency
flutter pub get
flutter pub run build_runner build --delete-conflicting-outputs
Entity — Mendefinisikan Model #
Entity adalah kelas Dart yang dipetakan ke “tabel” ObjectBox. Dekorasi dengan @Entity() dan pastikan ada field id bertipe int:
// models/produk.dart
import 'package:objectbox/objectbox.dart';
@Entity()
class Produk {
@Id()
int id; // wajib, int, auto-assign oleh ObjectBox
String nama;
double harga;
bool tersedia;
String kategori;
@Property(type: PropertyType.date)
DateTime createdAt;
// Field yang tidak disimpan ke database
@Transient()
bool isSelected = false;
Produk({
this.id = 0, // 0 = belum punya ID, ObjectBox akan assign saat put()
required this.nama,
required this.harga,
required this.tersedia,
required this.kategori,
required this.createdAt,
});
}
# Generate kode binding ObjectBox (objectbox.g.dart)
flutter pub run build_runner build --delete-conflicting-outputs
Store — Entry Point ObjectBox #
Store adalah titik masuk untuk semua operasi database. Buat satu instance dan gunakan sepanjang lifetime aplikasi:
// objectbox_store.dart
import 'package:objectbox/objectbox.dart';
import 'objectbox.g.dart'; // file generated
class ObjectBoxStore {
late final Store store;
ObjectBoxStore._create(this.store);
static Future<ObjectBoxStore> create() async {
final docsDir = await getApplicationDocumentsDirectory();
final store = await openStore(
directory: p.join(docsDir.path, 'objectbox'),
);
return ObjectBoxStore._create(store);
}
// Buat Box untuk setiap entity
Box<Produk> get produkBox => Box<Produk>(store);
Box<Pesanan> get pesananBox => Box<Pesanan>(store);
Box<Pelanggan> get pelangganBox => Box<Pelanggan>(store);
}
// main.dart
late ObjectBoxStore objectBox;
void main() async {
WidgetsFlutterBinding.ensureInitialized();
objectBox = await ObjectBoxStore.create();
runApp(const MyApp());
}
Box — Operasi CRUD #
Box<T> adalah antarmuka untuk semua operasi pada satu tipe entity:
final box = objectBox.produkBox; // Box<Produk>
// CREATE / UPDATE -- put() untuk keduanya
final produk = Produk(
nama: 'Flutter Book',
harga: 150000,
tersedia: true,
kategori: 'buku',
createdAt: DateTime.now(),
);
final id = box.put(produk); // return int ID yang di-assign
print('ID: $id'); // ID dari ObjectBox, misal: 1
// UPDATE -- modifikasi lalu put() lagi
produk.harga = 130000;
box.put(produk); // ID sudah ada, jadi ini update
// PUT BANYAK SEKALIGUS
final produkList = [produk1, produk2, produk3];
box.putMany(produkList); // lebih efisien dari loop put()
// READ -- berdasarkan ID
final ditemukan = box.get(1); // Produk? (null jika tidak ada)
// READ SEMUA
final semua = box.getAll(); // List<Produk>
// DELETE
box.remove(1); // hapus berdasarkan ID
box.removeMany([1, 2, 3]); // hapus banyak sekaligus
box.removeAll(); // hapus semua
// ASYNC -- untuk operasi di background
await box.putAsync(produk);
await box.putManyAsync(produkList);
// INFO
final count = box.count(); // jumlah item
final isEmpty = box.isEmpty(); // bool
final ada = box.contains(1); // cek berdasarkan ID
Query Builder — Filter dan Sort Data #
ObjectBox menyediakan query builder yang type-safe menggunakan property yang di-generate (Produk_):
// Kondisi dasar
final query = box.query(
Produk_.tersedia.equals(true)
).build();
final produkTersedia = query.find();
query.close(); // selalu close query setelah selesai!
// Multiple kondisi -- AND
final queryMahal = box.query(
Produk_.tersedia.equals(true)
.and(Produk_.harga.greaterThan(100000))
).build();
// Multiple kondisi -- OR
final queryFilter = box.query(
Produk_.kategori.equals('buku')
.or(Produk_.kategori.equals('alat tulis'))
).build();
// String query
final queryNama = box.query(
Produk_.nama.contains('flutter', caseSensitive: false)
).build();
// Range query
final queryHarga = box.query(
Produk_.harga.between(50000, 200000)
).build();
// Sort
final querySort = box.query()
.order(Produk_.harga) // ascending
.order(Produk_.nama, flags: Order.descending) // descending
.build();
// Limit dan offset
final queryPaginate = box.query()
.order(Produk_.createdAt, flags: Order.descending)
.build()
..limit = 20
..offset = 0;
final halaman1 = queryPaginate.find();
// Find first / unique
final satu = query.findFirst(); // Produk? -- hasil pertama
final unik = query.findUnique(); // Produk? -- pastikan hanya satu hasil
// Count tanpa load data
final jumlah = query.count();
// Selalu close!
query.close();
Relasi — ToOne dan ToMany #
ObjectBox mendukung relasi antar entity secara native:
ToOne — Relasi Satu ke Satu #
@Entity()
class Pesanan {
@Id()
int id;
String nomorPesanan;
double total;
// ToOne: satu pesanan punya satu pelanggan
final pelanggan = ToOne<Pelanggan>();
Pesanan({
this.id = 0,
required this.nomorPesanan,
required this.total,
});
}
@Entity()
class Pelanggan {
@Id()
int id;
String nama;
String email;
// Backlink: akses dari sisi Pelanggan ke semua pesanannya
@Backlink('pelanggan')
final pesanan = ToMany<Pesanan>();
Pelanggan({this.id = 0, required this.nama, required this.email});
}
// Penggunaan
final pelanggan = Pelanggan(nama: 'Budi', email: '[email protected]');
objectBox.pelangganBox.put(pelanggan);
final pesanan = Pesanan(nomorPesanan: 'ORD-001', total: 150000);
pesanan.pelanggan.target = pelanggan; // set relasi
objectBox.pesananBox.put(pesanan);
// Baca relasi -- lazy loaded
final targetPelanggan = pesanan.pelanggan.target; // Pelanggan?
print(targetPelanggan?.nama); // 'Budi'
ToMany — Relasi Satu ke Banyak / Banyak ke Banyak #
@Entity()
class Kategori {
@Id()
int id;
String nama;
// ToMany: satu kategori punya banyak produk
final produk = ToMany<Produk>();
Kategori({this.id = 0, required this.nama});
}
@Entity()
class Produk {
@Id()
int id;
String nama;
double harga;
// Backlink ke Kategori
@Backlink('produk')
final kategori = ToMany<Kategori>();
Produk({this.id = 0, required this.nama, required this.harga});
}
// Penggunaan
final kategoriElektronik = Kategori(nama: 'Elektronik');
final kategoriGadget = Kategori(nama: 'Gadget');
objectBox.kategoriBox.putMany([kategoriElektronik, kategoriGadget]);
final hp = Produk(nama: 'Smartphone X', harga: 5000000);
hp.kategori.addAll([kategoriElektronik, kategoriGadget]); // many-to-many!
objectBox.produkBox.put(hp);
// Baca relasi
final kategoris = hp.kategori; // ToMany<Kategori>
for (final k in kategoris) {
print(k.nama);
}
// Akses dari sisi Kategori (backlink)
final produkElektronik = kategoriElektronik.produk;
print('${produkElektronik.length} produk elektronik');
Reactive Streams — Watch Query #
ObjectBox mendukung query yang otomatis emit data baru saat database berubah:
// Watch box -- emit setiap ada perubahan apapun di box
final stream = box.query().watch(triggerImmediately: true);
stream.listen((query) {
final produk = query.find();
print('Produk berubah: ${produk.length} item');
});
// Watch query spesifik
final streamTersedia = box.query(
Produk_.tersedia.equals(true),
).watch(triggerImmediately: true)
.map((query) => query.find());
// Gunakan dengan StreamBuilder
StreamBuilder<List<Produk>>(
stream: streamTersedia,
builder: (context, snapshot) {
if (!snapshot.hasData) return const CircularProgressIndicator();
return ProdukList(produk: snapshot.data!);
},
)
Transaksi ACID #
ObjectBox mendukung transaksi penuh — semua operasi berhasil atau tidak sama sekali:
// Transaksi sinkron
objectBox.store.runInTransaction(TxMode.write, () {
final pelanggan = Pelanggan(nama: 'Alice', email: '[email protected]');
objectBox.pelangganBox.put(pelanggan);
final pesanan = Pesanan(nomorPesanan: 'ORD-001', total: 200000);
pesanan.pelanggan.target = pelanggan;
objectBox.pesananBox.put(pesanan);
// Jika ada exception di sini -- kedua put() akan di-rollback
});
// Transaksi async
await objectBox.store.runInTransactionAsync(TxMode.write, (store, _) {
final box = Box<Produk>(store);
// semua operasi di sini dalam satu transaksi
box.putMany(produkList);
});
Integrasi dengan Riverpod #
// Provider untuk ObjectBox store
final objectBoxProvider = Provider<ObjectBoxStore>((ref) {
throw UnimplementedError('Initialize in main()');
});
// Provider untuk Box
final produkBoxProvider = Provider<Box<Produk>>((ref) {
return ref.watch(objectBoxProvider).produkBox;
});
// StreamProvider untuk reactive data
final produkStreamProvider = StreamProvider<List<Produk>>((ref) {
final box = ref.watch(produkBoxProvider);
return box.query()
.order(Produk_.nama)
.watch(triggerImmediately: true)
.map((query) => query.find());
});
// Notifier untuk operasi CRUD
class ProdukNotifier extends AsyncNotifier<List<Produk>> {
@override
Future<List<Produk>> build() async {
final box = ref.watch(produkBoxProvider);
return box.getAll();
}
Future<void> tambah(Produk produk) async {
final box = ref.read(produkBoxProvider);
box.put(produk);
ref.invalidateSelf();
}
Future<void> hapus(int id) async {
final box = ref.read(produkBoxProvider);
box.remove(id);
ref.invalidateSelf();
}
}
// main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
final obx = await ObjectBoxStore.create();
runApp(
ProviderScope(
overrides: [
objectBoxProvider.overrideWithValue(obx),
],
child: const MyApp(),
),
);
}
ObjectBox Admin — GUI Debugging #
ObjectBox menyediakan web UI untuk inspect data secara real-time saat development:
# pubspec.yaml -- hanya untuk debug/development
dependencies:
objectbox_admin: any # tambahkan sementara untuk debugging
// Aktifkan Admin di main() -- HANYA saat development
if (kDebugMode) {
Admin(objectBox.store);
// Buka browser: http://localhost:8090
// Lihat semua entity, data, dan query secara real-time
}
Ringkasan #
- ObjectBox adalah database NoSQL berorientasi objek dengan performa tertinggi di Flutter — tercepat untuk baca dan tulis dibanding SQLite-based solutions.
- Definisikan Entity dengan
@Entity()dan@Id()— jalankanbuild_runneruntuk generate kode binding. Fieldidharus bertipeintdan di-assign0untuk objek baru.- Buat satu instance Store dan satu instance aplikasi — simpan di variable global atau inject via Provider/Riverpod.
Box<T>adalah antarmuka untuk operasi CRUD —put()untuk create dan update,get()untuk read,remove()untuk delete.- Query builder type-safe menggunakan property yang di-generate (
Entitas_.field) — selaluclose()query setelah selesai untuk menghindari memory leak.ToOne<T>untuk relasi satu-ke-satu,ToMany<T>untuk satu-ke-banyak dan banyak-ke-banyak. Relasi di-load secara lazy.box.query().watch()menghasilkan Stream yang otomatis emit data baru saat ada perubahan — sangat cocok untuk reactive UI.- Gunakan transaksi (
runInTransaction) untuk operasi yang harus atomic — semua berhasil atau semua dibatalkan.- ObjectBox tidak support web — jika butuh Flutter Web, gunakan Hive atau Drift.