fd516987de
Change-Id: I02d0d82c220401ac3a1ca180e8331a0f6dcd9e50
433 lines
18 KiB
Plaintext
433 lines
18 KiB
Plaintext
page.title=Syncing with App Engine
|
|
parent.title=Syncing to the Cloud
|
|
parent.link=index.html
|
|
|
|
trainingnavtop=true
|
|
next.title=Using the Backup API
|
|
next.link=backupapi.html
|
|
|
|
@jd:body
|
|
|
|
<div id="tb-wrapper">
|
|
<div id="tb">
|
|
|
|
<!-- table of contents -->
|
|
<h2>This lesson teaches you how to</h2>
|
|
<ol>
|
|
<li><a href="#prepare">Prepare Your Environment</a></li>
|
|
<li><a href="#project">Create Your Project</a></li>
|
|
<li><a href="#data">Create the Data Layer</a></li>
|
|
<li><a href="#persistence">Create the Persistence Layer</a></li>
|
|
<li><a href="#androidapp">Query and Update from the Android App</a></li>
|
|
<li><a href="#serverc2dm">Configure the C2DM Server-Side</a></li>
|
|
<li><a href="#clientc2dm">Configure the C2DM Client-Side</a></li>
|
|
</ol>
|
|
<h2>You should also read</h2>
|
|
<ul>
|
|
<li><a
|
|
href="http://developers.google.com/appengine/">App Engine</a></li>
|
|
<li><a href="http://code.google.com/android/c2dm/">Android Cloud to Device
|
|
Messaging Framework</a></li>
|
|
</ul>
|
|
<h2>Try it out</h2>
|
|
|
|
<p>This lesson uses the Cloud Tasks sample code, originally shown at the
|
|
<a href="http://www.youtube.com/watch?v=M7SxNNC429U">Android + AppEngine: A Developer's Dream Combination</a>
|
|
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.</p>
|
|
<p>
|
|
<a href="http://code.google.com/p/cloud-tasks-io/" class="button">Cloud Tasks
|
|
App</a>
|
|
</p>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<p>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.</p>
|
|
|
|
<p>Following this lesson shows you how to:</p>
|
|
<ul>
|
|
<li>Build Android and Appengine apps that can communicate with each other</li>
|
|
<li>Take advantage of Cloud to Device Messaging (C2DM) so your Android app doesn't have to poll for updates</li>
|
|
</ul>
|
|
|
|
<p>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.</p>
|
|
|
|
<h2 id="prepare">Prepare Your Environment</h2>
|
|
<p>If you want to follow along with the code example in this lesson, you must do
|
|
the following to prepare your development environment:</p>
|
|
<ul>
|
|
<li>Install the <a href="http://code.google.com/eclipse/">Google Plugin for
|
|
Eclipse.</a></li>
|
|
<li>Install the <a
|
|
href="http://code.google.com/webtoolkit/download.html">GWT SDK</a> and the <a
|
|
href="http://code.google.com/appengine/">Java App Engine SDK</a>. The <a
|
|
href="http://code.google.com/eclipse/docs/getting_started.html">Quick Start
|
|
Guide</a> shows you how to install these components.</li>
|
|
<li>Sign up for <a href="http://code.google.com/android/c2dm/signup.html">C2DM
|
|
access</a>. We strongly recommend <a
|
|
href="https://accounts.google.com/SignUp">creating a new Google account</a> specifically for
|
|
connecting to C2DM. The server component in this lesson uses this <em>role
|
|
account</em> repeatedly to authenticate with Google servers.
|
|
</li>
|
|
</ul>
|
|
|
|
<h2 id="project">Create Your Projects</h2>
|
|
<p>After installing the Google Plugin for Eclipse, notice that a new kind of Android project
|
|
exists when you create a new Eclipse project: The <strong>App Engine Connected
|
|
Android Project</strong> (under the <strong>Google</strong> 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.</p>
|
|
|
|
<p class="note"><strong>Note:</strong> Remember to enter the credentials for
|
|
your <i>role account</i> (the one you created to access C2DM services), not an
|
|
account you'd log into as a user, or as an admin.</p>
|
|
|
|
<p>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:</p>
|
|
|
|
<p>To spin up the Android application, make sure you have an AVD with a platform
|
|
version of <em>at least</em> Android 2.2 (API Level 8). Right click on the Android project in
|
|
Eclipse, and go to <strong>Debug As > Local App Engine Connected Android
|
|
Application</strong>. 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.</p>
|
|
|
|
<h2 id="data">Create the Data Layer</h2>
|
|
|
|
<p>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.</p>
|
|
|
|
<p>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 <strong>(yourApp)-AppEngine
|
|
> src > (yourapp) > server</strong> package. Create a new class in there containing some data you want to
|
|
store server-side. The code ends up looking something like this:</p>
|
|
<pre>
|
|
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;
|
|
}
|
|
...
|
|
}
|
|
</pre>
|
|
<p>Note the use of annotations: <code>Entity</code>, <code>Id</code> and
|
|
<code>GeneratedValue</code> are all part of the <a
|
|
href="http://www.oracle.com/technetwork/articles/javaee/jpa-137156.html">Java
|
|
Persistence API</a>. Essentially, the <code>Entity</code> annotation goes
|
|
above the class declaration, and indicates that this class represents an entity
|
|
in your data layer. The <code>Id</code> and <code>GeneratedValue</code>
|
|
annotations, respectively, indicate the field used as a lookup key for this
|
|
class, and how that id is generated (in this case,
|
|
<code>GenerationType.IDENTITY</code> indicates that the is generated by
|
|
the database). You can find more on this topic in the App Engine documentation,
|
|
on the page <a
|
|
href="http://code.google.com/appengine/docs/java/datastore/jpa/overview.html">Using
|
|
JPA with App Engine</a>.</p>
|
|
|
|
<p>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 <strong>New > Other</strong> and then, in the resulting
|
|
screen, select <strong>Google > RPC Service.</strong> A wizard appears, pre-populated
|
|
with all the Entities you created in the previous step,
|
|
which it found by seeking out the <code>@Entity</code> annotation in the
|
|
source files you added. Pretty neat, right? Click <strong>Finish</strong>, and the wizard
|
|
creates a Service class with stub methods for the Create, Retrieve, Update and
|
|
Delete (CRUD) operations of all your entities.</p>
|
|
|
|
<h2 id="persistence">Create the Persistence Layer</h2>
|
|
|
|
<p>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 <a
|
|
href="http://code.google.com/apis/storage/">Google Storage for Developers</a>
|
|
and App Engine's built-in <a
|
|
href="http://code.google.com/appengine/docs/java/gettingstarted/usingdatastore.html">Datastore</a>.
|
|
The sample code for this lesson uses DataStore code.</p>
|
|
|
|
<p>Create a class in your <code>com.cloudtasks.server</code> package to handle
|
|
persistence layer input and output. In order to access the data store, use the <a
|
|
href="http://db.apache.org/jdo/api20/apidocs/javax/jdo/PersistenceManager.html">PersistenceManager</a>
|
|
class. You can generate an instance of this class using the PMF class in the
|
|
<code>com.google.android.c2dm.server.PMF</code> package, and then use that to
|
|
perform basic CRUD operations on your data store, like this:</p>
|
|
<pre>
|
|
/**
|
|
* 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();
|
|
}
|
|
}
|
|
</pre>
|
|
|
|
<p>You can also use <a
|
|
href="http://code.google.com/appengine/docs/python/datastore/queryclass.html">Query</a>
|
|
objects to retrieve data from your Datastore. Here's an example of a method
|
|
that searches out an object by its ID.</p>
|
|
|
|
<pre>
|
|
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();
|
|
}
|
|
}
|
|
</pre>
|
|
|
|
<p>For a good example of a class that encapsulates the persistence layer for
|
|
you, check out the <a
|
|
href="http://code.google.com/p/cloud-tasks-io/source/browse/trunk/CloudTasks-AppEngine/src/com/cloudtasks/server/DataStore.java">DataStore</a>
|
|
class in the Cloud Tasks app.</p>
|
|
|
|
|
|
|
|
<h2 id="androidapp">Query and Update from the Android App</h2>
|
|
|
|
<p>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.</p>
|
|
|
|
<p>Pop open the source code for the main Activity in your project and look for
|
|
<code><YourProjectName> Activity.java</code>, then for the method
|
|
<code>setHelloWorldScreenContent()</code>. 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 {@link android.os.AsyncTask} and therefore <em>not</em> 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 <a
|
|
href="http://code.google.com/webtoolkit/doc/latest/DevGuideRequestFactory.html">RequestFactory</a>
|
|
class generated that was auto-generated for you by the Eclipse plugin (called
|
|
MyRequestFactory in the example below), and various {@code Request} types.</p>
|
|
|
|
<p>For instance, if your server-side data model included an object called {@code
|
|
Task} when you generated an RPC layer it automatically created a
|
|
{@code TaskRequest} class for you, as well as a {@code TaskProxy} representing the individual
|
|
task. In code, requesting a list of all these tasks from the server looks
|
|
like this:</p>
|
|
|
|
<pre>
|
|
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());
|
|
}
|
|
}
|
|
</pre>
|
|
|
|
<p>This {@link android.os.AsyncTask} returns a list of
|
|
<code>TaskProxy</code> objects, and sends it to the debug {@code 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 <code>TaskProxy</code> class.</p>
|
|
|
|
<p>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
|
|
<code>AsyncTask</code> to avoid doing networking on the UI thread. The end
|
|
result looks something like this.</p>
|
|
|
|
<pre>
|
|
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();
|
|
</pre>
|
|
|
|
<h2 id="serverc2dm">Configure the C2DM Server-Side</h2>
|
|
|
|
<p>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:
|
|
</p>
|
|
|
|
<pre>
|
|
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);
|
|
}
|
|
</pre>
|
|
|
|
<p>In the following example, a helper class, {@code 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 {@code TaskChange} class defined as this:</p>
|
|
|
|
<pre>
|
|
public class TaskChange {
|
|
public static String UPDATE = "Update";
|
|
public static String DELETE = "Delete";
|
|
public static String SEPARATOR = ":";
|
|
}
|
|
</pre>
|
|
|
|
<h2 id="clientc2dm">Configure the C2DM Client-Side</h2>
|
|
|
|
<p>In order to define the Android applications behavior when a C2DM is recieved,
|
|
open up the <code>C2DMReceiver</code> class, and browse to the
|
|
<code>onMessage()</code> method. Tweak this method to update based on the content
|
|
of the message.</p>
|
|
<pre>
|
|
//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]));
|
|
}
|
|
}
|
|
}
|
|
</pre>
|
|
|
|
<pre>
|
|
// 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);
|
|
}
|
|
}
|
|
</pre>
|
|
<p>
|
|
Once you have C2DM set up to trigger local updates, you're all done.
|
|
Congratulations, you have a cloud-connected Android application!</p>
|