Gradle Tutorial : Part 9 : Cloud Endpoints + Persistence + Android Studio

Welcome to Part 9 of the Gradle Tutorial. In the previous episode i.e. Part 8, we looked at support for Cloud Endpoints in your Android Project and how it can form the basis for your mobile backend. Our focus in that part was to understand the mechanics of Cloud Endpoints inside Android Studio, testing out our API via the local API Explorer and so on.

LAST UPDATE : January 22, 2015 :
i) Updated app/build.gradle as per the latest version of Android Studio 1.0.2
ii) Do note that the current code is meant to work with Objectify 5.0.3 version

This part of the tutorial is an extension of Part 8, maybe I should have called it Part 8 – Part II 🙂 but I am keeping it separate here so that we can focus on what it is meant to do i.e. address Persistence using Objectify in your Cloud Endpoitns.

For those of you, who are not aware of Cloud Endpoints, I strongly recommend going through a couple of my blog posts, where I have covered the premise for Cloud Endpoints and Cloud Endpoints basics in the first two parts of my Cloud Endpoints blog series. They are present here : Part 1 and Part 2.

ep8-logo

This part assumes that you have installed Android Studio on your development machine and that you are familiar with basic Gradle commands, project structure and files as we have seen in the series so far.

I have used Android Studio (Beta) 1.0.2 on Windows for this blog post.

We shall cover the following in this blog post:

  • Begin with a default Android application in Android Studio. Add an App Engine module to the Android Project. This part is the same as Part 8, so I will only mention the steps without putting in any screen shots. 
  • We are going to be using Objectify, a library that makes the task of adding persistence to your App Engine applications, as simple as it can get. Behind that simplicity is some serious piece of work, so a big thank you to the developer of Objectify.
  • We then develop a Quote Endpoint service, a simple service that allows us to manage Quotes by famous people. We shall look at methods to add, modify, delete and list Quotes and it will be backed with a persistent layer powered by Objectify.
  • We shall study the code in the Endpoints, test it out and also understand the additional gradle build dependency needed for Objectify.

Android Application

Allright, I believe you know the process now to generate an Android Application inside of Android Studio, so I will not give too many details like screen shots. Let me state the steps again and please follow along in Android Studio:

  1. Click on New Project in the Quick Launch dialog. This should bring up the New Project wizard. Give it a name and company domain.
  2. Click on Next and keep following all the default stuff till you Finish.

Be a bit patient and let your Android project build successfully.

Add an App Engine Module

Now that we have an Android project and an Android app module within the project i.e. app, let us move forward and make this a multi-module project by adding an App Engine module to this project. We covered this in Part 7, if you want to take a look.

The steps are straightforward:

  1. Click on File –> New Module
  2. Select Google Cloud Module in the first step. Click on Next.
  3. In the New Google Cloud Module step, select App Engine Java Endpoints module for the module type.
  4. Select the module name as api and give a package name as needed.

part8-1

Click on Finish to generate the project.

You should now have two modules in your project i.e. app (Android) and api (App Engine).

Gradle Files

If you are new to Gradle, I suggest following my earlier episodes, but for now, the only thing we are going to look at now is the build.gradle file in the api module.

The reason for that is that we are going to use Objectify for our persistene code and it is an external library.

The relevant section from api/build.gradle file is shown below:

api module – build.gradle

The api/build.gradle file is shown below:

...

dependencies {
 appengineSdk 'com.google.appengine:appengine-java-sdk:1.9.14'
 compile 'com.google.appengine:appengine-endpoints:1.9.14'
 compile 'com.google.appengine:appengine-endpoints-deps:1.9.14'
 compile 'com.googlecode.objectify:objectify:5.0.3'
 compile 'javax.servlet:servlet-api:2.5'
}
...

All we have done is added the objectify dependency to our build.gradle file. We are using the latest version of the library.

That’s about it really as far as the Gradle stuff is concerned.

Tip: Keep in mind that anytime you make any changes to your build files, do remember to click on the Sync Project with Gradle Files as shown below. It should launch the build and you should be able to see the Gradle commands getting executed in the Gradle Console.

Please do this, so that the Objectify dependency is downloaded to your local repository and you are all set!

gradle-ep6-11

Cloud Endpoints – Understand the files

This section assumes that you are familiar with to some extent with Google Cloud Endpoints in Java. I recommend going through Part 1 and Part 2 of my tutorials on End points, if you are absolutely new to Endpoints.

