Discover ObjectBox: The Lightning-Fast Mobile Database for Persistent Object Storage. Streamline Your Workflow, Eliminate Repetitive Tasks, and Enjoy a User-Friendly Data Interface.
Add ObjectBox to your project
You can get ObjectBox from the Central repository. To add ObjectBox to your Android project, follow these steps:
Open the Gradle build file of your root project (not the ones for your app or module subprojects) and add a global variable for the version and the ObjectBox Gradle plugin:
/build.gradle(.kts)
buildscript {ext.objectboxVersion="4.0.2"// For Groovy build scripts// val objectboxVersion by extra("4.0.2") // For KTS build scripts repositories {mavenCentral() } dependencies {// Android Gradle Plugin 4.1.0 or later supportedclasspath("com.android.tools.build:gradle:8.1.0")classpath("io.objectbox:objectbox-gradle-plugin:$objectboxVersion") }}
Open the Gradle build file for your app or module subproject and, after the com.android.application plugin, apply the io.objectbox plugin:
/app/build.gradle(.kts)
// Using plugins syntax:plugins {id("com.android.application")id("kotlin-android")// Only for Kotlin projectsid("kotlin-kapt")// Only for Kotlin projectsid("io.objectbox")// Apply last}// Or using the old apply syntax:apply plugin:"com.android.application"apply plugin:"kotlin-android"// Only for Kotlin projectsapply plugin:"kotlin-kapt"// Only for Kotlin projectsapply plugin:"io.objectbox"// Apply last
If you encounter any problems in this or later steps, check out the FAQ and Troubleshooting pages.
Then do "Sync Project with Gradle Files" in Android Studio so the Gradle plugin automatically adds the required ObjectBox libraries and code generation tasks.
Optional: Advanced Setup
The ObjectBox plugin uses reasonable defaults and detects most configurations automatically. However, if needed you can configure the model file path, the MyObjectBox package, enable debug mode and more using advanced setup options.
ObjectBox for Java supports JVM (Linux, macOS, Windows) Java or Kotlin projects.
Open the Gradle build file of your root project and add a global variable for the version and the ObjectBox Gradle plugin:
/build.gradle(.kts)
buildscript { ext.objectboxVersion ="4.0.2"// For Groovy build scripts// val objectboxVersion by extra("4.0.2") // For KTS build scripts repositories { mavenCentral() } dependencies { classpath("io.objectbox:objectbox-gradle-plugin:$objectboxVersion") }}
Open the Gradle build file for your subproject and, after other plugins, apply the io.objectbox plugin:
/app/build.gradle(.kts)
// Using plugins syntax:plugins { id("java-library") // or org.jetbrains.kotlin.jvm for Kotlin projects. id("io.objectbox") // Apply last.}// Or using the old apply syntax:apply plugin: "java-library"// or org.jetbrains.kotlin.jvm for Kotlin projects.apply plugin: "io.objectbox"// Apply last.
Using your IDE of choice with a Gradle project might require additional configuration. E.g.
For IntelliJ IDEA see the help page for Gradle. Also make sure to use version 2019.1 or newer as it has improved Gradle support, like delegating build actions to Gradle.
ObjectBox is an object database running mostly in native code written in C/C++ for optimal performance. Thus, ObjectBox will load a native library: a “.dll” on Windows, a “.so” on Linux, and a “.dylib” on macOS. By default, the ObjectBox Gradle plugin adds a dependency to the native library matching your system. This means that your app is already set up to run on your system.
ObjectBox binaries are built for 64-bit systems for best performance. Talk to us if you require 32-bit support.
To add native libraries for all platforms that your app supports and additional configuration options (configure the model file path, the MyObjectBox package, enable debug mode) see:
# Or when using ObjectBox Sync instead run:
flutter pub add objectbox objectbox_sync_flutter_libs:any
flutter pub add --dev build_runner objectbox_generator:any
To run unit tests on your machine, download the latest native ObjectBox library for your machine by running this script in a bash shell (e.g. Git Bash on Windows):
For ObjectBox Sync, append the --sync argument to above command.
This should add lines like this to your pubspec.yaml:
dependencies:objectbox:^4.0.2objectbox_flutter_libs:any# For ObjectBox Sync this dependency should appear instead:# objectbox_sync_flutter_libs: anydev_dependencies:build_runner:^2.0.0objectbox_generator:any
If you added the above lines manually, then install the packages with flutter pub get.
For ObjectBox Sync, also increase the Android minSdkVersion to at least 21:
# /android/app/build.gradle
android {
defaultConfig {
// ObjectBox Sync requires at least SDK 21 (Android 5.0)
minSdkVersion 21
}
}
For all macOS apps need to target macOS 10.15: in Podfile change the platform and in the Runner.xcodeproj/poject.pbxproj file update MACOSX_DEPLOYMENT_TARGET.
For sandboxed macOS apps specify an application group. Check all macos/Runner/*.entitlements files if they contain a <dict> section with the group ID. If necessary, change the string value to the DEVELOPMENT_TEAM you can find in your Xcode settings, plus an application-specific suffix. Due to macOS restrictions the complete string must be 19 characters or shorter. For example:
Then, in your app code, pass the same string when opening the Store. For example: openStore(macosApplicationGroup: 'FGDTDLOBXDJ.demo').
For Linux Desktop apps: the Flutter snap ships with an outdated version of CMake. Install Flutter manually instead to use the version of CMake installed on your system.
For iOS using Flutter 3.0 or older: increase the deployment target in XCode to iOS 12 and, under Architectures, replace ${ARCHS_STANDARD} with arm64 (or $ARCHS_STANDARD_64_BIT).
# Or when using ObjectBox Sync instead run:
bash <(curl -s https://raw.githubusercontent.com/objectbox/objectbox-dart/main/install.sh) --sync
By default the library is downloaded into the lib subdirectory of the working directory. It's not necessary to install the library system-wide. This also allows to use different versions for different projects. For details see below.
Deploying Dart Native projects
Natively compiled Dart applications that use ObjectBox Dart require a reference to the objectbox-c library. Hence, the shared library file downloaded with install.sh needs to be shipped with the executable.
The install.sh script downloads the library by default to the lib subdirectory of the working directory. An executable using ObjectBox Dart looks for the library in this lib directory.
If it is not found there, it falls back to using system directories (using Dart's DynamicLibrary.open):
Windows: working directory and %WINDIR%\system32.
macOS: /usr/local/lib (and maybe others).
Linux: /lib and /usr/lib (again, possibly others).
ObjectBox for Python is available via PyPI:
Stable Version (4.0.0):
pipinstall--upgradeobjectbox
Define Entity Classes
Define your model by adding an @Entity (internal name for database objects) annotation to at least one class and an @Id annotation to one of the class variables. Learn more about the ObjectBox model here.
A simple entity representing a user could look like this:
When using a data class, add default values for all parameters. This will ensure your data class will have a constructor that can be called by ObjectBox. (Technically this is only required if adding properties to the class body, like custom or transient properties or relations, but it's a good idea to do it always.)
Avoid naming properties like reserved Java keywords, like private and default. ObjectBox tooling works with the Java representation of your Kotlin code to be compatible with both Java and Kotlin. It will ignore such properties.
models.dart
@Entity()classUser {@Id()int id =0;String? name;@Property(type:PropertyType.date) // Store as int in millisecondsDateTime? date;@Transient() // Ignore this property, not stored in the database.int? computedProperty;}
You can have multiple entities in the same file (here models.dart), or you can have them spread across multiple files in your package's lib directory.
model.py
from objectbox import*@Entity()classUser:id= Id name = String
Important:
Entities must have one ID property of type long (or Long in Kotlin, int in Dart). If you need to use other types, like a String ID, see the @Id annotation docs. Also, it must have non-private visibility (or non-private getter and setter methods).
Entities must also have a no-args constructor, or for better performance, a constructor with all properties as arguments. In the above example, a default, no-args constructor is generated by the compiler.
For a deeper explanation and a look at all other available annotations (e.g. for relations and indexes) check the Entity Annotations page.
Generate ObjectBox code
Next, we generate some binding code based on the model defined in the previous step.
Build your project to generate the classes required to use ObjectBox, for example using Build > Make Project in Android Studio.
Note: If you make significant changes to your entities, e.g. by moving them or modifying annotations, make sure to rebuild the project so generated ObjectBox code is updated.
To generate the binding code required to use ObjectBox run
dart run build_runner build
ObjectBox generator will look for all @Entity annotations in your lib folder and create
a single database definition lib/objectbox-model.json and
supporting code in lib/objectbox.g.dart.
To customize the directory where generated files are written see Advanced Setup.
If you make changes to your entities, e.g. by adding a property or modifying annotations, or after the ObjectBox library has updated make sure to re-run the generator so generated ObjectBox code is updated.
You typically commit the generated code file objectbox.g.dart to your version control system (e.g. git) to avoid having to re-run the generator unless there are changes.
Actually we lied above. The generator will process lib and test folders separately and generate files for each one (if @Entity classes exist there). This allows to create a separate test database that does not share any of the entity classes with the main database.
Python bindings offer a convenient default Model to which Entity definitions are automatically associated if not specified otherwise.
Similar to the other bindings, a JSON model file is also used for management of Schema history (i.e. to handle add/remove/rename of Entity and Property).
Among other files ObjectBox generates a JSON model file, by default to
app/objectbox-models/default.json for Android projects,
lib/objectbox-model.json for Dart/Flutter projects, or
<user-module-dir>/objectbox-model.json for Python projects
In Android Studio you might have to switch the Project view from Android to Project to see the default.json model file.
Python checks for the call-stack to determine the user-module directory in which the JSON file is stored.
This JSON file changes when you change your entity classes (or sometimes with a new version of ObjectBox).
Keep this JSON file, commit the changes to version control!
This file keeps track of unique IDs assigned to your entities and properties. This ensures that an older version of your database can be smoothly upgraded if your entities or properties change.
BoxStore (Java) or Store (Dart) is the entry point for using ObjectBox. It is the direct interface to the database and manages Boxes. Typically, you want to only have a single Store (single database) and keep it open while your app is running, not closing it explicitly.
Create it using the builder returned by the generated MyObjectBox class, for example in a small helper class like this:
The best time to initialize ObjectBox is when your app starts. For a command line app this is typically inside the main method.
Create it using the generated openStore() method, for example in a small helper class like this:
import'package:path/path.dart'as p;import'package:path_provider/path_provider.dart';import'objectbox.g.dart'; // created by `flutter pub run build_runner build`classObjectBox {/// The Store of this app.latefinalStore store;ObjectBox._create(this.store) {// Add any additional setup code, e.g. build queries. }/// Create an instance of ObjectBox to use throughout the app.staticFuture<ObjectBox> create() async {final docsDir =awaitgetApplicationDocumentsDirectory();// Future<Store> openStore() {...} is defined in the generated objectbox.g.dartfinal store =awaitopenStore(directory: p.join(docsDir.path, "obx-example"));returnObjectBox._create(store); }}
The best time to initialize ObjectBox is when your app starts. We suggest to do it in your app's main() function:
/// Provides access to the ObjectBox Store throughout the app.lateObjectBox objectbox;Future<void> main() async {// This is required so ObjectBox can get the application directory// to store the database in.WidgetsFlutterBinding.ensureInitialized(); objectbox =awaitObjectBox.create();runApp(MyApp());}
On mobile devices/sandboxed apps, data should be stored in the app's documents directory. See Flutter: read & write files for more info. This is exactly what openStore()does, if the directory argument is not specified.
On desktop systems it is recommended to specify a directory to create a custom sub-directory to avoid conflicts with other apps.
If your code passes a directory that the application can't write to, you get an error that looks somewhat like this: failed to create store: 10199 Dir does not exist: objectbox (30).
Create it using the generated openStore() method, for example like this:
import'objectbox.g.dart'; // created by `dart pub run build_runner build`voidmain() {// Store openStore() {...} is defined in the generated objectbox.g.dartfinal store =openStore();// your app code ... store.close(); // don't forget to close the store}
The above minimal example omits the argument to (directory: ), using the default - ./objectbox - in the current working directory.
from objectbox import*store =Store()
It is possible to specify various options when building a store. Notably for testing or caching, to use an in-memory database that does not create any files:
final inMemoryStore =Store(getObjectBoxModel(), directory:"memory:test-db");
store =Store(directory="memory:testdata")
For more store configuration options: for Java see the BoxStoreBuilder and for Dart the Store documentation. (Python APIs will be published soon)
Basic Box operations
The Box class is likely the class you interact with most. A Box instance gives you access to objects of a particular type. For example, if you have User and Order entities, you need a Box object to interact with each:
These are some of the operations offered by the Box class:
put inserts a new object or updates an existing one (with the same ID). When inserting, an ID will be assigned to the just inserted object (this will be explained below) and returned. put also supports putting multiple objects, which is more efficient.
User user =newUser("Tina");userBox.put(user);List<User> users =getNewUsers();userBox.put(users);
val user =User(name ="Tina")userBox.put(user)val users: List<User> =getNewUsers()userBox.put(users)
final user =User(name:'Tina');userBox.put(user);final users =getNewUsers();userBox.putMany(users);
user =User(name="Tina")user_box.put(user)users =get_new_users()user_box.put(*users)
get and getAll: Given an object’s ID, get reads it from its box. To get all objects in the box use getAll .
User user =userBox.get(userId);List<User> users =userBox.getAll();
val user = userBox[userId]val users = userBox.all
final user = userBox.get(userId);final users = userBox.getMany(userIds);final users = userBox.getAll();
user = user_box.get(user_id)users = user_box.get_all()
query: Starts building a query to return objects from the box that match certain conditions. See queries for details.
remove and removeAll: Remove a previously put object from its box (deletes it). remove also supports removing multiple objects, which is more efficient. removeAll removes (deletes) all objects in a box.
count: Returns the number of objects stored in this box.
long userCount = userBox.count();
val userCount = userBox.count()
final userCount = userBox.count();
user_box.count()
For a complete list of methods available in the Box class, check the API reference documentation for Java or Dart.
Asynchronous operations
ObjectBox has built-in support to run (typically multiple or larger) database operations asynchronously.
runInTxAsync and callInTxAsync: runs the given Runnable/Callable in a transaction on a background thread (the internal ObjectBox thread pool) and calls the given callback once done. In case of callInTxAsync the callback also receives the returned result.
store.callInTxAsync(() -> {
Box<User> box = store.boxFor(User.class);
String name = box.get(userId).name;
box.remove(userId);
return text;
}, (result, error) -> {
if (error != null) {
System.out.println("Failed to remove user with id " + userId);
} else {
System.out.println("Removed user with name: " + result);
}
});
awaitCallInTx (Kotlin Coroutines only): wraps callInTxAsync in a coroutine that suspends until the transaction has completed. Likewise, on success the return value of the given callable is returned, on failure an exception is thrown.
try {
val name = store.awaitCallInTx {
val box = store.boxFor(User::class.java)
val name = box.get(userId).name
box.remove(userId)
name
}
println("Removed user with name $name")
} catch (e: Exception) {
println("Failed to remove user with id $userId")
}
Most Box methods do have async versions which run the operation in a worker isolate.
For example putAsync: asynchronously inserts a new object or updates an existing one (with the same ID). The returned future completes when the object is successfully written to the database.
final user = User(name: 'Tina');
Future<int> idFuture = userBox.putAsync(user);
...
final id = await idFuture;
userBox.get(id); // after the future completed, the object is inserted
To run multiple operations, it is more efficient to wrap the synchronous calls in an asynchronous transaction with runInTransactionAsync (API reference): run a callback with multiple database operations within a write or read transaction in the background without blocking the user interface. Can return results.
// The callback must be a function that can be sent to an isolate:
// either a top-level function, static method or a closure that only
// captures objects that can be sent to an isolate.
String? readNameAndRemove(Store store, int objectId) {
var box = store.box<User>();
final nameOrNull = box.get(objectId)?.name;
box.remove(objectId);
return nameOrNull;
}
final nameOrNull =
await store.runInTransactionAsync(TxMode.write, readNameAndRemove, objectId);
There is also runAsync (API reference): like runInTransactionAsync but does not start a transaction, leaving that to your callback code. This allows to supply a callback that is an async function.
If it is necessary to call put many times in a row, take a look at putQueued: Schedules the given object to be put later on, by an asynchronous queue, returns the id immediately even though the object may not have been written yet. You can use Store's awaitQueueCompletion() or awaitQueueSubmitted() to wait for the async queue to finish.
for (int i = 0; i < 100; i++) {
userBox.putQueued(User(name: 'User $i'));
}
// Optional: wait until submitted items are processed.
store.awaitQueueSubmitted();
expect(userBox.count(), equals(100));
Currently work in progress.
Object IDs
By default IDs for new objects are assigned by ObjectBox. When a new object is put, it will be assigned the next highest available ID:
User user = new User();
// user.id == 0
box.put(user);
// user.id != 0
long id = user.id;
val user = User()
// user.id == 0
box.put(user)
// user.id != 0
val id = user.id
final user = User();
// user.id == 0
box.put(user);
// user.id != 0
final id = user.id;
user = User()
box.put(user)
id: int = user.id
For example, if there is an object with ID 1 and another with ID 100 in a box, the next new object that is put will be assigned ID 101.
If you try to assign a new ID yourself and put the object, ObjectBox will throw an error.
DaoCompat is a compatibility layer that gives you a greenDAO like API for ObjectBox. It makes switching from greenDAO to ObjectBox simple. Have a look at the documentation and the example. Contact us if you have any questions!