ActiveAndroid – Data Persistence
ActiveAndroid
is an ORM library for SQLite DB, which provides simple but powerful API to interact with database on Android platform. It uses annotations and provides base class for Database Models.
Configuration
First, the library must be configured in an AndroidManifest file. We must add name and version of the database. Example is presented in Listing no. 1.
<manifest ...="">
<application ...="" android:name="com.futureprocessing.qe.QeApp">
<meta-data
android:name="AA_DB_NAME"
android:value="database.db" />
<meta-data
android:name="AA_DB_VERSION"
android:value="1" />
</application>
</manifest>
Listing 1
Database version is optional, if you don’t provide that setting, the version will be set as 1.
The next step is to initialize the library. In most cases, it should be done in extended android.app.Application
class. Simply call ActiveAndroid.initialize(this)
in onCreate()
method as you can see in Listing no. 2.
public class QeApp extends Application {
@Override
public void onCreate() {
super.onCreate();
ActiveAndroid.initialize(this);
}
}
Listing 2
There is also a method to reset the framework – ActiveAndroid.dispose()
. If you call this method, don’t forget to initialize it again. The authors of this library provide extension (com.activeandroid.app.Application
) of android.app.Application
class. It only calls initialize
and dispose
methods so we recommend to make your own class because probably other libraries will be initialized in this class too.
Tables and Columns
Creation of the simple database model using ActiveAndroid
required @Table
, @Column
annotations. The model must also extend com.activeandroid.Model
. In Listing no. 3, there is an example of database model.
@Table(name = "Events")
public class EventDAO extends Model {
@Column(name = "title")
public String title;
@Column(name = "description")
public String description;
@Column(name = "reminder_id")
public long reminderId = -1;
@Column(name = "favourite")
public boolean isFavourite = false;
@Column(name = "abstract")
public String eventAbstract;
@Column(name = "start_date")
public DateTime startDate;
@Column(name = "end_date")
public DateTime endDate;
@Column(name = "session", index = true)
public SessionDAO session;
@Column(name = "type")
public EventTypeDAO type = new EventTypeDAO();
}
Listing 3
Important parts of that class:
@Table
– Annotation which marks the class as database table, name attribute sets the table name. If you use this annotation, the name attribute is required. You can also change the name of primary key column using “id
” attribute. Default name of primary key is “Id
”. @Column
– Annotation which marks the field as a column of a table. You can set the name of the column, but if you don’t set this attribute, the name of the field will be used as column name. There is an option to set more attributes such as „notNull
”, „unique
”, „length
”, „onUpdate
”, „onDelete
”, etc. This annotation is required for all fields which should be in the table; as ActiveAndroid columns handling primitive types and relationships. extends Model
– Required for all database models. Creates auto-incrementing primary key id for table.ActiveAndroid
Model. It also provides methods such as save()
or delete()
. default constructor
– required. If you define your own constructor, you must define default constructor as well. If you want to speed up application startup, you can specify all Model
classes in the AndroidManifest
, as you can see in Listing no. 4. This operation can speed up application startup because in normal case startup, ActiveAndroid
will look for all Models.
<meta-data
android:name="AA_MODELS"
android:value="com.futureprocessing.qe.database.active.EventDAO,
com.futureprocessing.qe.database.active.DatabaseUpdateDAO,
com.futureprocessing.qe.database.active.EventTypeDAO,
com.futureprocessing.qe.database.active.JoinEventPrelegent,
com.futureprocessing.qe.database.active.PrelegentDAO,
com.futureprocessing.qe.database.active.SessionDAO,
com.futureprocessing.qe.database.active.SocialMediaAuthorDAO,
com.futureprocessing.qe.database.active.SocialMediaPostDAO,
com.futureprocessing.qe.database.active.SponsorDAO" />
Listing 4
If you prefer to declare models in the code, you can use Confugiration.Builder
from ActiveAndroid
library as it is presented in Listing 5.
public class QEApp extends Application {
@Override
public void onCreate() {
super.onCreate();
Configuration.Builder configurationBuilder = new Configuration.Builder(this);
configurationBuilder.addModelClasses(DatabaseUpdateDAO.class,
EventDAO.class, EventTypeDAO.class,
JoinEventPrelegent.class, PrelegentDAO.class,
SessionDAO.class, SocialMediaAuthorDAO.class,
SocialMediaPostDAO.class, SponsorDAO.class);
ActiveAndroid.initialize(this);
}
}
Listing 5
Simple Operations
Saving and updating is provided by Model
class. To save, you must create a new instance of the Model
, assign data to its fields and call save()
method. If you change something in this instance and call save()
once again, the insert action will be changed to update. An example is shown in Listing no. 6.
EventDAO event = new EventDAO();
event.title = "Sample title";
event.description = "Sample description";
event.save();
event = EventDAO.load(EventDAO.class, 1);
event.delete();
EventDAO.delete(EventDAO.class, 1);
Listing 6
The simplest method to load the Model
from DB is to call static
method load(EventDAO.class, 1)
and pass model class and id as parameters. An example is provided in Listing no. 6. The simplest method to delete the Model
form database is to call delete()
method on instance of your Model
class or call static delete(EventDAO.class, 1)
method in the same way as static load()
method. To save multiple records at once, you can use Transaction
.
ActiveAndroid.beginTransaction();
try {
for (int i = 0; i < 10; i++) {
EventDAO event = new EventDAO();
event.title = "Title " + i;
event.save();
}
ActiveAndroid.setTransactionSuccessful();
}
finally {
ActiveAndroid.endTransaction();
}
Listing 7
Queries
Queries in ActiveAndroid
are constructed as Builder Pattern and look like normal SQL statement. Recommended solution is to add queries method to Model
class as it will be DAO object with all queries. Also, pay attention to how we use constant fields as columns name. It will be helpful in defining queries to avoid typos. First, look at two examples of Select
action. In Listing no. 8, there are examples of query methods. One provides a single row and another one a collection of rows. We can use methods as in SQL, for example „from
”, „where
”, „orderBy
”, etc.
@Table(name = "Prelegents")
public class PrelegentDAO extends Model {
public static final String TABLE_NAME = "Prelegents";
public static final String NAME = "prelegent_name";
public static final String DETAILS = "details";
public static final String COMPANY = "company";
public static final String PHOTO_URI = "photo_uri";
public static final String JOB = "job_title";
@Column(name = NAME)
public String name;
@Column(name = DETAILS)
public String details;
@Column(name = COMPANY)
public String company;
@Column(name = JOB)
public String job;
@Column(name = PHOTO_URI)
public String photoUri;
public static List selectAll() {
return new Select().from(PrelegentDAO.class).orderBy(NAME).execute();
}
public static PrelegentDAO select(long id) {
return new Select().from(PrelegentDAO.class).where(ID + " = ?", id).executeSingle();
}
}
Listing 8
You can also delete elements from database using query and builder pattern. Look at Listing no. 9, the statement looks exactly the same as Select
action.
public static void deleteAll() {
new Delete().from(PrelegentDAO.class).execute();
}
Listing 9
Advanced Queries
Sometimes, for performance reasons, we want to create more complicated queries, for example, use join
s. In normal case, ActiveAndroid
gets all models from database for us, but it may not be the most optimal solution. We have Prelegents
and Events
in many to many relation. Take a look at Listing no. 10, there is a linking table for prelegents
and events
.
@Table(name = "JoinEventPrelegent")
public class JoinEventPrelegent extends Model {
public static final String JOIN_EVENT = "join_event";
public static final String JOIN_PRELEGENT = "join_prelegent";
public static final String TABLE_NAME = "JoinEventPrelegent";
@Column(name = JOIN_EVENT, onDelete = Column.ForeignKeyAction.CASCADE)
public EventDAO event;
@Column(name = JOIN_PRELEGENT, onDelete = Column.ForeignKeyAction.CASCADE)
public PrelegentDAO prelegent;
}
Listing 10
Now, we can query for all events belonging to prelegent using inner join
with the use of query builder provided by ActiveAndroid
framework. You can see how to use an inner join
in Listing no. 11.
new Select()
.from(EventDAO.class)
.innerJoin(JoinEventPrelegent.class)
.on(EventDAO.TABLE_NAME + "." + EventDAO.ID + " = " + _
JoinEventPrelegent.TABLE_NAME + "." + JoinEventPrelegent.JOIN_EVENT)
.where(JoinEventPrelegent.TABLE_NAME + "." + JoinEventPrelegent.JOIN_PRELEGENT + " = ?", getId())
.orderBy(EventDAO.START_DATE)
.execute();
Listing 11
ActiveAndroid
also provides custom SQL queries using SQLiteUtils
class. There are two methods executing SQL namely, “execSql
” and “rawQuery
”. The first one is provided for no results queries, whereas the second one for queries which return the result. In Listing no. 12, you can find examples of uses of those methods.
SQLiteUtils.execSql("DELETE FROM Prelegents where id = ?", new String[]{"1"});
List prelegents =
SQLiteUtils.rawQuery(PrelegentDAO.class, _
"SELECT * from Prelegents where id = ?", new String[]{"1"});
Listing 12
Prepopulating Database
Sometimes, we want to have prepopulated database on a start app to avoid downloading mass data from the Internet. ActiveAndroid
has a very easy to use prepopulating mechanism. To add prepopulated database to your app, you must create database file and simply copy it to assets folder in your project. Ensure that filename is the same as you define in the AndroidManifest file. After an app installation on the device, a db file will be copied from “assets” to “/data/data/myapp/databases”. How to create the prepopulated database? For example, you can launch the app, download actual data from the webservice and download database file from the device. It will be easier if you have a rooted device or if you use emulator, because in that case you will have full access to app sandbox.
Database Migration
Changing database in an app is a normal case, so if you want to migrate ActiveAndroid
database schema, there is a simple solution. If you want to add new table, just add new class and the framework will do everything for you. On the other hand, if you want to change something in the existing table, you must write migration SQL script and add it to the “assets/migration” folder in your project, and upgrade database version in the AndroidManifest
. The script must be named .sql, where “NewVersion
” is the upgraded database version from AndroidManifest
. ActiveAndroid
will execute your migration script if its filename is greater than the old database version and smaller or equal to new database version. Example:
Add new field to your table
@Table(name = "Session")
public class SessionDAO extends Model {
public static final String TABLE_NAME = "Session";
public static final String NAME = "session_name";
public static final String ORDER = "sequence";
public static final String NEW_COLUMN = "new_column";
@Column(name = NAME)
public String name;
@Column(name = ORDER)
public int order;
@Column(name = NEW_COLUMN)
public String newColumn;
}
Upgrade database version
<manifest ...="">
<application ...="" android:name="com.futureprocessing.qe.QeApp">
<meta-data
android:name="AA_DB_NAME"
android:value="database.db" />
<meta-data
android:name="AA_DB_VERSION"
android:value="2" />
</application>
</manifest>
Write migration script
ALTER TABLE Session ADD COLUMN new_column TEXT;
Type Serializers
Sometimes, we need to serialize custom data to database. For example, ActiveAndroid handle java.util.Date
, but if we want to use org.joda.time.DateTime
, we need to add custom TypeSerializer
to ActiveAndroid
. First, we need to implement our serializer
class as you can see in Listing 13.
public final class DateTimeSerializer extends TypeSerializer {
public Class<?> getDeserializedType() {
return DateTime.class;
}
public Class<?> getSerializedType() {
return long.class;
}
public Long serialize(Object data) {
if (data == null) {
return null;
}
return ((DateTime) data).getMillis();
}
public DateTime deserialize(Object data) {
if (data == null) {
return null;
}
return new DateTime(data);
}
}
Listing 13
The second thing that we must do is add information where serializers are to AndroidManifest
.
<meta-data
android:name="AA_SERIALIZERS"
android:value="com.futureprocessing.qe.database.active.serializer" />
After that step, always when the ActiveAndroid
finds a column of DateTime
type, it will use DateTimeSerializer
for serialization and deserialization of DateTime
to log.
Parse
Introduction
Parse is a really powerful framework that enables easy creation of backend for an application. It also allows to easily implement push notification, analytics tracking and more. Native Android library allows for easier integration with parse backend side. Parse provides ready, minimalistic framework for user permissions and user accounts as well as supports mechanism of sessions. It also brings help in linking users with Facebook accounts and serves easier way to connect your application to Facebook login mechanism.
Configuration
First things first, you need to setup an account at parse.com. Adding parse to your project begins with downloading Parse SDK (you can do that under this link). For the purpose of this article, you will only need to copy two jars bolts-android-1.2.0
and Parse-1.9.2
to your libs folder. Now, you have to add required permissions to your manifest file (see Listing 14).
<uses-permission android:name="android.permission.INTERNET">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE">
</uses-permission></uses-permission>
Listing 14
Having this done, you need to initialize the library in the onCreate()
method of your application subclass (see Listing 15). Remember to replace APPLICATION_ID
and CLIENT_ID
with your keys; you can find them in Keys section of settings on your projects page at parse.com (see Image 1).
@Override
public void onCreate() {
Parse.initialize(context, APPLICATION_ID, CLIENT_ID);
}
Listing 15
Image 1
Creating Your Model
Parse provides two ways of creating your model. One way is to simply create parse objects and save them, relying on the framework to create it for you. Another way is to visit parse.com and open Core-Data view, which allows you to manually create classes that can be used later in the code.
Object
Everything and anything related to storing data in parse revolves around ParseObject
. It is worth mentioning these objects are schemeless, they store data in key-value pairs, keys being alphanumeric string
. Objects created in Parse
by default get four properties: id, creation time, modification time and ACL (permissions set per object basis).
Create, Update, Delete
Objects in Parse
can be created using constructor with String
parameter being a name of the class or one of the create
method variants. It is also possible to create an object without data, meaning that it will have an id(previously known) but nothing else (it works as a pointer to an object), which can be useful when creating relations. It is achieved with one of createWithoutData
method variants. As mentioned earlier, when you save an object to the server and it has not been previously saved (has no corresponding class on the server side), Parse
will create it for you. Listing 16 shows how an object is created and saved.
ParseObject district = new ParseObject("District");
district.put("name", "Center");
district.save();
Listing 16
Parse
provides five different methods to execute save
operation. They are all listed below:
save();
saveInBackground() ;
saveInBackground(Save Callback callback);
saveEventually();
saveEventually(SaveCallback callback);
Listing 17
Updating objects is as simple as setting their value and calling save()
! You do not have to do anything else as Parse
will figure out which fields have been changed and send only these ones. An exemplary code is in Listing 18.
district.put("name","Downtown");
district.saveInBackground();
Listing 18
Deleting objects is achieved through delete()
method, which has the same set of variants as save()
method.
district.deleteInBackground();
Listing 19
It is also possible to delete single fields, with remove(String fieldName)
method. After this operation, the field has a value of null
.
district.remove("name");
district.saveInBackground();
Listing 20
Retrieving Objects
Retrieving objects from Parse
resembles normal SQL query commands. Queries provide us with a way to retrieve a single object as well as their collection. The first step is to construct a query object (ParseQuery) with a proper class (table) name. Having that object, we can add both filtering and non-filtering constraints. Constraints on a query can be applied with a variety of methods, which name starts with where i.e., whereEqualTo
, whereExists
, whereEndsWith
, whereNotContainedIn
. As I mentioned earlier, ParseQuery
resembles SQL select
syntax and as in regular select, you can restrict columns to be returned (selectKeys
), sort results in ascending and descending manner (addAscendingOrder
, addDescendingOrder
), limit their number (setLimit
) or skip first few results (setSkip
). It is also possible to create compound queries, to do this we have to utilize ParseQuery.or
method. It accepts a list of queries and returns a ParseQuery
. Any constraint set on the returned query act as if it was set with an AND
operator. Worth mentioning is that elements of a compound query must not contain GeoPoint
or non-filtering constraints. Exemplary query for a District
class is shown in the Listing 21. This query retrieves districts that have set budget and their population is greater than or equal to 180 000 people.
ParseQuery.getQuery("District")
.whereExists("budget")
.whereGreaterThan("population", 180000)
.findInBackground(new FindCallback() {
@Override
public void done(List list, ParseException e) {
if(e == null) {
} else {
}
}
});
Listing 21
And below is an example of a query for a District
object with known id.
ParseQuery.getQuery("District")
.getInBackground("xtwiqLKPbF", new GetCallback() {
@Override
public void done(ParseObject object, ParseException e) {
}
});
Listing 22
Object Relations
One-to-many
There are two ways to implement one-to-many relations either with Pointer or an array. The choice between them depends on the count of objects in the relation. General rule of thumb given by the Parse documentation is to use pointer if the many side of the relation accounts for more than 100 objects; otherwise you are probably good to go with an array. To illustrate one-to-many relation I will be using an analogy to a truck that can be driven by many drivers and with one driver driving only one truck.
Array Method
Creating an object with relation expressed as an array is relatively straightforward. The first thing to be done is to add a column of type Array to the given class (in the listing below its name is “streetsColumn”). Afterwards, we need to create a list of ParseObject type and add all of the related objects to it. The last step is to assign previously created list to the one side of the relation. This is shown in the listing 23.
ArrayList drivers= new ArrayList();
drivers.add(ParseObject.createWithoutData("Driver", "iUHcMaLwnm"));
drivers.add(ParseObject.createWithoutData("Driver", "YVNtkPTNsS"));
truck.put("drivers", drivers);
Listing 23
This approach also has a hidden benefit, we can fetch all related objects while retrieving district object from the previous listing. To achieve that we need to invoke include
method with a column name passed as a parameter. See listing 24.
ParseQuery.getQuery("Truck")
.include("drivers")
.getInBackground("", new GetCallback() {
@Override
public void done(ParseObject object, ParseException e) {
}
});
Listing 24
Pointer Method
This method implies that relation is saved not in the truck object itself but in the driver objects. This is shown in the listing 25.
ParseObject johnDriver = …
ParseObject markDriver = …
johnDriver.put("truck",truck);
markDriver.put("truck",truck);
Listing 25
Many-to-many
There are three ways (ParseRelation
, arrays and join
table) to implement this kind of relation. For this relation, we will discuss relationship between districts and streets.
Join table Method
Approach with join
table is particularly useful when we want to add some metadata to a relation. It requires creation of a new join
class. Let's add a date of incorporating the street to the district to out relation. Creation of the ParseObject
as well as all fields is shown in listing 26.
ParseObject relationObject = new ParseObject("DistrictStreetRelation");
relationObject.put("district",districtObject);
relationObject.put("street",streetObject);
relationObject.put("incorporationDate",new Date());
Listing 26
Array Method
Building many-to-many relations using arrays in parse
looks just like creating two one-to-many relations. That being said, let's see it in the code, take a look at listing 27.
List districtsForStreet = ...
ParseObject street = new ParseObject("Street");
street.put("districts", districtsForStreet);
List streetsForDistrict = …
ParseObject district = new ParseObject("District");
district.put("streets", streetsForDistrict);
Listing 27
When writing queries, you should use include
method to make sure that results also include related objects.
ParseRelation Method
Modelling relations with ParseRelation
requires visit to the Data Browser on the web panel at parse.com. In the required class, you should add a column of type ParseRelation
defining its target type and column name. Having that done, you should proceed to associate given objects.
ParseObject district = new ParseObject("District");
ParseRelation streets = book.getRelation("streets");
relation.add(ParseObject.createWithoutData("Street",""));
relation.add(mainStreet);
relation.add(firstStreet);
Listing 28
Listing 28 shows how to create previously described relation.
Working with ParseRelation
When retrieving an object with a field of type ParseRelation
, you must separately query for the relation objects, this is shown in the listing 29. Only then, you will have an access to objects referenced in the relation.
ParseObject district = new ParseObject("District");
ParseRelation streetsRelation = book.getRelation("streets");
streetsRelation.getQuery()
.findInBackground(new FindCallback() {
@Override
public void done(List list, ParseException e) {
}
});
Listing 29
Subclassing ParseObject
While reading listings, you might have imagined that using methods such as getString, getInt
, etc. to read or write is going to be inconvenient. To counter that, you can subclass ParseObject
class and register your subclasses before initializing Parse
. Listing 30 shows how to create such a subclass.
@ParseClassName("District")
public class DistrictDAO extends ParseObject {
public DistrictDAO() {
}
public String getName() {
return getString("name");
}
public void setName(String name) {
put("name", name);
}
public ArrayList getStreets() {
return (ArrayList) get("streetsArray");
}
public void setStreets(ArrayList streets) {
put("streetsArray", streets);
}
}
Listing 30
It is worth noting that previously, when creating ParseObject
, we used to pass “District
” as a parameter, in subclassed version, it is taken care of by the @ParseClassName
annotation. Having the subclass ready, you must register them with the local instance of Parse
before initializing it in the application's onCreate
method. Code is shown in the listing 31.
ParseObject.registerSubclass(DistrictDAO.class);
Parse.initialize(context, "", "");
Listing 31
References
- ActiveAndroid
- Parse