Android Studio + Cloud Endpoints + Objectify Persistence

Android Studio has solid support for working with Google Cloud Endpoints. I have written a series of tutorials on working with Gradle, Android Studio, Cloud Modules over here. One of the episodes in that chapter was around generating the Persistence Layer in your Endpoints using Objectify.

At the time of that writing, the skeleton wrapper code that was generated for Endpoint when you provide it your Entity class was minimal. It was of not much use and you had to pretty much write the Objectify code for list, get, add, update and remove methods.

Since then the support has been upped by several notches and I should have written about this earlier but got around to it only now. The bottom line is that if you have an Objectify annotated Entity code, Android Studio is currently doing a great job of generating the Endpoints class with all the persistence goodness of Objectify generated for you. This can greatly help to reduce the time to generate working persistence code for your Mobile backend.

Plan of Action

We shall be doing the following:

  1. Use Android Studio to generate a default Android project. We will not do much with Android here.
  2. Add a Google Cloud Module to our project generated in step 1. We shall select the Endpoints Module.
  3. Add Objectify Support to the Gradle build file for the Google Cloud Module in our project.
  4. Add our own entity class called Quote. We will annotate this with basic Objectify annotations.
  5. Use Android Studio to generate the Cloud Endpoints class for Quote.

Android Application

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.

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 specific section for 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.

gradle-ep6-11

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

Our Entity class –> Quote.java

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, let’s add the Quote.java file to in the following folder : api/src/main/java/<package-name>

Let us look at the source code now.

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;
 }
}

The final step

If you have been following my earlier series in which I discussed writing the Endpoints class on our own, this is no longer needed now since the code generation has improved a lot. All we need to do now to generate the QuoteEndpoint.java file is the following:

  1. Right click the Quote.java file and select the option “Generate Cloud Endpoint from Java class”.
  2. This will generate the QuoteEndpoint.java file. Check it out – it has all the goodness of Objectify built into it with the persistence code for get, list, add, remove and update methods. Neat .. isn’t it? I am including the generated code over here.
    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.response.CollectionResponse;
    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.ObjectifyService;
    import com.googlecode.objectify.cmd.Query;
    
    import java.util.ArrayList;
    import java.util.List;
    import java.util.logging.Logger;
    
    import javax.annotation.Nullable;
    import javax.inject.Named;
    
    import static com.googlecode.objectify.ObjectifyService.ofy;
    
    /**
    * WARNING: This generated code is intended as a sample or starting point for using a
    * Google Cloud Endpoints RESTful API with an Objectify entity. It provides no data access
    * restrictions and no data validation.
    * &lt;p/&gt;
    * DO NOT deploy this code unchanged as part of a real application to real users.
    */
    @Api(
    name = &quot;quoteApi&quot;,
    version = &quot;v1&quot;,
    resource = &quot;quote&quot;,
    namespace = @ApiNamespace(
    ownerDomain = &quot;api.mindstorm.com&quot;,
    ownerName = &quot;api.mindstorm.com&quot;,
    packagePath = &quot;&quot;
    )
    )
    public class QuoteEndpoint {
    
    private static final Logger logger = Logger.getLogger(QuoteEndpoint.class.getName());
    
    private static final int DEFAULT_LIST_LIMIT = 20;
    
    static {
    // Typically you would register this inside an OfyServive wrapper. See: https://code.google.com/p/objectify-appengine/wiki/BestPractices
    ObjectifyService.register(Quote.class);
    }
    
    /**
    * Returns the {@link Quote} with the corresponding ID.
    *
    * @param id the ID of the entity to be retrieved
    * @return the entity with the corresponding ID
    * @throws NotFoundException if there is no {@code Quote} with the provided ID.
    */
    @ApiMethod(
    name = &quot;get&quot;,
    path = &quot;quote/{id}&quot;,
    httpMethod = ApiMethod.HttpMethod.GET)
    public Quote get(@Named(&quot;id&quot;) Long id) throws NotFoundException {
    logger.info(&quot;Getting Quote with ID: &quot; + id);
    Quote quote = ofy().load().type(Quote.class).id(id).now();
    if (quote == null) {
    throw new NotFoundException(&quot;Could not find Quote with ID: &quot; + id);
    }
    return quote;
    }
    
    /**
    * Inserts a new {@code Quote}.
    */
    @ApiMethod(
    name = &quot;insert&quot;,
    path = &quot;quote&quot;,
    httpMethod = ApiMethod.HttpMethod.POST)
    public Quote insert(Quote quote) {
    // Typically in a RESTful API a POST does not have a known ID (assuming the ID is used in the resource path).
    // You should validate that quote.id has not been set. If the ID type is not supported by the
    // Objectify ID generator, e.g. long or String, then you should generate the unique ID yourself prior to saving.
    //
    // If your client provides the ID then you should probably use PUT instead.
    ofy().save().entity(quote).now();
    logger.info(&quot;Created Quote with ID: &quot; + quote.getId());
    
    return ofy().load().entity(quote).now();
    }
    
    /**
    * Updates an existing {@code Quote}.
    *
    * @param id the ID of the entity to be updated
    * @param quote the desired state of the entity
    * @return the updated version of the entity
    * @throws NotFoundException if the {@code id} does not correspond to an existing
    * {@code Quote}
    */
    @ApiMethod(
    name = &quot;update&quot;,
    path = &quot;quote/{id}&quot;,
    httpMethod = ApiMethod.HttpMethod.PUT)
    public Quote update(@Named(&quot;id&quot;) Long id, Quote quote) throws NotFoundException {
    // TODO: You should validate your ID parameter against your resource's ID here.
    checkExists(id);
    ofy().save().entity(quote).now();
    logger.info(&quot;Updated Quote: &quot; + quote);
    return ofy().load().entity(quote).now();
    }
    
    /**
    * Deletes the specified {@code Quote}.
    *
    * @param id the ID of the entity to delete
    * @throws NotFoundException if the {@code id} does not correspond to an existing
    * {@code Quote}
    */
    @ApiMethod(
    name = &quot;remove&quot;,
    path = &quot;quote/{id}&quot;,
    httpMethod = ApiMethod.HttpMethod.DELETE)
    public void remove(@Named(&quot;id&quot;) Long id) throws NotFoundException {
    checkExists(id);
    ofy().delete().type(Quote.class).id(id).now();
    logger.info(&quot;Deleted Quote with ID: &quot; + id);
    }
    
    /**
    * List all entities.
    *
    * @param cursor used for pagination to determine which page to return
    * @param limit the maximum number of entries to return
    * @return a response that encapsulates the result list and the next page token/cursor
    */
    @ApiMethod(
    name = &quot;list&quot;,
    path = &quot;quote&quot;,
    httpMethod = ApiMethod.HttpMethod.GET)
    public CollectionResponse&lt;Quote&gt; list(@Nullable @Named(&quot;cursor&quot;) String cursor, @Nullable @Named(&quot;limit&quot;) Integer limit) {
    limit = limit == null ? DEFAULT_LIST_LIMIT : limit;
    Query&lt;Quote&gt; query = ofy().load().type(Quote.class).limit(limit);
    if (cursor != null) {
    query = query.startAt(Cursor.fromWebSafeString(cursor));
    }
    QueryResultIterator&lt;Quote&gt; queryIterator = query.iterator();
    List&lt;Quote&gt; quoteList = new ArrayList&lt;Quote&gt;(limit);
    while (queryIterator.hasNext()) {
    quoteList.add(queryIterator.next());
    }
    return CollectionResponse.&lt;Quote&gt;builder().setItems(quoteList).setNextPageToken(queryIterator.getCursor().toWebSafeString()).build();
    }
    
    private void checkExists(Long id) throws NotFoundException {
    try {
    ofy().load().type(Quote.class).id(id).safe();
    } catch (com.googlecode.objectify.NotFoundException e) {
    throw new NotFoundException(&quot;Could not find Quote with ID: &quot; + id);
    }
    }
    }
    
  3. The necessary entries for the new Endpoint Java classes have been added to web.xml too. So its all wired up correctly for you.
    &lt;servlet&gt;
     &lt;servlet-name&gt;SystemServiceServlet&lt;/servlet-name&gt;
     &lt;servlet-class&gt;com.google.api.server.spi.SystemServiceServlet&lt;/servlet-class&gt;
     &lt;init-param&gt;
     &lt;param-name&gt;services&lt;/param-name&gt;
     &lt;param-value&gt;com.mindstorm.api.MyEndpoint, com.mindstorm.api.QuoteEndpoint&lt;/param-value&gt;
     &lt;/init-param&gt;
     &lt;/servlet&gt;
    
  4. Notice that basic Objectify stuff like Registering your classes, a static ObjectifyService class (ofy) is all there. This removes a lot of boilerplate code that you had to write earlier.

