This tutorial will help you get started with the ObjectBox Flutter Database. We will create a simple task-list app using all ObjectBox CRUD operations (Create, Read, Update, Delete). Additionally, we will support adding a tag to each task by setting up a to-one relation between tasks and tags. The pure Dart ObjectBox API is very easy to use, as you will see by going through the steps outlined below.
Users can enter a new task, choose which tag to apply and add the task. All added tasks are displayed as a checklist with the associated tag. Users can mark a task as finished by ticking its checkbox and delete it by swiping it away.
Each task entry also shows the date when it was created or finished, depending on the state of the task.
How to start using the ObjectBox Database in your Flutter app
Add the library
Please refer to the Getting Started page for up-to-date information about adding the ObjectBox dependencies to your project.
Create a model file
ObjectBox is an object-oriented non-relational (NoSQL) database. First, we need to tell the database which entities to store. We can do this by defining a model and then using the build_runner to generate the binding code. The model is defined by writing Dart classes and annotating them.
Create a model file (e.g. model.dart), where we want to define two entities: one for task tags and one for tasks. These are just Dart classes with the @Entity annotation. Each entity must have an ID property of type int, which serves as the unique identifier. When this is called “id”, the property is recognized automatically but if you want to use a different name, annotate it with “@Id()”. In the Tag class, we also create a String property for the tag name. Here is how the model will look. Don’t worry about the objectbox.g.dart import for now – this file will be generated later.
1
2
3
4
5
6
7
8
9
10
11
12
import'package:intl/intl.dart';
import'package:objectbox/objectbox.dart';
import'objectbox.g.dart';
@Entity()
classTag{
intid;
Stringname;
Tag(this.name,{this.id=0});
}
Additional properties of our Task entity include a String for the task’s text and two DateTime properties for the date when a task was created and finished. Then we also define a to-one relation between Tasks and Tags. This is needed so that one can assign a tag to each task created within the app. We’ll come back to how relations work at the end of this tutorial.
1
2
3
4
5
6
7
8
9
10
11
12
@Entity()
classTask{
intid;
Stringtext;
DateTime dateCreated;
DateTime?dateFinished;
Task(this.text,{this.id=0,DateTime?dateCreated})
:dateCreated=dateCreated??DateTime.now();
finaltag=ToOne<Tag>();
}
Generate binding code
Once our model is done, we generate the ObjectBox binding code by running flutter pub run build_runner build. This will create the objectbox.g.dart file from the import above. You will need to do this every time you update the model (e.g. by adding or removing an entity or a property), and ObjectBox will take care of the change. However, in cases like entity renaming, you will need to provide ObjectBox with more information. Read more about data model updates in the ObjectBox docs.
Create a Store
Store represents an ObjectBox database and works together with Boxes to allow getting and putting. A Box instance gives you access to objects of a particular type. One would typically create one Store for their app and a number of Boxes that depends on the number of entities they want to store in the database.
In a separate file, e.g. objectbox.dart, we define the ObjectBox class that will help us create the store. We only need a single database store where we get two Boxes – one for each object type. Let’s call these taskBox and tagBox.
So that our app can always display the current list of tasks without the need to actively refresh it, we create a data stream. Within ObjectBox, we can stream data using queries. We query all tasks by using taskBox.query() and order them by date created in a descending order: order(Task_.dateCreated, flags: Order.descending). Then we use the watch() method to create a stream.
Finally, we define the create() method that will create an instance of ObjectBox to use in our app.
1
2
3
4
5
6
staticFuture<objectbox>create()async{
// Future<store> openStore() {...} is defined in the generated objectbox.g.dart
finalstore=await openStore();
returnObjectBox._create(store);
}
</store></objectbox>
Open the store
Now we can initialise the store in our app’s main() function. Do this by calling the create() method of the ObjectBox class.
1
2
3
4
5
6
7
8
9
10
11
12
13
/// Provides access to the ObjectBox Store throughout the app.
late ObjectBox objectbox;
Future<void>main()async{
// This is required so ObjectBox can get the application directory
// to store the database in.
WidgetsFlutterBinding.ensureInitialized();
objectbox=await ObjectBox.create();
…
}
</void>
CRUD operations
CREATE – Put a new Task or Tag into the Store
In the homepage state subclass of main.dart, we define the methods for adding new tasks and tags. Start by creating a task using the input controller. Then, set a tag we want to relate to this task by calling tag.target(). Now we only need to put the new Task object in its Box.
1
2
3
Task task=Task(_taskInputController.text);
task.tag.target=tagToAdd;
objectbox.taskBox.put(task);
READ – Get all tag names
To get all tag names, we call getAll() on our tagBox. This will return a list with all tags. If you want to read just a single object, call the get(id) method to get only the desired single object back. For a range of objects, use getMany(), passing a list of ids to it.
1
List<Tag>tags=objectbox.tagBox.getAll();
Another way of reading data is by using queries. In the “Create a Store” section above, we created a task stream with the help of a query builder. There we just needed all tasks, so no criteria was specified. But generally, one can specify custom criteria to obtain a list of objects matching the needs. Learn more about how to use queries using the ObjectBox Query Docs.
DELETE – remove tasks by swiping
To remove objects from the database, we add a dismissible Flutter widget. Inside the setState method of the onDismissed property, we simply use the ObjectBox remove() operation with the corresponding task id.
1
2
3
4
5
6
7
8
9
Dismissible(
…
onDismissed:(direction){
// Remove the task from the store.
objectbox.taskBox.remove(tasks[index].id);
setState((){});
…
},
),
UPDATE – Updating the date when Task was finished
Our app prints all tasks as a list with checkboxes. Each task’s finished date is initially set to null. We want the finished date of a task to update when the user marks the task as done. The non-null finished date therefore will act as an indicator that the task is finished. Let’s do this inside the setState() method of the onChanged property of our Checkbox class. Set the dateFinished to DateTime.now() if it was null when the checkbox value was changed, and set back to null otherwise.
1
2
3
4
5
6
7
8
9
10
11
12
Checkbox(
value:notNull(tasks[index].dateFinished),
onChanged:(bool?value){
setState((){
if(!notNull(tasks[index].dateFinished)){
tasks[index].dateFinished=DateTime.now();
}else{
tasks[index].dateFinished=null;
}
});
}
)
Relations
Remember we initialised a ToOne relation in the very first section and planned to come back to this at the end? Now that we have covered all CRUD operations, we can come back to discussing those.
Relations allow us to build references between objects. They have a direction: a source object references a target object. There are three types of relations:
to-one relations have one target object, as used above;
one-to-many relations can have multiple target objects, but each target only has one source, e.g. we could add a backlink (one-to-many) to Tag in our example to find out all tasks with a specific tag;
many-to-many relations involve targets that can have multiple sources, e.g. to improve our example app by supporting multiple tags per task, we could replace the to-one with a many-to-many relation.
When listing all tasks, we might want to include each task’s tag next to the task name. The relations are already initialised (see the “Create a model file” section). Now we need to read the tag of each task we want to list. So, inside the Text widget that displays the task name, we use tasks[index].tag.target?.name to print the name of the corresponding tag.
Build your own Flutter app with the ObjectBox Database
Now you have all the tools needed to build your own version of the task-list app with tags. The setup we described is rather minimal, so that anyone can get started easily. However, this gives you lots of room for improvement. For example, you could replace the existing to-one relation with a to-many, allowing users to add more than one tag per task. Or you can add task-list filtering and/or sorting functionality using different queries. The possibilities are truly endless.
We’d like to learn more about the creative ways to use the ObjectBox Database in Flutter you came up with! Let us know via Twitter or email.
As a direct follow up from the ObjectBox database installation tutorial, today we’ll code a simple C++ example app to show how the database can be used. Before starting to program, let’s briefly overview what we want to achieve with this tutorial and what is the best way to work through it.
Overview of the app we want to build
In short, we will make a console calculator app with an option to save results into memory. These will be stored as objects of the Number class. Every Number will also have an ID for easy reference in future calculations. Apart from the function to make calculations, we will create a function to enter memory. It will list all the database entries and have an option to clear memory. By coding all of this, we will make use of such standard ObjectBox operations as put, get, getAll and removeAll.
Our program will consist of seven files:
the FlatBuffers schema file, that defines the model of a class we want to store in the database
the header file, for class function definitions
the source file, for function implementation
the four files with objectbox binding code that will be created by objectbox-generator
How to use this tutorial
While looking at coding examples is useful in many cases, the best way to learn such a practical skill like programming is to solve problems independently. This is why we included an exercise for each step. You are encouraged to make the effort and do each of them, even if you don’t know the answer straight away. Only move to the next step after you test each part of your program and make sure that everything works as intended. Ideally, you should only use the code snippets presented here to check yourself or look for hints when you feel stuck. Bear in mind that sometimes there might be several different ways to achieve the same results. So if something that we ask you to do in this tutorial doesn’t work for you, try to come up with your own solution.
How to create the FlatBuffers file?
First, we’ll create the FlatBuffers schema (.fbs) for our app. This is required for the objectbox-generator to generate binding code that will allow us to use the ObjectBox library in our project.
The FlatBuffers schema consists of a table, which defines the object we want to store in the database, and the properties of this object. Each property consists of a name and a type. We want to keep our example very simple, so just two properties is enough.
To replicate a calculator’s memory, we want ObjectBox to store some numbers. We can define the Number object by giving the table a corresponding name.
Inside the table, we want to have two properties: id and contents. The contents of each Number object is the number itself (double), while id is an ulong that our program will assign to each of them for easy identification.
Exercise: create a file called numbers.fbs and define the table in the format
1
2
3
tableName{
property_name:type;
}
Reveal code
1
2
3
4
tableNumber{
id:ulong;
contents:double;
}
Generating binding code
Now that the FlatBuffers file is ready, we can generate the binding code. To do this, run the objectbox-generator for our FlatBuffers file:
1
objectbox-generator-cpp numbers.fbs
The following files will be generated:
objectbox-model.h
objectbox-model.json
numbers.obx.hpp
numbers.obx.cpp
The header file
This is where the main chunk of our code will be. It will contain the Calculator class and all the function definitions.
Start by including the three ObjectBox header files: objectbox.hpp, objectbox-model.h and numbers.obx.hpp. Our whole program will be based on one class, called Calculator. It should only have two private members: Store and Box. Store is a reference to the database and will manage Boxes. Each Box stores objects of a particular class. In this example, we only need one Box. Let’s call it numberBox, as it will store Numbers that we want to save in the memory of our calculator.
Exercise: create a file called calculator.hpp and define the Calculator class with two private members: reference to the obx library member Store and a Box of Numbers.
Reveal code
1
2
3
4
classCalculator{
obx::Store&store;
obx::Box<Number>numberBox;
}
2. After the constructor, we define the run function. It will be responsible for the menu of our program. There should be two main options: to perform calculations and enter memory. As discussed above, we want this app to do two things: perform calculations and show memory. We’ll define these as separate functions, called Calculate and Memory. The first one is quite standard, so we won’t go into a detailed explanation here. The only thing you should keep in mind is that we need to account for the case when the user wants to operate on a memory item. To deal with this, we’ll process input in a function called processInput.
Exercise: define the parametrised constructor which takes a reference to Store as a parameter. Then define the run and Calculate functions.
Reveal code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public:
Calculator(obx::Store&obxStore)
:store(obxStore),
numberBox(obxStore){}
intrun(){
std::stringinput;
std::cout<<"Welcome to the example calculator app."<<std::endl;
3. The final part of this function is for saving results into memory. We start by asking the user if they want to do that. If the answer is positive, we create a new instance of Number and set the most recent result as a value of its contents. To save our object in the database, we can operate with put(object) on our Box. put is one of the standard ObjectBox operations, which is used for creating new objects and overwriting existing ones.
Exercise: create an option to store the result in memory, making use of the ObjectBox put operation.
Reveal code
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
std::cout<<"Save this number [y/n]? ";
while(true){
std::getline(std::cin,input);
if(input=="y"){
Numberobject{};
object.contents=result;
numberBox.put(object);
std::cout<<"New number in memory: "<<object.contents<<", ID: "<<object.id<<std::endl;
break;
}elseif(input=="n"){
break;
}else{
std::cerr<<"Unknown command. Try again: ";
fflush(stderr);
continue;
}
}
4. Next, we should define processInput, which will read input as a string and check whether it has the right format. Now, to make it recognise the memory items, we have to come up with a standard format for these. Remember, we defined an ID property for our Numbers. Every number in our database has an ID, so we can refer to them as, e.g. m1, m2, m3 etc. To read the numbers from memory, we can make use of the get(obx_id) operation. It returns a unique pointer to the corresponding Number, whose contents we need to access and use as our operand.
Exercise: define the processInput function, which detects when something like m1 was used as an operand and updates x, y, and op according to the input.
//Iterate over the two numbers from input, retrieving them from memory if needed
for(inti=0;i<2;i++){
try{
if(inputStrings[i][0]=='m'){
inputStrings[i]=inputStrings[i].substr(1);
obx_id id=std::stoull(inputStrings[i]);
std::unique_ptr<Number>num=numberBox.get(id);
outNumbers[i].get()=num->contents;
}else{
outNumbers[i].get()=std::stod(inputStrings[i]);
}
}
catch(std::exception&e){
std::cout<<"Invalid input. Try again: ";
returnfalse;
}
}
returntrue;
}
5. The last function in our header file will be Memory. It should list all the numbers contained in the database and have an option to clear data. We can read all the database entries by calling the getAll ObjectBox operator. It returns a vector of unique pointers. To clear memory, you can simply operate with removeAll on our Box.
Exercise: define the Memory function, which lists all the memory items, and can delete all of them by request.
std::cout<<"Enter clear to delete all or back to return to menu. "<<std::endl;
while(std::getline(std::cin,input)){
if(input=="clear"){
numberBox.removeAll();
std::cout<<"Data deleted."<<std::endl;
break;
}elseif(input=="back"){
break;
}else{
std::cerr<<"Unknown command."<<input<<std::endl;
fflush(stderr);
continue;
}
}
}
The source file
To tie everything together, we create a source (.cpp) file. It should contain only the main function that initialises the objectbox model, creates an instance of the Calculator app, and runs it. To create the ObjectBox model, use
1
obx::Store::Options options(create_obx_model())
then passing options as a parameter when you initialise the Store.
Exercise: create the source file
Reveal code
1
2
3
4
5
6
7
8
9
#include "example.hpp"
intmain(){
obx::Store::Options options(create_obx_model());
obx::Store store(options);
Calculator app(store);
returnapp.run();
}
Final notes
Now you can finally compile and run your application. At this point, a good exercise would be to try and add some more functionality to this project. Check out the ObjectBox C++ documentation to learn more about the available operations.
This ObjectBox beginner tutorial is for people who have limited knowledge of C++ development (no prior experience with external libraries is required). It will walk you through the installation process of all the development tools needed to get started with ObjectBox on Windows. By the way, ObjectBox is a database with intuitive native APIs, so it won’t take you long to start using it.
Firstly, we will need to set up a Linux subsystem (WSL2) and install such tools as:
CMake, which will generate build files from the ObjectBox source code to work on Linux;
Git, which will download the source code from the ObjectBox repository.
Then, we will install ObjectBox and run a simple example in Visual Studio Code.
Windows Subsystem for Linux (WSL2)
In this section, you will set up a simple Linux subsystem that you can use to build Objectbox in C++.
Install WSL (Note: this requires a reboot; it also configures a limited HyperV that may cause issues with e.g. VirtualBox). Warning: to paste e.g. a password to the Ubuntu setup console window, right-click the title bar and select Edit → Paste. CTRL + V may not work.
(optional, but recommended) install Windows Terminal from Microsoft Store and use Ubuntu from there (does not have the copy/paste issue, also supports terminal apps better).
3. Within Windows Terminal, open Ubuntu by choosing it from the dropdown menu.
3. Create a text file called CMakeLists.txt with the following code. It will tell CMake to get the ObjectBox source code from its Git repository and link the library to your project.
4. Create a simple main.cpp file that will help us verify the setup:
1
2
3
4
5
6
#include "objectbox.hpp"
intmain(){
printf("Using ObjectBox version %s\n",obx_version_string());
}
5. Follow this official guide for VS code and CMake to select Clang as the compiler, configure and build ObjectBox. As a result, .vscode and build folders will be generated. So your directory should now look like this:
Running the tasks-list app example
Finally, we can check that everything works and run a simple example.
1. Click the “Select target to launch” button on the status bar and select “myapp” from the dropdown menu. Then launch it. You should see it output the correct version as in the screenshot.
2. Before proceeding with the example, you need to download the most recent ObjectBox generator for Linux from releases. Then come back to the Windows Terminal and type
1
explorer.exe.
to open the current directory in Windows Explorer. Copy the objectbox-generator file in there.
3. Back in VS Code, you should now run the generator for the example code:
If you get a “permission denied” error, try this to make the generator file executable for your user:
1
chmod+xobjectbox-generator
4. Now choose objectbox-c-examples-tasks-cpp-gen as the target and run it. You should see the menu of a simple to-do list app as shown on the screenshot. It stores your tasks, together with their creation time and status. Try playing around with it and exploring the code of this example app to get a feel of how ObjectBox can be used.
Note: if you see a sync error (e.g. Can not modify object of sync-enabled type “Task” because sync has not been activated for this store), please delete the first line from the tasklist.fbs file and run the objectbox generator once again. Or, if you want to try sync, apply for our Early Access Data Sync. There is a separate example (called objectbox-c-examples-tasks-cpp-gen-sync) that you can run after installing the Sync Server.
We use cookies to ensure that we give you the best experience on our website. If you continue to use this site we will assume that you are happy with it.Ok