By default only the MyBean and MyEndpoint files would be generated for you. We saw that in the previous episode and you can ignore them for now, since we will be generating a new Endpoint class on our own. 

The model that we shall be managing here is a simple Quote object. A Quote is a famous saying by a person and we are going to keep it simple. It will have 3 attributes:

  • Id : This will be a unique value for the record when it is persistent in the Datastore.
  • Who : This is the name of the person who said the quote i.e. to whom the quote is attributed.
  • What : The text of the Quote.

So technically, we shall have at least 2 files i.e. the Quote.java file, which will be the Java Bean with some annotations for persistence and the QuoteEndpoint.java file, which will have the Endpoint specific code with all its annotations.

Additionally, we may have our other support files as needed.  Remember that I have created all these Java classes in the following path :
– api/src/main/java/<package-name>

Let us look at the source code now.

Quote.java


package com.mindstorm.api;

import com.googlecode.objectify.annotation.Entity;
import com.googlecode.objectify.annotation.Id;

@Entity
public class Quote {
 @Id
 Long id;
 String who;
 String what;

public Quote() {}

public Long getId() {
 return id;
 }

public void setId(Long id) {
 this.id = id;
 }

public String getWho() {
 return who;
 }

public void setWho(String who) {
 this.who = who;
 }

public String getWhat() {
 return what;
 }

public void setWhat(String what) {
 this.what = what;
 }
}

Let us look at the important points in the above code:

  • It is a standard bean and you can see our 3 attributes i.e. id, who and what.
  • Two annotations are important from an Objectify perspective. We need to mention to Objectify that this is an entity that needs to be persisted, there is the @Entity annotation for that and we have it right at the top at the class level.
  • Additionally, we are specifying the @Id attribute for the attribute that is the identifier.

This should suffice for us. Do note that you should definitely study the Objectify documentation for more of the annotation stuff.

QuoteEndpoint.java


package com.mindstorm.api;

import com.google.api.server.spi.config.Api;
import com.google.api.server.spi.config.ApiMethod;
import com.google.api.server.spi.config.ApiNamespace;
import com.google.api.server.spi.config.Nullable;
import com.google.api.server.spi.response.CollectionResponse;
import com.google.api.server.spi.response.ConflictException;
import com.google.api.server.spi.response.NotFoundException;
import com.google.appengine.api.datastore.Cursor;
import com.google.appengine.api.datastore.QueryResultIterator;
import com.googlecode.objectify.cmd.Query;

import static com.mindstorm.api.OfyService.ofy;

import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;

import javax.inject.Named;

@Api(name = "quoteEndpoint", version = "v1", namespace = @ApiNamespace(ownerDomain = "api.mindstorm.com", ownerName = "api.mindstorm.com", packagePath=""))
public class QuoteEndpoint {

// Make sure to add this endpoint to your web.xml file if this is a web application.

public QuoteEndpoint() {

}

/**
* Return a collection of quotes
*
* @param count The number of quotes
* @return a list of Quotes
*/
@ApiMethod(name = "listQuote")
public CollectionResponse<Quote> listQuote(@Nullable @Named("cursor") String cursorString,
@Nullable @Named("count") Integer count) {

Query<Quote> query = ofy().load().type(Quote.class);
if (count != null) query.limit(count);
if (cursorString != null && cursorString != "") {
query = query.startAt(Cursor.fromWebSafeString(cursorString));
}

List<Quote> records = new ArrayList<Quote>();
QueryResultIterator<Quote> iterator = query.iterator();
int num = 0;
while (iterator.hasNext()) {
records.add(iterator.next());
if (count != null) {
num++;
if (num == count) break;
}
}

//Find the next cursor
if (cursorString != null && cursorString != "") {
Cursor cursor = iterator.getCursor();
if (cursor != null) {
cursorString = cursor.toWebSafeString();
}
}
return CollectionResponse.<Quote>builder().setItems(records).setNextPageToken(cursorString).build();
}

/**
* This inserts a new <code>Quote</code> object.
* @param quote The object to be added.
* @return The object to be added.
*/
@ApiMethod(name = "insertQuote")
public Quote insertQuote(Quote quote) throws ConflictException {
//If if is not null, then check if it exists. If yes, throw an Exception
//that it is already present
if (quote.getId() != null) {
if (findRecord(quote.getId()) != null) {
throw new ConflictException("Object already exists");
}
}
//Since our @Id field is a Long, Objectify will generate a unique value for us
//when we use put
ofy().save().entity(quote).now();
return quote;
}

/**
* This updates an existing <code>Quote</code> object.
* @param quote The object to be added.
* @return The object to be updated.
*/
@ApiMethod(name = "updateQuote")
public Quote updateQuote(Quote quote)throws NotFoundException {
if (findRecord(quote.getId()) == null) {
throw new NotFoundException("Quote Record does not exist");
}
ofy().save().entity(quote).now();
return quote;
}

/**
* This deletes an existing <code>Quote</code> object.
* @param id The id of the object to be deleted.
*/
@ApiMethod(name = "removeQuote")
public void removeQuote(@Named("id") Long id) throws NotFoundException {
Quote record = findRecord(id);
if(record == null) {
throw new NotFoundException("Quote Record does not exist");
}
ofy().delete().entity(record).now();
}

//Private method to retrieve a <code>Quote</code> record
private Quote findRecord(Long id) {
return ofy().load().type(Quote.class).id(id).now();
//or return ofy().load().type(Quote.class).filter("id",id).first.now();
}

}

