Android Guide

Introduction

The Android SDK lets you make apps that use Appstax as a backend. It's a new SDK, so please let us know if you encounter any issues, or if you have any suggestions. This SDK is a small layer on top of the Java SDK which adds helper a few methods for making asynchronous requests. If you prefer your own method for writing async code, you can use the Java SDK directly.

Add the SDK to your project

<!-- app/build.gradle -->
dependencies {
    compile 'com.appstax:appstax-android:+'
}

<!-- AndroidManifest.xml -->
<uses-permission android:name="android.permission.INTERNET" />

You can also download the SDK project and check out the simple Android Studio project in examples/basic.

Initialize Appstax

To use the Appstax API, you must first create an instance of the Appstax SDK base class. Add this code to the onCreate method of your activity, replacing YourAppKey with the AppKey you received when signing up.

Appstax ax = new Appstax("YourAppKey");

Working with objects

Create & save objects

Say we want to create an object for storing contact information, then we just add the following lines of code:

AxObject object = ax.object("Contacts");
object.put("name", "Foo McBar");
object.put("email", "foo@example.com");
ax.save(object, null);

In line 1, we create an object that will be stored to the Contacts collection. Line 2 and 3 set the name and email properties of our new object. And finally, in line 4 we save the object to the Appstax server, without any callbacks.

Before you can create objects from code you must define your collection using the admin web interface.

So, for example, if you want to start saving product objects to Appstax you must first log on to the admin and create a collection named products.

Remember that collection names are case sensitive.

Asynchronous communication and completion handlers

The ax.save(object, null) call in the previous example kicks off an asynchronous HTTP call to the server and immediately returns control to your next line of code in order to avoid blocking the main thread and freezing up your UI.

To handle the server's response, provide the onSuccess and onError callback methods:

ax.save(object, new Callback<AxObject>() {
    public void onSuccess(AxObject object) {
        // Do something with the successful response.
    }
    public void onError(Exception e) {
        // Handle any errors.
    });
});

Both methods are optional, but it's a good idea to handle errors in all situations. If you're not interested in the result of the request, pass null instead of a Callback object.

Load all objects in a collection

If you want to load all the objects of a collection:

ax.find("Contacts", new Callback<List<AxObject>>() {
    public void onSuccess(List<AxObject> objects) {
        // Do something with the list of objects.
    }
    public void onError(Exception e) {
        // Handle any errors.
    }
});

Object ID

The ID of an object (sysObjectID) is accessible through the object.getId() method:

AxObject object = ax.object("Contacts");

// The ID is null before the object has been saved.
assert(object.getId() == null)

ax.save(object, new Callback<AxObject>() {
    public void onSuccess(AxObject object) {
        // The ID matches the sysObjectID column in the databrowser.
        System.out.println(object.getId());
    }
});

Load a single object

For loading a single object the following lines will do:

ax.find("Contacts", "1234-3456-234-54321", new Callback<AxObject>() {
    public void onSuccess(AxObject object) {
        // Do something with the object.
    }
    public void onError(Exception e) {
        // Handle any errors.
    }
});

Remove an object

Just call ax.remove(object, ...) and it will be deleted from your datastore:

ax.remove(object, new Callback<AxObject>() {
    public void onError(Exception e) {
        // Handle any errors.
    }
});

Refresh an object

If you already have an object reference but the data may have changed on the server, you can refresh it:

ax.refresh(object, new Callback<AxObject>() {
    public void onSuccess(AxObject object) {
        // The object has been updated.
    }
    public void onError(Exception e) {
        // Handle any errors.
    }
});

Querying Objects

There are several ways to find a selection of objects in a collection. We provide a generic query block that can be used for any request, and more specific methods for the most common use cases.

Matching property values

You can specify a map of properties and values to match:

HashMap properties = new HashMap<String, String>();
properties.put("Gender", "Male");
properties.put("Hometown", "Oslo");

ax.filter("Friends", properties, new Callback<List<AxObject>>() {
    public void onSuccess(List<AxObject> objects) {
        // Do something with the matched objects.
    }
    public void onError(Exception e) {
        // Handle any errors.
    }
});

You can perform complex match operations with a filter string:

String filter = "Age between 30 and 39 or (gender='M' and country='NORWAY')"

ax.filter("Friends", filter, new Callback<List<AxObject>>() {
    public void onSuccess(List<AxObject> objects) {
        // Do something with the matched objects.
    }
    public void onError(Exception e) {
        // Handle any errors.
    }
});

