Vault: an Android library for content sync

Vault

Vault is a new addition to our Android developer tool set. This post is written for the developers of Android apps, and it describes how Vault simplifies syncing content between a mobile device and Contentful.

Content sync in mobile apps

Synchronizing content between the server and local device storage greatly improves the user experience in mobile applications. In addition to making the content available in offline mode and improving loading times, it reduces data consumption and battery usage.

Contentful provides a Sync API, which enables delta content updates. This endpoint delivers only new and changed content and notifies the client(s) about deleted content.

Vault: a library for syncing content in Android apps

We've decided to further simplify developing Contentful-powered Android apps. Meet Vault: an Android library that simplifies persistence of data from Contentful via SQLite. It enables defining Java representations for your existing Contentful models. On the compilation step Vault creates a corresponding database schema by generating all the required boilerplate code and injecting it into the classpath.

Vault is bundled with a complementary lightweight runtime which exposes a simple ORM-like API for pulling resources from the generated database.

Usage

First add the dependencies to the project (assuming that you use Gradle for the builds):

1
2
compile 'com.contentful.vault:compiler:VERSION'
compile 'com.contentful.vault:core:VERSION'

Replace VERSION with the latest version of Vault, which you can find on the GitHub repository.

Configuration

Have a look at the Cat content type of the official demo space: Cat Content Type

The models are defined by declaring a subclass of the Resource class. Annotate the class with @ContentType, which takes the content type ID as the value.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@ContentType("cat")
public class Cat extends Resource {
  @Field String name;

  @Field List<String> likes;

  @Field String color;

  @Field Cat bestFriend;

  @Field String birthday;

  @Field Integer lives;

  @Field Asset image;
}

Notice that fields are defined by annotating class attributes with the @Field annotation, which by default uses the name of the attribute as the field ID, but can also be specified explicitly:

1
2
@Field("field-id-goes-here")
String someField;

The spaces can be defined by declaring a class annotated with the @Space annotation, which requires the space ID and an array of model classes:

1
2
3
4
5
@Space(
  value = "cfexampleapi", 
  models = { Cat.class }
)
public class DemoSpace { }

Arrays of resources are also supported:

1
2
3
4
5
// Array of assets
@Field List<Asset> images;

// Array of Dog entries
@Field List<Dog> dogs;

And so is the embedded JSON type:

1
@Field Map someJsonField;

Synchronization

At runtime Vault should be used with a pre-configured CDAClient:

1
2
3
4
5
6
7
8
9
10
11
12
13
// Client
CDAClient client = new CDAClient.Builder()
    .setSpaceKey("cfexampleapi");
    .setAccessToken("b4c0n73n7fu1");
    .build();

// Configuration
SyncConfig config = SyncConfig.builder()
      .setClient(client)
      .build();

// Sync
Vault.with(context, DemoSpace.class).requestSync(config);

Calling requestSync() will spawn a worker thread that fetches content via the Sync API to synchronize the remote state with its database. Once the sync is complete, a broadcast with the action Vault.ACTION_SYNC_COMPLETE will be fired. This should only be done if you want to refresh the state from the server, because the locally cached data is available without it.

Alternatively you can provide a SyncCallback which will be invoked once the sync is complete, but make sure to cancel it according to its host lifecycle events:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class SomeActivity extends Activity {
  SyncCallback callback;

  @Override protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    Vault.with(this, DemoSpace.class).requestSync(config, callback = new SyncCallback() {
      @Override public void onComplete(boolean success) {
        // Sync completed \o/
      }
    });
  }

  @Override protected void onDestroy() {
    Vault.cancel(callback);
    super.onDestroy();
  }
}

Queries

Vault provides a wrapper around the databases it generates, which can be easily used to pull persisted resources. Here are some examples:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Vault vault = Vault.with(context, DemoSpace.class);

// Fetch the first Cat
vault.fetch(Cat.class).first();

// Fetch the most recently updated Cat
vault.fetch(Cat.class)
    .order("updated_at DESC")
    .first();

// Fetch a Cat with a specific name
vault.fetch(Cat.class)
    .where("name = ?", "Nyan Cat")
    .first();

// Fetch all Cats, oredered by creation date:
vault.fetch(Cat.class)
    .order("created_at")
    .all();

Summary

Vault helps Android developers to save time on syncing content between local storage and the server: instead of writing lots of boilerplate code, in particular to represent the remote content model locally, they can now just include the library in the project and have proper models and syncing available at once. Vault is open source and available on GitHub. If you need help in setting this up, let us know, our customer success team is happy to assist you.

Blog posts in your inbox

Subscribe to receive most important updates. We send emails once a month.