This is our main Endpoint class. Note that this class is not being generated currently by Android Studio. So do not expect that you will simply provide the annotations and Android Studio will be able to generate it for now. I do expect that in future versions, all this will be addressed with much more solid code but for now, this should give you a flavor for how easy Objectify makes it to add persistence to your code.

Let us understand what is going on in the code:

  1. We want to provide an Endpoints layer for our Quote object. And that is what this class QuoteEndpoint.java is all about.
  2. We plan to have methods to add, modify, list and delete Quote objects. Hence you see those methods implemented.
  3. We are using the annotation @Api at the class level to mark this as an Endpoint class. The name is quoteEndpoint.
  4. Each of the methods is annotated with the @Apimethod annotation and we provide the method name via the name attribute.
  5. Objectify code is beautifully summarized by a snippet shown in the original documentation, which I reproduce here. Just look at the pattern for Add, Get and Delete operations and you will understand it.ep9-1
  6. In the insertQuote method, we are first checking if a record exists in the database with the same id. If it exists, pay close attention to the Exception that we are throwing. These are standard Exceptions available in the Endpoint package and you should throw them, so that the correct HTTP Code is returned in the response too. If the record is not present, we go ahead and save the entity that was passed. Since it is a Long type, Objectify will generate the id for us automatically. The now() is the synchronous nature of the call, where we want it to execute immediately. The ofy() is a static object that is a handle to the Objectify Service and that utility class is covered in the next section.
  7. Similarly, take a look at updateQuote and deleteQuote methods. It should be straight forward.
  8. The last method that I want to touch up here is the listQuote API method. I have tried to reproduce this as much as I could from the way the code was generated by the Eclipse plugin for JDO supported code earlier. For those of you from the JDO Endpoints world of Eclipse, this will look similar. For those of you not familiar with that, don’t worry.
  9. The listQuote method supports optional parameters where you can provide a count i.e. number of records you want and in subsequent calls, you can even give the cursor from where it should give the next set of records. The rest of the code is standard Objectify code for querying the object, iterating through the number of records required, putting them in a list to return and remembering to set the cursor in case the client wants to make subsequent calls.

That’s about it for our Endpoint. Spend some time with the code and Objectify documentation and you will understand things better. Or just use my code as a rough template for your own Endpoint classes.

OfyService.java


package com.mindstorm.api;

import com.googlecode.objectify.Objectify;
import com.googlecode.objectify.ObjectifyFactory;
import com.googlecode.objectify.ObjectifyService;

/**
 * Objectify service wrapper so we can statically register our persistence classes
 * More on Objectify here : https://code.google.com/p/objectify-appengine/
 *
 */
public class OfyService {

static {
 ObjectifyService.register(Quote.class);
 }

public static Objectify ofy() {
 return ObjectifyService.ofy();
 }

public static ObjectifyFactory factory() {
 return ObjectifyService.factory();
 }
}

This is a utility class that we have written that provides an instance to the Objectify service i.e. the ofy() object that you saw earlier in the Endpoint code. One important thing is that you need to tell Objectify which Entity objects it can deal with i.e. you have to register them upfront. We are registering our Quote entity via the line 

 ObjectifyService.register(Quote.class);

In case you have other endpoint classes in your module, please remember to register those in the ObjectifyService.java class too.

Endpoints in Action

This should standard stuff now i.e. running the local App Enging Development Server.