See the REST Guide for more examples on how to write filters.

Handling users

You can create new users and handle their sessions through the app. The library keeps track of the currently logged in user, and uses their details when creating or querying objects.

Security note: Make sure you review the users collection permissions in the Admin UI. If you are using email addresses as usernames or are otherwise storing sensitive information in user objects, set the "read" permissions to "owner" or "admin" level.

Signup

To create a new user:

String username = "user@example.com"
String password = "myspecialsecret"

ax.signup(username, password, new Callback<AxUser>() {
    public void onSuccess(AxUser user) {
        // The user is now signed up and logged in.
        assert(ax.getCurrentUser() == user)
    }
    public void onError(Exception e) {
        // Something went wrong.
    }
});

Login

To login a previously created user:

String username = "user@example.com"
String password = "myspecialsecret"

ax.login(username, password, new Callback<AxUser>() {
    public void onSuccess(AxUser user) {
        // The user is now logged in.
        assert(ax.getCurrentUser() == user)
    }
    public void onError(Exception e) {
        // Something went wrong.
    }
});

Social Login

If you want to let your users log in with a social network like Facebook, you create a login button and call this code in your event handler:

ax.loginWithProvider("facebook", activity, new Callback<AxUser>() {
    public void onSuccess(AxUser output) {
        // The user is logged in with a Facebook account
    }
    public void onError(Exception e) {
        // Something went wrong. The user may have declined access for your app.
    }
});

Please read our Social Login Guide for a complete description of how to use Social Login.

Logout

To logout the current user:

ax.logout(null);

Password reset

You can set up a password reset flow by using the requestPasswordReset and changePasssword methods.

Create a text field where user can provide an email address, and start the process:

String email = ""; // Get the email address from a text field

ax.requestPasswordReset(email, new Callback<Void>() {
    public void onSuccess(Void output) {
        // password reset email has been sent to user
    }
    public void onError(Exception e) {
        // ... something went wrong
    }
});

An email will be sent to the user containing a code that can then be used to specify a new password. You need to log in to the AppStax Admin UI to set up the contents of this password reset email.

Now set up a second set of text fields where the user can use the received code to set a new password, and send the values like this:

String username = "";    // Get these values from your text fields
String password = "";    // Get these values from your text fields
String code     = "";    // Get these values from your text fields
String login    = false; // Set this to true to automatically log in

ax.changePassword(username, password, code, login, new Callback<AxUser>() {
    public void onSuccess(AxUser user) {
        // Password was successfully changed.
        // user will be set if you specified login = true
    }
    public void onError(Exception e) {
        // ... something went wrong
    }
});

Note that the pin code will only be valid for 10 minutes. Note also that password reset currently only works if users provide emails as their usernames.

Add more data to user objects

You can add extra properties to user objects. This data will be saved in a special users collection, so make sure you add the appropriate columns in the Data browser first:

AxUser user = ax.getCurrentUser();
user.put("gender", "Female");
user.put("hometown", "New York");
ax.save(user, null);

Controlling access to objects

When you create new collections, all users can create and read objects from it. While this enables you to get started quickly, it may not be what you want in an app with authenticated users. To restrict access to objects and only allow explicit permissions per object, log in to the Admin UI and set the create and write collection permissions to "only owners of the objects". You can then use the API described below to grant object permissions.

Sharing objects between users

For two users to share an object, the user that created it must grant permissions on the object with the username of the other user:

// You must provide the means for the user to input a username
// to share with (like a text field or a friend list.)
// In our example the object will be shared with a user "buddy"
String username = "buddy";

AXObject note = ax.object("Notes");

note.grant(username, "read", "update");

ax.save(note, null);

The example creates a note and gives "read" and "update" permissions, but "buddy" will not be able to delete the note or give anyone else access. The following table lists all permissions a user could have for an object:

With permission ... ... user is able to
"read" Load the object and view all its properties. It will be included in queries.
"update" Change property values on the object and save it.
"delete" Delete the object from storage.
"grant read" Grant and revoke "read" access to others.
"grant update" Grant and revoke "update" access to others.
"grant delete" Grant and revoke "delete" access to others.

The previous example granted permissions on a new object, but of course you can do it on existing objects as well. Just remember to call ax.save(object, ...) to persist the permission:

ax.find("Posts", "1234-3456-234-54321", new Callback<AxObject>() {
    public void onSuccess(AxObject object) {
        object.grant("buddy", "read");
        ax.save(object, null);
    }
});

