Skip to content

ActiveAndroid Guide

petervdl edited this page Jun 25, 2014 · 68 revisions

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. If the term "ORM" is new to you, it is an abbreviation for "Object-Relational Mapping". Wikipedia defines it as a "technique for converting data between incompatible type systems in object-oriented programming languages. This creates a "virtual object database" that can be used from within the programming language." Another way to think of it is as a gigantic example of the Adapter pattern.

ActiveAndroid works like any ORM by mapping java classes to database tables and mapping java class member variables to table columns. Through this process, each table represents a particular Java model and the columns 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

You can download a recent ActiveAndroid JAR snapshot and drag this into the "libs" folder OR generate the latest JAR yourself based on the source as described below:

  1. Download the latest build of ActiveAndroid.
  2. Unzip the master.zip file.
  3. Open up the Terminal and navigate to the directory that was just unzipped.
  4. Type ant to build the project and generate a project jar file.
  5. Move the generated jar file from dist/ActiveAndroid.jar to the libs directory in your project home.
mv dist/ActiveAndroid.jar <your_project_home>/libs/

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"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    ...>
    <!- adjust the name property of your application node ->
    <application
        android: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-data
            android:name="AA_DB_NAME"
            android:value="RestClient.db" />
        <meta-data
            android:name="AA_DB_VERSION"
            android:value="1" />

        <activity
            android:name="com.codepath.apps.activities.MainActivity"
            android:noHistory="true"
            android:label="@string/app_name" >
            <!- ... ->
        </activity>
    </application>
</manifest>

Now you are ready to use ActiveAndroid. If you have an custom application class, check out more details for how to approach that in the installation guide.

Usage

Defining Models

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

import com.activeandroid.Model;
import com.activeandroid.annotation.Table;

@Table(name = "Items")
public class Item extends Model {
    // This is how you avoid duplicates
    @Column(name = "remote_id", unique = true, onUniqueConflict = Column.ConflictAction.REPLACE)
    public int remoteId;
    @Column(name = "Name")
    public String name;
    @Column(name = "Category", onUpdate = ForeignKeyAction.CASCADE, onDelete = ForeignKeyAction.CASCADE)
    public Category category;
    
    // Make sure to have a default constructor for every ActiveAndroid model
    public Item(){
       super();
    }
    
    public Item(int remoteId, String name, Category category){
        super();
        this.remoteId = remoteId;
        this.name = name;
        this.category = category;
    }
}

@Table(name = "Categories")
public class Category extends Model {
    // This is how you avoid duplicates based on a unique ID
    @Column(name = "remote_id", unique = true)
    public int remoteId;
    @Column(name = "Name")
    public String name;

    // Make sure to have a default constructor for every ActiveAndroid model
    public Category(){
       super();
    }

    // Used to return items from another table based on the foreign key
    public List<Item> items() {
        return getMany(Item.class, "Category");
    }
}

Note that ActiveAndroid creates a local id (mId) in addition to our manually managed remoteId (unique) which is the id on the server (for networked applications).

CRUD Operations

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

// Create a category
Category restaurants = new Category();
restaurants.remoteId = 1;
restaurants.name = "Restaurants";
restaurants.save();

// Create an item 
Item item = new Item();
item.remoteId = 1;
item.category = restaurants;
item.name = "Outback Steakhouse";
item.save();

// Deleting items
Item item = Item.load(Item.class, 1);
item.delete();
// or with
new Delete().from(Item.class).where("remote_id = ?", 1).execute();

Querying Records

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

@Table(name = "Items")
public class Item extends Model {
    // ...
    public static List<Item> getAll(Category category) {
        // This is how you execute a query
        return new Select()
          .from(Item.class)
          .where("Category = ?", category.getId())
          .orderBy("Name ASC")
          .execute();
    }
}

That's ActiveAndroid in a nutshell.

Official Reference Guides

Check these official reference guides for more detailed information:

Be sure to review the common questions below.

Common Questions

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.

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 primary key in the model?

The solution 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")
public class SampleModel extends Model {
    // Ensure the remoteId must be unique. If a duplicate tries to save, replace the 
    @Column(name = "remote_id", unique = true, onUniqueConflict = Column.ConflictAction.REPLACE)
    private int remoteId;

    // ... 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.

Problem: SQLiteConstraintException: foreign key constraint failed when saving object

This is because you need to make sure to CASCADE the delete to associated objects when an existing object is being replaced. For example, if you have a Post that contains a User, you need to:

@Table(name = "posts")
public class Post extends Model {
    @Column(name = "user", onUpdate = ForeignKeyAction.CASCADE, onDelete = ForeignKeyAction.CASCADE)
    private User user; // the embedded user in the tweet
}

Note how we are forcing the CASCADE so the user get's removed as well and can be replaced. Make sure to uninstall the app afterward on the emulator to ensure the schema changes take effect.

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

This is true, see creating your database model for more details. The column is called "mId"

Question: How do you specify the data type (int, text)? Does AA automatically know what the column type should be?

The type is inferred automatically from the type of the field.

Question: How do I store dates into ActiveAndroid?

AA supports serializing Date fields automatically. Simply:

@Column(name = "timestamp", index = true)
private Date timestamp; 

and the date will be serialized to SQLite. You can parse strings into a Date object using SimpleDateFormat:

public void setDateFromString(String date) {
    SimpleDateFormat sf = new SimpleDateFormat("EEE MMM dd HH:mm:ss ZZZZZ yyyy");
    sf.setLenient(true);
    this.timestamp = sf.parse(date);
}

Question: How do you represent a 1-1 relationship?

Check out the relationships section if you haven't yet. There are many ways to manage this. You can declare a type "User" and then that field will be associated with a foreign key representing the user.

public class User extends Model {
  public String email;
  public Address address;
}

You can manage this process by simply constructing and assigning the user object to a field of the parent object and then calling save on the parent.

User u = new User("bob@foo.com");
u.address = new Address("135 Hesby St, Los Angeles, CA");
u.address.save();
u.save();

You should make sure to call save separately on the associated object in most cases.

Question: How do I delete all the records from a table?

You can delete individual records using the .delete method and you can delete all records matching a particular condition with:

new Delete().from(Item.class).execute(); // all records
// or based on a conditional clause
new Delete().from(Item.class).where("Id = ?", 1).execute();

This allows for bulk deletes.

Question: Is it possible to do joins with ActiveAndroid?

Joins are done using the query language AA provides in the From class. You can read more here on the Querying the Database docs

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

I've seen people 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 AA can be helpful especially for simple mappings.

References

Finding these guides helpful?

We need help from the broader community to improve these guides, add new topics and keep the topics up-to-date. See our contribution guidelines here and our topic issues list for great ways to help out.

Check these same guides through our standalone viewer for a better browsing experience and an improved search. Follow us on twitter @codepath for access to more useful Android development resources.

Clone this wiki locally