Important

Before you jump right ahead and try to launch the Dev App Server, remember to make sure that the Endpoints class is added to the web.xml file. 

Visit the api/src/main/webapp/WEB-INF/web.xml file and add the QuoteEndpoint class as shown below:

s18

Follow the next steps to launch the local development server:

  1. Launch a Terminal window as shown below. Navigate to the root folder and then fire the gradle command as shown below. Do note that we are simply starting up the App Engine Local Dev Server here via the appengineRun task. We are prefixing it with the module name i.e. api , since we are at the root and we need to specify which module and Gradle Task inside of that, which we are interested in firing.s22
  2. Once the server is started, you should see the a message saying that the local server is running as shown below:

s23

Once the Local Dev Server is started, go ahead and visit the local dev server at localhost:8080 URL via browser as shown below.

s8

We need to now access the local API Explorer that will help us test out the functionality of our Quote Endpoint class.

Typically, this is accessible via http://localhost:8080/_ah/api in your browser. You could either type that out or you can click on the Google Cloud Endpoints API Explorer as shown below:

s10

Click that and it should bring up the API Explorer as shown below. Make sure you are connected to the Internet.

ep9-2

The above screen indicates that you have two Endpoint APIs, the default MyAPI if you have not removed the default one and also our quoteEndpoint API:

Click on the quoteEndpoint API to see the different methods that we had exposed. You should see all the methods listed as shown below:

ep9-3

Click on the quoteEndpoint.insertQuote method. This will bring up a form where you can test out the API:

ep9-4

Notice how the API Explorer is able to introspect your method parameters and provide you a convenient HTML form, where you can enter your parameter values. Enter some values (for who and what attributes, leave the id field blank since it will be autogenerated) and click on Execute. This will invoke your API, and show you the request / response details as given below:

ep9-5

Similarly do try out other methods i.e. listQuote, removeQuote .. and it should all work. If you are looking to understand the count and cursor parameters, do refer to this tip : Using Cursor and Limit Parameters, that I wrote.

Here is a screenshot from the listQuote execution:

ep9-6

In case you want to check if the records have been persisted in the local datastore, you can visit the local Admin app via http://localhost:8080/_ah/admin URL. Visit the Datastore Viewer link and you should see your Entity (Quote) and the various records there.

ep9-7

Download Project

The Android Studio project for this episode has been hosted on Github. If you want, you can download that.

Moving forward

This tutorial helped you understand writing real Endpoint classes which implement persistence. Specifically, we looked at Objectify library to build in persistence support.

In the next episode, we shall look at integrating the Endpoints code into our Android project.

Till then, Happy Gradling! or should I say “Happy Cloud Endpoints Generation”!

Advertisements

25 thoughts on “Gradle Tutorial : Part 9 : Cloud Endpoints + Persistence + Android Studio

  1. why cant I insert a quote object this way from client android app

    quoteApi = service.build();

    Quote quote = new Quote();
    quote.setWho(“Shakespear”);
    quote.setWhom(“Life is not bed of roses”);
    quoteApi.insert(quote).execute();

  2. Please note, the sample code doesn’t work with objectify version 5.1.3.

    It will throw this error You have not started an Objectify context. You are probably missing the ObjectifyFilter

  3. Hi, I’ve noticed that when i execute gradlew :appengineRun, it’s stuck at Building 88% > :backend:appengineRun. Then I cannot execute any command via the terminal. In order to execute any command, I need to close the terminal session. Any workaround??

    1. Nothing to workaround!
      This is because the jetty-server-instance is running on this process.
      Try (Strg + C) to cancel the process (this also kill the jetty instance) and to free your console.

  4. Hey, this tutorial was really helpful. There is nothing in the whole web, that explains Objectify with Endpoints so nicely.
    But I am having a little trouble with the other Endpoint API methods. Can you possibly give an example of how I could use the get, delete etc methods as well. That would be really helpful.
    If I follow your tutorial correctly, and use this, ..
    Long id = Long.getLong(“9649299534313129”);
    Quote q1= endpointBldr.get(id).execute();

    It gives a Null Pointer Exception .. Where am I going wrong. ?
    Thanks

  5. Is there any chance you can show us how to implement some example of relationship (ManyToMany, ManyToOne, OneToMany) in endpoint projects? There some examples how to, but there isn’t complete tutorial, so I am stuck 😦
    Thanks…
    P.S: This tutorial is awesome 🙂

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s