Robin Chutaux

"Think out of the box"

Using Retrofit with ActiveAndroid

Posted on 22 December 2014.

In a previous article I talked about a smart way to use Retrofit library. This is a great library but recently I had to manage offline data and I thought ‘Hey, it would be great to download data and save it into a database to create some sort of cache :)’. I found a lot of ORM library such as GreenDAO, OrmLite, SugarORM and ActiveAndroid. I wanted a simple and powerful library. I’ve chosen ActiveAndroid library and I will explain how to use it with Retrofit.

Don’t worry, you don’t need to be a Senseï in SQLite to understand this article (I’m not ;). You just need to know what is a DataBase, a model and the definition of a foreign key. I will explain each of these points at the right moment so don’t panic, it will be easy and fast to use and to enjoy ;).

What do you need ?

First of all you will need to add a Gradle dependencie and next you will have to add some lines to the Manifest and the Application class.

Gradle

ActiveAndroid is not on GradlePlease so you will have to add the Snapshots repository to the build.gradle.

1
2
3
repositories {
    maven { url "https://oss.sonatype.org/content/repositories/snapshots/" }
}

Now you can fetch it like others Gradle libraries by adding this line in Gradle dependencies.

1
2
3
dependencies {
    compile 'com.michaelpardo:activeandroid:3.1.0-SNAPSHOT'
}

If you synchronize project with Gradle files, everything should be ok :).

Manifest and Application class

You will need to update the manifest to define the name and the version of the SQLite database. You also need to use ActiveAndroid Application class to start using this library.

Application Class

If you don’t have already created a custom Application class, you have to create a new class that extends from com.activeandroid.app.Application

1
2
3
4
5
6
7
8
public class App extends com.activeandroid.app.Application
{
    @Override
    public void onCreate()
    {
        super.onCreate();
    }
}

Or if you have already a class that extends from Application or whatever you have to call the initialize method from ActiveAndroid in the onCreate method.

1
2
3
4
5
6
7
8
9
10
11
public class App extends SomeOtherApplication
{
    @Override
    public void onCreate()
    {
        super.onCreate();

        // Here you start using the ActiveAndroid library.
        ActiveAndroid.initialize(this);
    }
}

Manifest

If you want that your application use the custom application, you have to add it in the Manifest. Moreover you have to set the name and the version of your database. The version is used to update the database tables without having to uninstall the application.

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" >
  <application
      ...
      android:name="CustomApplication">

      <meta-data android:name="AA_DB_NAME" android:value="yourDatabase.db" />
      <meta-data android:name="AA_DB_VERSION" android:value="1" />

  </application>
</manifest>

Models

The ActiveAndroid ORM is a Code-First ORM. This means that your POJOs are used to create tables in the Database. With annotations above classes and fields, you can set the name of tables and the name of the columns.

Books

This is an example that show how to use ActiveAndroid with a simple POJO.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import com.activeandroid.Model;
import com.activeandroid.annotation.Column;
import com.activeandroid.annotation.Table;

// "name" has to finish with an "s", this is a mirror of the table named Books of your database
@Table(name = "Books")
public class Book extends Model
{

    // This means that the table "Books" has a column named "Author"
    @Column(name = "Author")
    public String author;

    @Column(name = "Title")
    public String title;

    @Column(name = "Description")
    public String description;

    public List<Page> pageList;

    public Book()
    {
      // You have to call super in each constructor to create the table.
      super();
    }
}

Pages

In a book there are pages, so let’s go create a Page POJO. In this example we need to associate each page to a book it’s a many-to-one relation.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import com.activeandroid.Model;
import com.activeandroid.annotation.Column;
import com.activeandroid.annotation.Table;

// "name" has to finish with an "s", this is a mirror of the table named Pages of your database
@Table(name = "Pages")
public class Page extends Model
{
    @Column(name = "Number")
    public Integer number;

    @Column(name = "Text")
    public String text;

    @Column(name = "Chapter")
    public String chapter;

    @Column(name = "Book", onDelete = Column.ForeignKeyAction.CASCADE)
    public Book book;

    public Page()
    {
      super();
    }
}

With the book object, the library create a foreign key that is the id of the page book. The onDelete is optional, it means that if we delete a book, the pages with its foreign key will be deleted too. Next we have to add two lines to the Book class to retrieve these pages.

