ActiveAndroid Basics – Models
All code for this series is located here.
In the previous post I went over how to get ActiveAndroid up and running in your application, now we are going to implement our first Models. Disclaimer: This post will be longer than the previous two and will include some basic SQLite knowledge bombs.
First, I want to share my idea for the simple app that we will be building upon through this series. We’ll start with the classic Car example. So what do you need to build a car? I’m going to include wheels, an engine, a body, and a serial number in my Car class. The informal diagram below summarizes the Models that we will be creating.
The eventual idea here is to be able to create and save custom cars to a database and be able to edit them later, much like a car manufacturer would do when servicing custom car orders. Now that we have a basic idea of what we are aiming for, let’s get started with some code.
Below is the first iteration of our Car class which I will break down piece by piece:
Let’s first take a look at how we create a Table using ActiveAndroid.
@Table annotation tells ActiveAndroid that we are going to create a SQL table named Cars. It is really that simple. Note that our Car class extends
com.activeandroid.Model. This is required for ActiveAndroid to find our classes, create our database, and save to the database. The
@Table annotation can also take another argument, a string Id, but we will stick with the default for the time-being. Now let’s look at how we create columns in our table:
In SQLite tables, columns represent individual fields or data types. Each row can be thought of as an entry into the database (our objects themselves). I first declare all of the column names that we are going to use as private constants. This is not required, just something I do to keep my code clean and organized. It also makes it simple to change column names or use them later. Next you’ll see there are four
@Column annotations. Each one provides a name for the column, as well as the data that column will represent. The picture below represents what the SQLite table would look like after four cars were added.
The first two member variables, Engine and Body are simply other objects in our application domain. As mentioned in my first post, SQLite stores data as one of four types: INTEGER, REAL, TEXT, or NONE. Primitive data types are mapped to one of these SQLite types – int and boolean map to INTEGER, float and double map to REAL, etc. Strings obviously map to the TEXT type, so what happens to these custom objects that we have created? How does ActiveAndroid (and SQLite) reduce these objects into a datatype that it can store?
In databases there is a concept of keys. Since the objects referenced in our Car class are also derivatives of the Model class, ActiveAndroid generates a foreign key that, in essence, maps to the other objects’ tables. Specifically, the foreign key maps to a specific column’s primary key. This concept underlies much of Object-Relational Mapping and allows us to save complex object hierarchies to a SQL database. The answer to the original question is that foreign keys are saved as the TEXT SQLite data type which is important to remember further down the line when we look at schema migrations.
The third column and member variable is an interesting one – it is a List of Wheel objects. It is interesting because ActiveAndroid can not handle saving Java’s List types. There are two solutions to this problem. One would be to write a custom List class that implements Java’s list interface and extends Model. The better solution is to write a custom TypeSerializer that tells ActiveAndroid how to map a given type to a SQLite type for saving, and vice-versa for queries. We will do later in the series, for now just know that our
List<Wheel> will not be saved properly.
Finally, the fourth column contains some additional arguments that are very useful for customizing your database. After declaring the column name we set
unique = true (default is false); this tells ActiveAndroid to set up our table so that each
serialNumber is a guaranteed unique value. Next we set
onUniqueConflict = Column.ConflictAction.REPLACE; this lets ActiveAndroid know that if we save a car with a serial number that already exists we want to overwrite the existing one. Other possible actions include: FAIL (default), ABORT, IGNORE, and ROLLBACK – I’ll point you to the SQLite documentation to see the differences between these polices. The last customization we make is to set
index = true; doing so adds an index to this column in the underlying SQLite table that can speed up queries. In all honesty our app will not be large enough to warrant column indexing, but it is a useful feature to know about. The image below shows all of the possible
@Column annotation arguments which I will cover in more depth later in the series when we talk about advanced ActiveAndroid features.
The final piece of our Car class to look at is the mandatory no-arg constructor.
ActiveAndroid utilizes the no arg constructor when servicing database queries, so you must include this in all of your model classes. Do not forget to make the call to construct the superclass!
Other than the getters and setters, a few helper methods were also removed that I will go over in the next post. They are not related to ActiveAndroid, but are there to help display our data. Below I have included the other Model classes that are part of our application, again without the getters, setters, and helper methods.
The CarPart class is what all of our other parts (Body, Engine, and Wheel) will be derived from. This is mostly to show that as long as the superclass inherits ActiveAndroid’s Model class, all subclasses are also Models.
All three of the models shown above use primitive day types that map directly to a SQLite value and are otherwise pretty self-explanatory.
What is next?
In the next post we will build our first iteration of the UI and learn the basics of saving and querying.