Revoking permissions

Removing someones access to an object is just as easy. If "buddy" has "read" and "update" permissions, the following would leave him with just "read" permissions:

object.revoke("buddy", "update")
ax.save(object, null);

Public access

In some applications you will want everyone, including anonymous users, to see or interact with objects. Some use cases are wikis, blogging and public photo sharing applications. Objects have special methods for granting and revoking public access:

// Publishing a blog post
post.grantPublic("read");
ax.save(post, null);

// Removing post from public
post.revokePublic("read")
ax.save(post, null);

// Creating a wiki article anyone can edit
article.grantPublic("read", "update");
ax.save(article, null);

Working with files

You add files to your application by using the file column type in the Data browser and the AxFile in the SDK.

AxFile file = ax.file("filename", bytes);

Saving files

Now let's say you add an Attachment column to your Notes collection. You then let your user upload a file by assigning it to the attachment property and saving the note object.

// Create a note
AxObject note = ax.object("Notes");
note.put("Title", "My note with attachment");

// Create an AXFile instance and set as 'Attachment' property
AxFile file = ax.file("filename", bytes);
note.put("Attachment", file);

// Saving the object also uploads the file(s) belonging to the object
ax.save(note, new Callback<AxObject>() {
    public void onSuccess(AxObject object) {
        // The note is saved and the attachment uploaded
    }
});

Loading the file data

File data is not included when loading objects. Otherwise, loading objects would cause a lot of unnecessary network traffic. To load the contents of a file, use ax.load(file, ...):

// The file data is null by default.
assert(file.getData() == null);

ax.load(file, new Callback<AxFile>() {
    public void onSuccess(AxFile file) {
        // You can now access the file data.
        assert(file.getData() != null);
    }
});

Relations

As your data model grows, you will often need objects to reference other objects. There are two types of relations you can add:

  • A single relation adds a property that can reference one other appstax object.
  • An array relation adds an array where you can add as many appstax objects as you like.

If you come from a database modelling background you will find single relations useful for creating one-to-one and many-to-one models, while array relations work well for one-to-many and many-to-many models.

Single relations (one-to-one / many-to-one)

Given two collections invoices and customers, we can add a .customer single relation to the invoices collection in order for an invoice to include customer information without duplicating the details into all the invoices.

To create the relation in the databrowser: Select the invoices collection, click the relations button.

To create the relation on the command line, run appstax relation invoices.customer customers

Let's create a new customer and his first invoice and save them both with saveAll:

AxObject customer = ax.object("customers");
AxObject invoice  = ax.object("invoices");

customer.put("name", "Bill Buyer");
customer.put("address", "Money Hill");

invoice.put("total", 199);
invoice.createRelation("customer", customer);

ax.saveAll(invoice, new Callback<AxObject>() {
    public void onSuccess(AxObject output) {
        // ... both objects have been saved
    }

    public void onError(Exception e) {
        // ... handle any errors
    }
});

Choosing between ax.save and ax.saveAll

An object needs to be saved before it can be used in a relation, and calling save will fail if it finds relations with unsaved objects. You can choose to call save on each object yourself, or call saveAll to have all the related objects saved automatically.

To load invoices and include customer data you can use the find method with an expand depth argument. The number specifies how many levels of relations should be returned. You can then use getObject or getObjects to find the related items:


ax.find("invoices", 1, new Callback<List<AxObject>>() {
    public void onSuccess(List<AxObject> output) {
        String name = output.get(0).getObject("customer").getString("name");

        // ...
    }
});

Array relations (one-to-many / many-to-many)

Given two collections blogs and posts, we can connect the posts to a blog by adding a .posts[] array relation to the blog collection.

To create the relation in the databrowser: Select the blogs collection and click the relations button.

To create the relation on the command line, run appstax relation blogs.posts[] posts

Let's create a new blog and a couple of posts:

// create the blog
AxObject blog = ax.object("blogs");
blog.put("title", "Zen");

// create a post
AxObject post1 = ax.object("posts");
post1.put("title", "My first post");
post1.put("content", "...");

// create another post
AxObject post2 = ax.object("posts");
post2.put("title", "My second post");
post2.put("content", "...");

// add posts to blog and save
blog.createRelation("posts", post1, post2);
ax.saveAll(blog, null); // saves both posts and blog

We can now load all blogs, including posts, with a query using find/findAll with a depth parameter:

