Yavor is a PM at Snowflake working on developer experience. Previously at Docker, Auth0, Hulu, and Microsoft Azure.
04 March 2013
To celebrate the Mobile Services Android launch, I’ve decided to put together a blog series over the next week to highlight some of the cool new features in our Android SDK. Here is the list of subjects I plan to cover:
Today, let’s focus on the data access aspect of the client, which lets us work with server entities in a typed or untyped way. Let’s start with a simple class that we want to store in a server-side table. We’re naming our class and properties to resemble the serialized JSON, we will cover how to customize the mapping between the properties and the serialized representation in a later post.
public class droid {
public int id;
public String name;
public Float weight;
public Integer numberOfLegs;
public Integer numberOfWheels;
}
Note that a phantom menace lurks here: the class needs to contain a property called Id, ID, or id which will be populated by the server when it stores the entity.
To store instances of the class inside Mobile Services, we first need to create our Mobile Service in the Azure portal and then create a table called droid. Capitalization is important here. We then use the URL and application key provided in the portal to instantiate the MobileServiceClient.
MobileServiceClient client = new MobileServiceClient(
"https://droid.azure-mobile.net/",
"RrBQdeuaScZCIUsDSNnHPUyKowDDdW89",
this);
The third parameter we pass here is the Activity or Service where the client is instantiated. We use that reference to grab the application context, which we use to store the installation ID for this application and also to display our authentication UI dialog.
Now we need to get a reference to the table object so we can insert data into it. Let’s look at the different overloads of the getTable method.
public class MobileServiceClient {
public MobileServiceJsonTable getTable(String name);
public <E> MobileServiceTable<E> getTable(String name, Class<E> clazz);
public <E> MobileServiceTable<E> getTable(Class<E> clazz);
}
The first overload is for working with our untyped JSON-based programming model, we will discuss that in a second. The second overload lets you work with a table if its name is different from the type name. The last overload assumes the class name and the table name are the same; this is the simplest one and that’s what we’ll use here.
MobileServiceTable<droid> table = client.getTable(droid.class);
We can then instantiate a new instance of the droid class and insert it into our server-side table.
droid r2d2 = new droid();
r2d2.name = "R2D2";
r2d2.numberOfLegs = 0;
r2d2.numberOfWheels = 2;
r2d2.weight = 432.21f;
table.insert(r2d2, new TableOperationCallback<droid>() {
public void onCompleted(droid entity, Exception exception,
ServiceFilterResponse response){
if(exception == null){
Log.i(TAG, "Object inserted with ID " + entity.id);
}
}
});
A few things about the above snippet:
Note that by default mobile services have dynamic schematization turned on. That means that the objects inserted determine the columns that end up being created in the database. This is a handy feature to speed up development but you probably want to turn it off in production.
Updating an object is straightforward.
r2d2.weight = 500f;
table.update(r2d2, new TableOperationCallback<droid>() {
public void onCompleted(droid entity, Exception exception,
ServiceFilterResponse response) {
if(exception == null){
Log.i(TAG, "Updated object with ID " + entity.id);
}
}
});
So is deleting. Notice the shape of the onCompleted method on the TableDeleteCallback class is slightly different because there is no object to return.
table.delete(r2d2, new TableDeleteCallback() {
public void onCompleted(Exception exception,
ServiceFilterResponse response) {
if(exception == null){
Log.i(TAG, "Object deleted");
}
}
});
// You can also use the ID directly
// table.delete(r2d2.id, ...);
We will look at reading in a subsequent post, but for now let’s stick to a very simple example. This first snippet reads an object by ID.
table.lookUp(1, new TableOperationCallback<droid>() {
public void onCompleted(droid entity, Exception exception,
ServiceFilterResponse response) {
if(exception == null){
Log.i(TAG, "Read object with ID " + entity.id);
}
}
});
This second example will load up all server objects (a “select *” query). We will take a look at the details of the query model in a subsequent post.
table.execute(new TableQueryCallback<droid>(){
public void onCompleted(List<droid> result, int count, Exception exception,
ServiceFilterResponse response) {
if(exception == null){
for (droid d : result) {
Log.i(TAG, "Read object with ID " + d.id);
}
}
}
});
Now let’s take a look at the untyped model. Frequently having a client-side type to represent server-side data is very handy for debugging and data-binding purposes. But sometimes creating types is just cumbersome and you want to simply treat your entities as dictionaries on the client.
The key here is to get an instance of the untyped MobileServiceJsonTable by calling the right getTable overload on MobileServiceClient.
MobileServiceJsonTable untypedTable = client.getTable("droid");
Note that you need to specify the table name here since we are not supplying a type to deserialize into, so the client has no way of knowing what table name to expect.
Our untyped model relies on the gson serialization library, so we will use its JsonObject type to build up our object.The following snippet covers the same scenarios as shown above in the typed case. One difference in behavior here is that the original objects are not mutated in the insert/update case.
JsonObject c3po = new JsonObject();
c3po.addProperty("name", "C3PO");
c3po.addProperty("numberOfLegs", 2);
c3po.addProperty("numberOfWheels", 0);
c3po.addProperty("weight", 300f);
untypedTable.insert(c3po, new TableJsonOperationCallback() {
public void onCompleted(JsonObject jsonObject, Exception exception,
ServiceFilterResponse response) {
if(exception == null){
Log.i(TAG, "Object inserted with ID " +
jsonObject.getAsJsonPrimitive("id").getAsInt());
}
}
});
// In the JSON case, we don't touch the original object, so the
// id does not get added and we need to add it ourselves
c3po.addProperty("id", 8);
c3po.addProperty("weight", 200f);
untypedTable.update(c3po, new TableJsonOperationCallback() {
public void onCompleted(JsonObject jsonObject, Exception exception,
ServiceFilterResponse response) {
if(exception == null){
Log.i(TAG, "Updated object with ID " +
jsonObject.getAsJsonPrimitive("id").getAsInt());
}
}
});
untypedTable.delete(c3po, new TableDeleteCallback() {
public void onCompleted(Exception exception, ServiceFilterResponse response) {
if(exception == null){
Log.i(TAG, "Object deleted");
}
}
});
// You can also use the ID directly
// untypedTable.delete(c3po.getAsJsonPrimitive("id").getAsInt(), ...)
untypedTable.lookUp(1, new TableJsonOperationCallback() {
public void onCompleted(JsonObject jsonObject, Exception exception,
ServiceFilterResponse response) {
if(exception == null){
Log.i(TAG, "Read object with ID " +
jsonObject.getAsJsonPrimitive("id").getAsInt());
}
}
});
untypedTable.execute(new TableJsonQueryCallback() {
public void onCompleted(JsonElement result, int count, Exception exception,
ServiceFilterResponse response) {
if(exception == null){
JsonArray results = result.getAsJsonArray();
for(JsonElement item : results){
Log.i(TAG, "Read object with ID " +
item.getAsJsonObject().getAsJsonPrimitive("id").getAsInt());
}
}
}
});
That is all for today. In the next episode, we look at how to customize serialization with the power of the gson library.