1
2
3
4
public List<Page> getPageList()
{
    return getMany(Page.class, "Book");
}

Optimization

Now that you know how to create a Model with ActiveAndroid, you can optimize a little bit the creation and the search process of this library by adding in the manifest the list of models. This way the library will not have to search in all your classes to find which one extends from Model. Just below the database name and version, you have to add the list of Model separated by a coma.

1
2
3
<meta-data
            android:name="AA_MODELS"
            android:value="com.example.Book, com.example.Page" />

What about Retrofit ?

The purpose of this article is to explain how to combine Retrofit and ActiveAndroid. You can use ActiveAndroid in the generic callback of Retrofit or create a custom callback class to manage it. In both cases you have to add some changes to the gson object in the RestClient class created in the previous article about Retrofit.

To avoid issue when serialization and deserialization of POJOs you have to add the @Expose annotation above fields and exclude serialization of fields without Expose annotation by calling excludeFieldsWithoutExposeAnnotation() method on the Gson object. For example Page class will look like this :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
import com.activeandroid.Model;
import com.activeandroid.annotation.Column;
import com.activeandroid.annotation.Table;

// "name" has to finish with an "s", this is a mirror of the table named Pages of your database
@Table(name = "Pages")
public class Page extends Model
{
    @Expose
    @Column(name = "Number")
    public Integer number;

    @Expose
    @Column(name = "Text")
    public String text;

    @Expose
    @Column(name = "Chapter")
    public String chapter;

    @Column(name = "Book", onDelete = Column.ForeignKeyAction.CASCADE)
    public Book book;

    public Page()
    {
      super();
    }
}

You have to do the same thing to expose fields from Book class. Now that we have all classes to make it works, take a look at this JSON example that will be used with Retrofit callback.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
[
    {
        "author": "John Smith",
        "title": "Android World",
        "description": "Lorem ipsum",
        "pages": [
            {
                "number": 1,
                "text": "Lorem ipsum",
                "chapter": 1
            },
            {
                "number": 2,
                "text": "Lorem ipsum",
                "chapter": 1
            },
            {
                "number": 3,
                "text": "Lorem ipsum",
                "chapter": 1
            }
        ]
    },
    {
        "author": "John Smith",
        "title": "Android World II",
        "description": "Lorem ipsum Lorem ipsum",
        "pages": [
            {
                "number": 1,
                "text": "Lorem ipsum Lorem ipsum",
                "chapter": 1
            },
            {
                "number": 2,
                "text": "Lorem ipsum Lorem ipsum",
                "chapter": 1
            },
            {
                "number": 3,
                "text": "Lorem ipsum Lorem ipsum",
                "chapter": 1
            }
        ]
    }
]

With this JSON you can expect to receive, in the Retrofit callback, a List of Books. Until here it’s the same thing that I’ve explained in the Retrofit article. But here, POJOs extends from Model so the difference is that you can save it into database, you just have to call save() method on each item of the received list.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
final Callback<List<Book>> bookCallback = new Callback<List<Book>>()
{
    @Override
    public void success(List<Book> books, Response response)
    {
        for (Book book : books)
        {
            book.save();
            for (Page page : book.pageList)
            {
                page.book = book;
                page.save();
            }
        }
    }

    @Override
    public void failure(RetrofitError error)
    {

    }
};

Now, you have saved all pages and books into database. You can try to fetch books and pages without network. As I said previously, you can also create a custom callback class and manage all this stuff in it.

Warning

When I used this combination for the first time, I had several issues and I wanted to warn you about it. There are two main points, the ActiveAndroid cache and the save function.

Cache Request

ActiveAndroid library has a cache system. If you have some fields in POJOs that do not exist in SQLite database then you each time you will fetch data from database the same object will be retrieved. So be careful when you are using POJOs with potentially persistent fields. If you don’t want to use the ActiveAndroid cache you can clear it by calling the static method clearCache().

1
ActiveAndroid.clearCache();

Save trap

When you instantiate a new Model, it’s not saved until you call save() and if you save an object, the children like pages of book are not saved. To create the foreign key and save child objects you have to set book reference and next call save method on page. But this will work only if book parent is previously saved because ActiveAndroid library needs an _id to create the foreign key.

Comments