ActiveAndroid Guide

Overview

Using the ActiveAndroid ORM makes managing client-side models extremely easy in simple cases. For more advanced or custom cases, you can use SQLiteOpenHelper to manage the database communication directly. But for simple model mapping from JSON, ActiveAndroid keeps things simple.

ActiveAndroid works like any Object Relational Mapper by mapping java classes to database tables and mapping java class member variables to the table columns. Through this process, each table maps to a Java model and the columns in the table represent the respective data fields. Similarly, each row in the database represents a particular object. This allows us to create, modify, delete and query our SQLite database using model objects instead of raw SQL.

For example, a "Tweet" model would be mapped to a "tweets" table in the database. The Tweet model might have a "body" field that maps to a body column in the table and a "timestamp" field that maps to a timestamp column. Through this process, each row would map to a particular tweet.

Installation

In Android Studio, you can setup ActiveAndroid via Gradle in app/build.gradle:

Configuration

Next, we need to configure the application node with the name property of com.activeandroid.app.Application to use the correct application class name within the AndroidManifest.xml:

<?xml version="1.0" encoding="utf-8"?><manifestxmlns:android="http://schemas.android.com/apk/res/android"...><!- adjust the name property of your application node -><applicationandroid:name="com.activeandroid.app.Application"android:icon="@drawable/ic_launcher"android:label="@string/app_name"android:theme="@style/AppTheme"><!- add the following metadata for version and database name -><meta-dataandroid:name="AA_DB_NAME"android:value="RestClient.db"/><meta-dataandroid:name="AA_DB_VERSION"android:value="1"/><activityandroid:name="com.codepath.apps.activities.MainActivity"android:label="@string/app_name"><!- ... -></activity></application></manifest>

Note that you must either directly use the com.activeandroid.app.Application as your application class (specified in the manifest) or if you have a custom application class, check out more details for how to approach that in the installation guide. Now you are ready to use ActiveAndroid.

Usage

Defining Models

First, we define our models by annotating the class with the table mapping and the member variables with the column mapping:

importcom.activeandroid.Model;importcom.activeandroid.annotation.Column;importcom.activeandroid.annotation.Table;@Table(name="Items")publicclassItemextendsModel{// This is the unique id given by the server
@Column(name="remote_id",unique=true,onUniqueConflict=Column.ConflictAction.REPLACE)publiclongremoteId;// This is a regular field
@Column(name="Name")publicStringname;// This is an association to another activeandroid model
@Column(name="Category",onUpdate=ForeignKeyAction.CASCADE,onDelete=ForeignKeyAction.CASCADE)publicCategorycategory;// Make sure to have a default constructor for every ActiveAndroid model
publicItem(){super();}publicItem(intremoteId,Stringname,Categorycategory){super();this.remoteId=remoteId;this.name=name;this.category=category;}}@Table(name="Categories")publicclassCategoryextendsModel{// This is the unique id given by the server
@Column(name="remote_id",unique=true)publiclongremoteId;// This is a regular field
@Column(name="Name")publicStringname;// Make sure to have a default constructor for every ActiveAndroid model
publicCategory(){super();}// Used to return items from another table based on the foreign key
publicList<Item>items(){returngetMany(Item.class,"Category");}}

The "name" part of the annotations refers to the name the Table or Columns will be given, so make sure to use the SQLite naming conventions for those. Also note that ActiveAndroid creates a local id (Id) in addition to our manually managed remoteId (unique) which is the id on the server (for networked applications). To access that primary key Id, you can call getId() on an instance of your model.

CRUD Operations

Now we can create, modify and delete records for these models backed by SQLite:

Querying Records

We can query records with a simple query syntax using the Select object:

@Table(name="Items")publicclassItemextendsModel{// ...
publicstaticList<Item>getAll(Categorycategory){// This is how you execute a query
returnnewSelect().from(Item.class).where("Category = ?",category.getId()).orderBy("Name ASC").execute();}}

Populating ListView with CursorAdapter

Review this Custom CursorAdapter and ListViews guide in order to load content from a Cursor into a ListView. In summary, in order to populate a ListView directly from the content within the ActiveAndroid SQLite database, we can define this method on the model to retrieve a Cursor for the result set:

publicclassTodoItemextendsModel{// ...
// Return cursor for result set for all todo items
publicstaticCursorfetchResultCursor(){StringtableName=Cache.getTableInfo(TodoItem.class).getTableName();// Query all items without any conditions
StringresultRecords=newSelect(tableName+".*, "+tableName+".Id as _id").from(TodoItem.class).toSql();// Execute query on the underlying ActiveAndroid SQLite database
CursorresultCursor=Cache.openDatabase().rawQuery(resultRecords,null);returnresultCursor;}}

We need to define a custom TodoCursorAdapter as outlined here in order to define which XML template to use for the cursor rows and how to populate the template views with the relevant data.

Next, we can fetch the data cursor containing all todo items with TodoItem.fetchResultCursor() and populate the ListView using our custom CursorAdapter:

That's all we have to do to load data from ActiveAndroid directly through a Cursor into a list.

Loading with Content Providers

