This lesson teaches you how to
- Prepare Your Environment
- Create Your Project
- Create the Data Layer
- Create the Persistence Layer
- Query and Update from the Android App
- Configure the C2DM Server-Side
- Configure the C2DM Client-Side
You should also read
Try it out
This lesson uses the Cloud Tasks sample code, originally shown at the Android + AppEngine: A Developer's Dream Combination talk at Google I/O. You can use the sample application as a source of reusable code for your own application, or simply as a reference for how the Android and cloud pieces of the overall application fit together. You can also build the sample application and see how it runs on your own device or emulator.
Writing an app that syncs to the cloud can be a challenge. There are many little details to get right, like server-side auth, client-side auth, a shared data model, and an API. One way to make this much easier is to use the Google Plugin for Eclipse, which handles a lot of the plumbing for you when building Android and App Engine applications that talk to each other. This lesson walks you through building such a project.
Following this lesson shows you how to:
- Build Android and Appengine apps that can communicate with each other
- Take advantage of Cloud to Device Messaging (C2DM) so your Android app doesn't have to poll for updates
This lesson focuses on local development, and does not cover distribution (i.e, pushing your App Engine app live, or publishing your Android App to market), as those topics are covered extensively elsewhere.
Prepare Your Environment
If you want to follow along with the code example in this lesson, you must do the following to prepare your development environment:
- Install the Google Plugin for Eclipse.
- Install the GWT SDK and the Java App Engine SDK. The Quick Start Guide shows you how to install these components.
- Sign up for C2DM access. We strongly recommend creating a new Google account specifically for connecting to C2DM. The server component in this lesson uses this role account repeatedly to authenticate with Google servers.
Create Your Projects
After installing the Google Plugin for Eclipse, notice that a new kind of Android project exists when you create a new Eclipse project: The App Engine Connected Android Project (under the Google project category). A wizard guides you through creating this project, during the course of which you are prompted to enter the account credentials for the role account you created.
Note: Remember to enter the credentials for your role account (the one you created to access C2DM services), not an account you'd log into as a user, or as an admin.
Once you're done, you'll see two projects waiting for you in your workspace—An Android application and an App Engine application. Hooray! These two applications are already fully functional— the wizard has created a sample application which lets you authenticate to the App Engine application from your Android device using AccountManager (no need to type in your credentials), and an App Engine app that can send messages to any logged-in device using C2DM. In order to spin up your application and take it for a test drive, do the following:
To spin up the Android application, make sure you have an AVD with a platform version of at least Android 2.2 (API Level 8). Right click on the Android project in Eclipse, and go to Debug As > Local App Engine Connected Android Application. This launches the emulator in such a way that it can test C2DM functionality (which typically works through Google Play). It'll also launch a local instance of App Engine containing your awesome application.
Create the Data Layer
At this point you have a fully functional sample application running. Now it's time to start changing the code to create your own application.
First, create the data model that defines the data shared between the App Engine and Android applications. To start, open up the source folder of your App Engine project, and navigate down to the (yourApp)-AppEngine > src > (yourapp) > server package. Create a new class in there containing some data you want to store server-side. The code ends up looking something like this:
package com.cloudtasks.server; import javax.persistence.*; @Entity public class Task { private String emailAddress; private String name; private String userId; private String note; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; public Task() { } public String getEmailAddress() { return this.emailAddress; } public Long getId() { return this.id; } ... }
Note the use of annotations: Entity
, Id
and
GeneratedValue
are all part of the Java
Persistence API. Essentially, the Entity
annotation goes
above the class declaration, and indicates that this class represents an entity
in your data layer. The Id
and GeneratedValue
annotations, respectively, indicate the field used as a lookup key for this
class, and how that id is generated (in this case,
GenerationType.IDENTITY
indicates that the is generated by
the database). You can find more on this topic in the App Engine documentation,
on the page Using
JPA with App Engine.
Once you've written all the classes that represent entities in your data
layer, you need a way for the Android and App Engine applications to communicate
about this data. This communication is enabled by creating a Remote Procedure
Call (RPC) service.
Typically, this involves a lot of monotonous code. Fortunately, there's an easy way! Right
click on the server project in your App Engine source folder, and in the context
menu, navigate to New > Other and then, in the resulting
screen, select Google > RPC Service. A wizard appears, pre-populated
with all the Entities you created in the previous step,
which it found by seeking out the @Entity
annotation in the
source files you added. Pretty neat, right? Click Finish, and the wizard
creates a Service class with stub methods for the Create, Retrieve, Update and
Delete (CRUD) operations of all your entities.
Create the Persistence Layer
The persistence layer is where your application data is stored long-term, so any information you want to keep for your users needs to go here. You have several options for writing your persistence layer, depending on what kind of data you want to store. A few of the options hosted by Google (though you don't have to use these services) include Google Storage for Developers and App Engine's built-in Datastore. The sample code for this lesson uses DataStore code.
Create a class in your com.cloudtasks.server
package to handle
persistence layer input and output. In order to access the data store, use the PersistenceManager
class. You can generate an instance of this class using the PMF class in the
com.google.android.c2dm.server.PMF
package, and then use that to
perform basic CRUD operations on your data store, like this:
/** * Remove this object from the data store. */ public void delete(Long id) { PersistenceManager pm = PMF.get().getPersistenceManager(); try { Task item = pm.getObjectById(Task.class, id); pm.deletePersistent(item); } finally { pm.close(); } }
You can also use Query objects to retrieve data from your Datastore. Here's an example of a method that searches out an object by its ID.
public Task find(Long id) { if (id == null) { return null; } PersistenceManager pm = PMF.get().getPersistenceManager(); try { Query query = pm.newQuery("select from " + Task.class.getName() + " where id==" + id.toString() + " && emailAddress=='" + getUserEmail() + "'"); List<Task> list = (List<Task>) query.execute(); return list.size() == 0 ? null : list.get(0); } catch (RuntimeException e) { System.out.println(e); throw e; } finally { pm.close(); } }
For a good example of a class that encapsulates the persistence layer for you, check out the DataStore class in the Cloud Tasks app.
Query and Update from the Android App
In order to keep in sync with the App Engine application, your Android application needs to know how to do two things: Pull data from the cloud, and send data up to the cloud. Much of the plumbing for this is generated by the plugin, but you need to wire it up to your Android user interface yourself.
Pop open the source code for the main Activity in your project and look for
<YourProjectName> Activity.java
, then for the method
setHelloWorldScreenContent()
. Obviously you're not building a
HelloWorld app, so delete this method entirely and replace it
with something relevant. However, the boilerplate code has some very important
characteristics. For one, the code that communicates with the cloud is wrapped
in an AsyncTask
and therefore not hitting the
network on the UI thread. Also, it gives an easy template for how to access
the cloud in your own code, using the RequestFactory
class generated that was auto-generated for you by the Eclipse plugin (called
MyRequestFactory in the example below), and various Request
types.
For instance, if your server-side data model included an object called Task
when you generated an RPC layer it automatically created a
TaskRequest
class for you, as well as a TaskProxy
representing the individual
task. In code, requesting a list of all these tasks from the server looks
like this:
public void fetchTasks (Long id) { // Request is wrapped in an AsyncTask to avoid making a network request // on the UI thread. new AsyncTask<Long, Void, List<TaskProxy>>() { @Override protected List<TaskProxy> doInBackground(Long... arguments) { final List<TaskProxy> list = new ArrayList<TaskProxy>(); MyRequestFactory factory = Util.getRequestFactory(mContext, MyRequestFactory.class); TaskRequest taskRequest = factory.taskNinjaRequest(); if (arguments.length == 0 || arguments[0] == -1) { factory.taskRequest().queryTasks().fire(new Receiver<List<TaskProxy>>() { @Override public void onSuccess(List<TaskProxy> arg0) { list.addAll(arg0); } }); } else { newTask = true; factory.taskRequest().readTask(arguments[0]).fire(new Receiver<TaskProxy>() { @Override public void onSuccess(TaskProxy arg0) { list.add(arg0); } }); } return list; } @Override protected void onPostExecute(List<TaskProxy> result) { TaskNinjaActivity.this.dump(result); } }.execute(id); } ... public void dump (List<TaskProxy> tasks) { for (TaskProxy task : tasks) { Log.i("Task output", task.getName() + "\n" + task.getNote()); } }
This AsyncTask
returns a list of
TaskProxy
objects, and sends it to the debug dump()
method
upon completion. Note that if the argument list is empty, or the first argument
is a -1, all tasks are retrieved from the server. Otherwise, only the ones with
IDs in the supplied list are returned. All the fields you added to the task
entity when building out the App Engine application are available via get/set
methods in the TaskProxy
class.
In order to create new tasks and send them to the cloud, create a request
object and use it to create a proxy object. Then populate the proxy object and
call its update method. Once again, this should be done in an
AsyncTask
to avoid doing networking on the UI thread. The end
result looks something like this.
new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... arg0) { MyRequestFactory factory = (MyRequestFactory) Util.getRequestFactory(TasksActivity.this, MyRequestFactory.class); TaskRequest request = factory.taskRequest(); // Create your local proxy object, populate it TaskProxy task = request.create(TaskProxy.class); task.setName(taskName); task.setNote(taskDetails); task.setDueDate(dueDate); // To the cloud! request.updateTask(task).fire(); return null; } }.execute();
Configure the C2DM Server-Side
In order to set up C2DM messages to be sent to your Android device, go back into your App Engine codebase, and open up the service class that was created when you generated your RPC layer. If the name of your project is Foo, this class is called FooService. Add a line to each of the methods for adding, deleting, or updating data so that a C2DM message is sent to the user's device. Here's an example of an update task:
public static Task updateTask(Task task) { task.setEmailAddress(DataStore.getUserEmail()); task = db.update(task); DataStore.sendC2DMUpdate(TaskChange.UPDATE + TaskChange.SEPARATOR + task.getId()); return task; } // Helper method. Given a String, send it to the current user's device via C2DM. public static void sendC2DMUpdate(String message) { UserService userService = UserServiceFactory.getUserService(); User user = userService.getCurrentUser(); ServletContext context = RequestFactoryServlet.getThreadLocalRequest().getSession().getServletContext(); SendMessage.sendMessage(context, user.getEmail(), message); }
In the following example, a helper class, TaskChange
, has been created with a few
constants. Creating such a helper class makes managing the communication
between App Engine and Android apps much easier. Just create it in the shared
folder, define a few constants (flags for what kind of message you're sending
and a seperator is typically enough), and you're done. By way of example,
the above code works off of a TaskChange
class defined as this:
public class TaskChange { public static String UPDATE = "Update"; public static String DELETE = "Delete"; public static String SEPARATOR = ":"; }
Configure the C2DM Client-Side
In order to define the Android applications behavior when a C2DM is recieved,
open up the C2DMReceiver
class, and browse to the
onMessage()
method. Tweak this method to update based on the content
of the message.
//In your C2DMReceiver class public void notifyListener(Intent intent) { if (listener != null) { Bundle extras = intent.getExtras(); if (extras != null) { String message = (String) extras.get("message"); String[] messages = message.split(Pattern.quote(TaskChange.SEPARATOR)); listener.onTaskUpdated(messages[0], Long.parseLong(messages[1])); } } }
// Elsewhere in your code, wherever it makes sense to perform local updates public void onTasksUpdated(String messageType, Long id) { if (messageType.equals(TaskChange.DELETE)) { // Delete this task from your local data store ... } else { // Call that monstrous Asynctask defined earlier. fetchTasks(id); } }
Once you have C2DM set up to trigger local updates, you're all done. Congratulations, you have a cloud-connected Android application!