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() — jalankan build_runner untuk generate kode binding. Field id harus bertipe int dan di-assign 0 untuk 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) — selalu close() 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.

← Sebelumnya: Hive   Berikutnya: Drift →

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