Instead of using the underlying SQLite database directly, we can instead expose the ActiveAndroid data as a content provider with a few simple additions. First, override the default identity column for all ActiveAndroid models:

Then you can use the SimpleCursorAdapter to populate adapters using the underlying database directly:

// Define a SimpleCursorAdapter loading the body into the TextView in simple_list_item_1
SimpleCursorAdapteradapterTodo=newSimpleCursorAdapter(getActivity(),android.R.layout.simple_list_item_1,null,newString[]{"body"},newint[]{android.R.id.text1},0);// Attach the simple adapter to the list
myListView.setAdapter(adapterTodo);

You could also use a custom CursorAdapter instead for more flexibility. Next, we can load the data into the list using the content provider system through a CursorLoader:

Write your migration script. Name your script [newDatabaseVersion].sql, and place it in the directory [YourApp’sName]/app/src/main/assets/migrations. In my specific example, I’ll create the file [MyAppName]/app/src/main/assets/migrations/2.sql. (You might have to create the migrations directory yourself). You should write the SQLite script to add a column here:

ALTERTABLEItemsADDCOLUMNPriorityTEXT;

Note that in order trigger the migration script, you’ll have to save an instance of your model somewhere in your code.

Question: How does ActiveAndroid handle duplicate IDs? For example, I want to make sure no duplicate twitter IDs are inserted. Is there a way to specify a column is the primary key in the model?

The first step is to mark the column recording the unique id of an object as a unique column acting as your pseudo primary key. As explained here, the annotation is:

@Table(name="items")publicclassSampleModelextendsModel{// Ensure the remoteId must be unique. If a duplicate tries to save, replace the
@Column(name="remote_id",unique=true,onUniqueConflict=Column.ConflictAction.REPLACE)privateintremoteId;// ... set the remote id based on the json response
}

Make sure to uninstall the app afterward on the emulator to ensure the schema changes take effect. Note that you may need to manually ensure that you don't attempt to re-create existing objects by verifying they are not already in the database as shown below.

Question: I read somewhere that ActiveAndroid automatically creates another auto-increment ID column, is this true? What field names should I avoid using?

Question: What are the best practices when interacting with the sqlite in Android, is ORM/DAO the way to go?

Developers use both SQLiteOpenHelper and several different ORMs. It's common to use the SQLiteOpenHelper in cases where an ORM breaks down or isn't necessary. Since Models are typically formed anyways though and persistence on Android in many cases can map very closely to objects, ORMs like ActiveAndroid can be helpful especially for simple database mappings.

Troubleshooting

Problem: I am getting a "java.lang.NullPointerException at com.activeandroid.Cache.getTableInfo"

Android API 23+ Users: Be sure to also check this exception workaround and applying one of those three options to avoid this exception.

This usually means that you have not properly configured ActiveAndroid. You need to initialize ActiveAndroid within your application. This can be done either by leveraging the ActiveAndroid Application class directly in your manifest:

<applicationandroid:name="com.activeandroid.app.Application"

OR by extending a custom application class class MyApplication extends com.activeandroid.app.Application and then:

<applicationandroid:name=".MyApplication"

or by calling ActiveAndroid.initialize(this); in your own custom Application class as outlined here.

Problem: I am getting an "SQLiteException: no such table" error when I try to run the app

This is because ActiveAndroid only generates the schema if there is no existing database file. In order to "regenerate" the schema after creating a new model, the easiest way is to uninstall the app from the emulator and allow it to be fully re-installed. This is because this clears the database file and triggers ActiveAndroid to recreate the tables based on the annotated models in the project.

Problem: I am getting a NullPointerException when trying to save a model

This is because ActiveAndroid needs you to save all objects separately. Before saving a tweet for example, be sure to save the associated user object first. So when you have a tweet that references a user be sure to user.save() before you call tweet.save() since storing the user requires the local id to be set and assigned as the foreign key for the tweet.

This error means that you are attempting to save a object which would create a duplicate row in the database. This means that the object you are trying to save has the same remoteId (or other unique column) as an object already in the database.

With ActiveAndroid, be sure to avoid attempting to save duplicate data. Instead, you can check to ensure the data has not already been saved before saving a new object:

@Table(name="users")publicclassUserextendsModel{@Column(name="remote_id",unique=true)publiclongremoteId;// Finds existing user based on remoteId or creates new user and returns
publicstaticUserfindOrCreateFromJson(JSONObjectjson){longrId=json.getLong("id");// get just the remote id
UserexistingUser=newSelect().from(User.class).where("remote_id = ?",rId).executeSingle();if(existingUser!=null){// found and return existing
returnexistingUser;}else{// create and return new user
Useruser=User.fromJSON(json);user.save();returnuser;}}}

Then when you want to create this record and avoid duplicates, you can just call:

Useruser=User.findOrCreateFromJson(objectJson);// Returns either the existing user or the created user

This will help avoid any foreign key constraint exceptions due to duplicate rows.

Make sure to annotate the class with the @Parcel(analyze={} decorator. Otherwise, the Parceler library will try to serialize the fields that are associated with the Model class and trigger Error:Parceler: Unable to find read/write generator for type errors. To avoid this issue, specify to Parceler exactly which class in the inheritance chain should be examined (see this discussion for more details):