In this section we run through how to build a simple database, table, and establish relationship between Model
.
The Ant Queen: We are curious in storing data about our ant colonies. We want to track and tag all ants for a specific colony as well as each queen Ant.
We have this relationship:
Colony (1..1) -> Queen (1...many)-> Ants
To initialize DBFlow, place this code in a custom Application
class (recommended):
public class ExampleApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
FlowManager.init(this);
}
}
Never fear, this only initializes once and it will hold onto only the Application
context even if initialized in another Context
.
Lastly, add the definition to the manifest (with the name that you chose for your custom application):
<application
android:name="{packageName}.ExampleApplication"
...>
</application>
In DBFlow, a @Database
is a placeholder object that generate a subclass of BaseDatabaseDefinition
, which connect all tables, ModelAdapter, Views, Queries, and more under a single object. All connections are done pre-compile time, so there's no searching, reflection, or anything else that can slow down the runtime impact of your app.
In this example, we need to define where we store our ant colony:
@Database(name = ColonyDatabase.NAME, version = ColonyDatabase.VERSION)
public class ColonyDatabase {
public static final String NAME = "Colonies";
public static final int VERSION = 1;
}
For best practices, we create the constants NAME
and VERSION
as public, so other components we define for DBFlow can reference it later (if needed).
Note: If you wish to use SQLCipher please read setup here
Now that we have a place to store our data for the ant colonies, we need to explicitly define how the underlying SQL data is stored and what we get is a Model
that represents that underlying data.
We will start and go top-down within the colony. There can be only one queen per colony. We define our database objects using the ORM (object-relational mapping) model. What we do is mark each field we want represented as a database column in a class that corresponds to an underlying database table.
In DBFlow, anything that represents an object that interacts with the database using ORM must implement Model
. The reason for an interface vs. a baseclass ensures that other kinds of Model
such as views/virtual tables can conform to the same protocol and not rely on one base class to rule them all. We extend BaseModel
as a convenience for the standard table to Model
class. Also, if not forced to at least an interface, this prevents passing in objects not meant for the methods they belong to.
To properly define a table we must:
- Mark the class with
@Table
annotation - Point the table to the correct database, in this case
ColonyDatabase
- Define at least one primary key
- The class and all of its database columns must be package private,
public
, or private (with corresponding getters and setters), so that the classes generated from DBFlow can access them.
The basic definition of Queen
we can use is:
@Table(database = ColonyDatabase.class)
public class Queen extends BaseModel {
@PrimaryKey(autoincrement = true)
long id;
@Column
String name;
}
So we have a queen ant definition, and now we need to define a Colony
for the queen.
@ModelContainer // more on this later.
@Table(database = ColonyDatabase.class)
public class Colony extends BaseModel {
@PrimaryKey(autoincrement = true)
long id;
@Column
String name;
}
Now that we have a Queen
and Colony
table, we want to establish a 1-1 relationship. We want the database to care when data is removed, such as if a fire occurs and destroys the Colony
. When the Colony
is destroyed, we assume the Queen
no longer exists, so we want to "kill" the Queen
for that Colony
so it no longer exists.
To establish the connection, we will define a Foreign Key that the child, Queen
uses:
@ModelContainer
@Table(database = ColonyDatabase.class)
public class Queen extends BaseModel {
//...previous code here
@Column
@ForeignKey(saveForeignKeyModel = false)
Colony colony;
}
Defining the Foreign Key as a Model
will automatically load the relationship when loading from the database using a query on the value of the reference in that column. For performance reasons we default saveForeignKeyModel=false
to not save the parent Colony
when the Queen
object is saved.
If you wish to keep that pairing intact, set saveForeignKeyModel=true
.
As of 3.0, we no longer need to explicitly define the @ForeignKeyReference
for each referenced column. DBFlow will add them automatically to the table definitions based on the @PrimaryKey
of the referenced tables. They will appear in the format of {foreignKeyFieldName}_{referencedColumnName}
.
Now that we have a Colony
with a Queen
that belongs to it, we need some ants to serve her!
@Table(database = ColonyDatabase.class)
public class Ant extends BaseModel {
@PrimaryKey(autoincrement = true)
long id;
@Column
String type;
@Column
boolean isMale;
@ForeignKey(saveForeignKeyModel = false)
ForeignKeyContainer<Queen> queenForeignKeyContainer;
/**
* Example of setting the model for the queen.
*/
public void associateQueen(Queen queen) {
queenForeignKeyContainer = FlowManager.getContainerAdapter(Queen.class).toForeignKeyContainer(queen);
}
}
We have the type
, which can be "worker", "mater", or "other". Also if the ant is male or female.
We use a ForeignKeyContainer
in this instance, since we can have thousands of ants. For performance reasons this will "lazy-load" the relationship of the Queen
and only run the query on the DB for the Queen
when we call toModel()
. With this said, in order to set the proper values on the ForeignKeyContainer
you should call its generated method for converting itself into a ForeignKeyContainer
via FlowManager.getContainerAdapter(Queen.class).toForeignKeyContainer(queen)
.
Since ModelContainer
usage is not generated by default, we must add the @ModelContainer
annotation to the Queen
class in order to use for a ForeignKeyContainer
.
Lastly, using @ForeignKeyContainer
can prevent circular references. If both the Queen
and Colony
referenced each other by Model
, we would run into a StackOverFlowError
, since they both would try to load each other out of the database.
Next, we establish the 1-to-many relationship by lazy-loading the ants, since we may have thousands, if not, millions stored:
@ModelContainer
@Table(database = ColonyDatabase.class)
public class Queen extends BaseModel {
//...
// needs to be accessible for DELETE
List<Ant> ants;
@OneToMany(methods = {OneToMany.Method.SAVE, OneToMany.Method.DELETE}, variableName = "ants")
public List<Ant> getMyAnts() {
if (ants == null || ants.isEmpty()) {
ants = SQLite.select()
.from(Ant.class)
.where(Ant_Table.queenForeignKeyContainer_id.eq(id))
.queryList();
}
return ants;
}
}
If you wish to lazy-load the relationship yourself, specify OneToMany.Method.DELETE
and SAVE
instead of ALL
. If you wish not to save them whenever the Queen
's data changes, specify DELETE
and LOAD
only.