ax.find("blogs", 1, new Callback<List<AxObject>>() {
    public void onSuccess(List<AxObject> output) {
        List<AxObject> posts = output.get(0).getObjects("posts");

        String title1 = posts.get(0).getString("title");
        String title2 = posts.get(1).getString("title");

        // ...
    }
});

Relations and access control

Querying relations

Querying objects with relations enforces "read" permissions. For example, a blog.posts[] relation may have many posts, but a call to ax.find("blogs", "blogId", 1, null) will only include those posts the current user has "read" access to.

The kind of access restrictions you want on objects will affect the relations model you should use. Adding or removing related objects requires "create" or "update" permissions on the object where the relation is stored. In practise, this often means choosing between implementing one-to-many or many-to-one using array or single relations.

Example 1: If you have a blog data model with posts and comments collections, you may let users comment on posts by adding a post.comments[] array relation. But in order for users to add comments in this model they would need "update" permissions on the posts object. This is probably not what you want, as it would allow all users to change the contents of the blog post. Instead you should use a comment.post single relation, which will allow anyone to add a comment referencing any post.

Example 2: On the flip side of example 1 you may have blogs and posts in your data model and only want the owner of a blog to add posts to it. If you do this with a post.blog single relation, anyone would be able to add posts to the blog. If you instead choose a blog.posts[] array relation, adding posts will be restricted to users with "update" permissions to the blog.

Realtime Channels

Realtime channels is currently in beta. Drop us an email at support@appstax.com if you want to try it out!

Channels use WebSockets to provide bidirectional communication between the backend and your app.

There are three types of channels: public channels, private channels, and object channels:

  • Public channels are open to all users. Anyone can send and receive messages.
  • Private channels lets you grant or revoke read/write access to specific users.
  • Object channels lets you use sockets to observe changes to a specific collection.

A channel identifier consists of its type, a slash, and then an name given by the developer:

// A public channel called 'foobar':
ax.channel("public/foobar")

// A private channel identified by an email:
ax.channel("private/james@example.com")

// A channel for updates to the "notes" collection:
ax.channel("objects/notes")

Public channels

Public channels are the simplest type of channel. Any user can subscribe to a public channel, read all messages sent to that channel, and send new messages to the other subscribers. Here's an example:

To send a message, call the send method. You can send plain string, or any AxObject:

AxObject object = ax.object("messages");

object.put("date", new Date());
object.put("text", "Hello World!");

ax.channel("public/messages").send(object);

To receive messages, call the listen method with a set of callbacks:

ax.channel("public/chat").listen(new AxListener() {

    public void onOpen() {
        // called when you've just subscribed
    }

    public void onClose() {
        // called when you unsubscribe
    }

    public void onMessage(AxEvent event) {
        // called when a new message arrives
    }

    public void onError(Exception e) {
        // Something went wrong
    }

});

In onMessage, the AxEvent object contains the message that was sent by the other user. If your users are sending strings, you can use event.getString() to retrieve it. If they are sending objects, use event.getObject(). See the chatstax sample app for a more detailed example.

Private channels

Private channels are public channels with the possiblity of access control.

Before you can send or listen on a private channel it must be created. Conversely, and unlike public channels, private channels can be deleted. You can use grant and revoke to control read and write access.

// Private channels have their own prefix:
AxChannel ch = ax.channel("private/john/jane")

// Private channels must be explicity created:
ch.create();

// Use grant/revoke to control access:
ch.grant("read", "jane");
ch.grant("write", "jane");

// You can now send messages:
ch.send("Hello Jane!");

Object channels

Object channels lets your observe changes to a collection without resorting to periodic polling.

In the following example, the app listens for any additions, updates or deletions to objects in the notes collection:

ax.channel("objects/notes").listen(new AxListener() {
    public void onMessage(AxEvent event) {

        // The event type can be "object.created", "object.updated", or "object.deleted"
        event.getType();

        // Call getObject to get the object that was changed:
        AxObject obj = event.getObject();

    }
});

These channels lets you display news or react to changes to objects in your collections, without having to check the server periodically for new updates. Unlike public and private channels, you can't send directly to an object channel. Use the normal AxObject methods to perform changes to the collection.

If you're only interested in certain objects, you can add a filter to the channel:

ax.channel("objects/notes").filter("title like 'Hello%'").listen(new AxListener() {
    public void onMessage(AxEvent event) {
        event.getObject().getString("title"); // => "Hello World!"
    }
});

The filter syntax is the same as the one used when querying objects.