To see your new endpoints in action, simply run the api module and navigate over to http://localhost:8080/_ah/api/explorer.

As Android Studio gets better at code generation, this will give a huge boost to productivity. I can already see how correct code generation and running it immediately afterwards can create a big impact while demoing this to an audience.

Advertisements

7 thoughts on “Android Studio + Cloud Endpoints + Objectify Persistence

  1. Really enjoying this series. You are helping me a great deal here. Thanks.

    But could you go over on saving and loading entities with ancestors using Objectify? As in, A user has a house and I want to find witch user has said house.

    1. I think what you have mentioned can definitely be done with Objectify. However, currently if you define your Entities in that fashion and then try to generate a Google Cloud Endpoints class from that, it will give an error because Key is not one of the parametrized types that it expects. The only option for you to do at this point (to the best of my knowledge) would be to create your own endpoints class methods with required parameters and then return the data after executing Objectify specific code in the API method implementation. In short, you will need to write your own Endpoints class and methods rather than relying on the code generation.

  2. Hey Romin, Totally agree with you and really like this tutorial and the others from 2014. The code generation has really got better and helps with productivity. btw BIG HELLO from Trinidad and Tobago cheers

  3. Un gran avance, muy esperado para los que usamos Objectify.
    Generar automáticamente la clase Endpoint nos ahorra el trabajo mecánico que nadie quiere hacer.

  4. Nice article! But I do have a request… Have you taught of writing an article explaining how to write unit tests to run inside android studio direct from the Api project? It is a little more complicated than just running normal Android tests. I searched online but did not found much information about it… Right now I have my tests running directly from my Android project but I would like to isolate it from the Android project itself. Thanks for the article!

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