This blog article demonstrates how to get started with App Engine’s Cloud Endpoints Framwork v2.0 in Android Studio and details configuration necessary for migration from version 1.0 of App Engine Cloud Endpoints.
Selecting a cloud platform serving as backend for an Android or web application can be tedious, there are numerous services such as Heroku, Amazon AWS, Google App Engine as well as providers offering hosting for Docker containers.
While there are a large variety of factors that should influence your decision of choosing the right platform, such as how complex maintenance is, how price compares and how much many ties there are to any given platform that might lock you in, it is still tempting to go ahead and just try out a platform that offers the most convenience.
Thus, when developing Android applications in Android Studio it is quite difficult to oversee the apparent ease of integration into Google App Engine.
One of the most convenient features of writing backend code in Java when using it together with an Android application, is the reuse of class code that is shared between the Android client application and the server side code.
When checking the “New Module” dialog in Android Studio, there are a couple of different ways to set up an App Engine service. The option “App Engine Java Endpoints Module” seems to offer an impressive set of features, among them Security (+ Authentication), Monitoring, Performance.
Having spent a good amount of time to understand how user registration, authentication and account maintenance in a web application work, it becomes apparent that not only implementation is very effort intensive, also storing and accessing user data in a secure and save way proves to be a challenge as well.
Especially maintaining data such as passwords and private user information in a secure way can lead to large overhead for a startup project. The solution for this is to use 3rd party authentication services and even better, to let users log in by using existing one of their accounts such as their Google Login, Facebook, Twitter or Github service. One of the 3rd parties offering ease of access is “Firebase Auth”. And coincidentally they also offer integration into App Engine Cloud Endpoints.
Now of course it goes without saying, that when using a service like that, the application will heavily rely on the existence of the 3rd party service, meaning that if for some reason the provider shuts down or has issues with their service, the users will most probably will not be able to log into the web application depending on authentication.
Set aside all the considerations and fears of using any services at all, let’s dive into setting up a project with Google Cloud Endpoints.
https://cloud.google.com/endpoints/
One of the first things to notice is that currently there seems to be two versions of Cloud Endpoints available. The most recent iteration (at the time of this writing version 2.0) promises improvements of performance, reduced latency, more features. The only caveat, currently Android Studio sets up a Cloud Endpoints module with version 1.0. Of course this is utterly unacceptable you might agree, which is why migrating to version 2.0 is the only real option.
This article will help you to get started to get your Android Application and the Cloud Endpoints Framework v2.0 running on a local development server. Setting up a Google App Engine account for the purpose of local debugging is not even necessary.
While there are official guides that help migration to Cloud Endpoints v2.0 they don’t really address how to do this in Android Studio, let alone with gradle, thus this guide will try to fill the gap by guiding the reader through the process of setting up a new project.
Let’s get started.
Creating an Android Application with a Cloud Endpoints Module
First create a new Android Application Project inside Android Studio with an Empty Activity.
Your Project should look like in the image below.
After Gradle is synced, add a new module of the type “App Engine Java Cloud Endpoints Module”.
After the module is created, be sure to wait until Gradle syncs (possibly hit the gradle sync button above, or go to Tools > Android > Sync Project with Gradle Files.
You will notice that besides the new module, there is also a new gradle build file for “backend” available.
Open it in Android Studio and replace its contents with the ones below:
Use the gradle code available here:
// Copyright 2017 Google Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // [START buildscript] buildscript { // Configuration for building repositories { mavenCentral() jcenter() // Bintray's repository - a fast Maven Central mirror & more } dependencies { // App Engine Gradle plugin classpath 'com.google.cloud.tools:appengine-gradle-plugin:1.3.0' // Endpoints Frameworks Gradle plugin classpath 'com.google.cloud.tools:endpoints-framework-gradle-plugin:1.0.0-beta9' } } // [END buildscript] repositories { // repositories for Jar's you access in your code mavenCentral() jcenter() } apply plugin: 'java' // standard Java tasks apply plugin: 'war' // standard Web Archive plugin // [START apply_appengine] apply plugin: 'com.google.cloud.tools.appengine' // App Engine tasks // [END apply_appengine] // [START apply_endpoints-framework-server] apply plugin: 'com.google.cloud.tools.endpoints-framework-server' // [END apply_endpoints-framework-server] dependencies { providedCompile group: 'javax.servlet', name: 'servlet-api', version:'2.5' compile 'jstl:jstl:1.2' compile group: 'javax.inject', name: 'javax.inject', version: '1' // Uncomment to use Endpoints Frameworks v1.0 // compile group: 'com.google.appengine', name: 'appengine-endpoints', version: '1.9.48' // End of Endpoints Frameworks v1.0 // Endpoints Frameworks v2.0 // [START endpoints-tools] compile group: 'com.google.endpoints', name: 'endpoints-framework-tools', version: '2.0.7' // [END endpoints-tools] // End of Endpoints Frameworks v2.0 } appengine { // App Engine tasks configuration deploy { // deploy configuration version = findProperty("appengine.deploy.version") def promoteProp = findProperty("appengine.deploy.promote") if (promoteProp != null) { promote = new Boolean(promoteProp) } } } /* [START endpoints-server] endpointsServer { // Endpoints Framework Plugin server-side configuration } [END endpoints-server] */ group = 'com.example.helloendpoints' // Generated output GroupId version = '1' // Version in generated output sourceCompatibility = 1.7 // App Engine Standard uses Java 7 targetCompatibility = 1.7 // App Engine Standard uses Java 7
After replacing the Gradle Build Script you will receive an error pointing out that the Android module cannot find the corresponding build configuration.
Error:Project :app declares a dependency from configuration 'compile' to configuration 'android-endpoints' which is not declared in the descriptor for project :backend.
In order to fix that, go into the “app” Gradle build script and adjust the following lines under “dependencies”.
# build.gradle (app) compile project(path: ':backend', configuration: 'android-endpoints')
change it to
# build.gradle (app) compile project(path: ':backend', configuration: 'android-backend')
Let Gradle sync. Now a new issue will arise, throwing following exception:
Error:Execution failed for task ':backend:endpointsDiscoveryDocs'. > com.google.common.reflect.TypeToken.isSubtypeOf(Ljava/lang/reflect/Type;)Z
In order to fix the issue, open the Gradle build file of the module “CloudApp” and place the first dependency lines of the backend Gradle inside it in order for it to look like the code below.
// Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { repositories { jcenter() } dependencies { classpath 'com.android.tools.build:gradle:2.3.1' classpath 'com.google.cloud.tools:appengine-gradle-plugin:1.3.0' // Endpoints Frameworks Gradle plugin classpath 'com.google.cloud.tools:endpoints-framework-gradle-plugin:1.0.0-beta9' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } } allprojects { repositories { jcenter() } } task clean(type: Delete) { delete rootProject.buildDir }
After Running a clean via Build > Clean Project and hitting the build button (hammer symbol) there will be yet again an Error.
Error running backend: Cannot start process, the working directory '/home/mdc/aw/CloudApp/backend/build/exploded-app' does not exist
It seems the WAR dir is not properly configured. Make sure to be in the “Project Files” perspective and navigate into the backend.iml File.
<option name="WAR_DIR" value="$MODULE_DIR$/build/exploded-app" />
The above line needs to be changed into
<option name="WAR_DIR" value="$MODULE_DIR$/build/exploded-backend" />
Again, hit Clean and Build. Afterwards hit the Run button just on the right of it. Possibly if you have a service running already on port 8080, you will need to adjust the settings of the development server. Following error will be shown if you cannot start the server on port 8080:
Could not open the requested socket: Address already in use Try overriding --address and/or --port.
In order to change the port we need to adjust the Android Studio project settings for the backend project.
Therefore, change into the “Project Files” view and navigate to backend > backend > backend.iml.
Find the HTTP_PORT parameter and change it accordingly. Also if you later want to access the backend from your local network, change the http address from “localhost” to “0.0.0.0”.
<option name="HTTP_ADDRESS" value="localhost" /> <option name="HTTP_PORT" value="8080" />
<option name="HTTP_PORT" value="9292" /> <option name="HTTP_ADDRESS" value="0.0.0.0" />
Once more hit clean, then build and run.
In the “Run” tab you will notice a couple of exceptions.
WARNING: EXCEPTION java.lang.ClassNotFoundException: com.google.api.server.spi.SystemServiceServlet at java.net.URLClassLoader.findClass(URLClassLoader.java:381) at java.lang.ClassLoader.loadClass(ClassLoader.java:424) at com.google.appengine.tools.development.IsolatedAppClassLoader.loadClass(IsolatedAppClassLoader.java:198) at java.lang.ClassLoader.loadClass(ClassLoader.java:357) at org.mortbay.util.Loader.loadClass(Loader.java:91) at org.mortbay.util.Loader.loadClass(Loader.java:71) at org.mortbay.jetty.servlet.Holder.doStart(Holder.java:73) at org.mortbay.jetty.servlet.ServletHolder.doStart(ServletHolder.java:242) at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:50) at org.mortbay.jetty.servlet.ServletHandler.initialize(ServletHandler.java:685) at org.mortbay.jetty.servlet.Context.startContext(Context.java:140) at org.mortbay.jetty.webapp.WebAppContext.startContext(WebAppContext.java:1250) at org.mortbay.jetty.handler.ContextHandler.doStart(ContextHandler.java:517) at org.mortbay.jetty.webapp.WebAppContext.doStart(WebAppContext.java:467) at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:50) at org.mortbay.jetty.handler.HandlerWrapper.doStart(HandlerWrapper.java:130) at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:50) at org.mortbay.jetty.handler.HandlerWrapper.doStart(HandlerWrapper.java:130) at org.mortbay.jetty.Server.doStart(Server.java:224) at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:50) at com.google.appengine.tools.development.JettyContainerService.startContainer(JettyContainerService.java:271) at com.google.appengine.tools.development.AbstractContainerService.startup(AbstractContainerService.java:284) at com.google.appengine.tools.development.AutomaticInstanceHolder.startUp(AutomaticInstanceHolder.java:26) at com.google.appengine.tools.development.AbstractModule.startup(AbstractModule.java:87) at com.google.appengine.tools.development.Modules.startup(Modules.java:105) at com.google.appengine.tools.development.DevAppServerImpl.doStart(DevAppServerImpl.java:262) at com.google.appengine.tools.development.DevAppServerImpl.access$000(DevAppServerImpl.java:45) at com.google.appengine.tools.development.DevAppServerImpl$1.run(DevAppServerImpl.java:217) at com.google.appengine.tools.development.DevAppServerImpl$1.run(DevAppServerImpl.java:215) at java.security.AccessController.doPrivileged(Native Method) at com.google.appengine.tools.development.DevAppServerImpl.start(DevAppServerImpl.java:215) at com.google.appengine.tools.development.DevAppServerMain$StartAction.apply(DevAppServerMain.java:277) at com.google.appengine.tools.util.Parser$ParseResult.applyArgs(Parser.java:48) at com.google.appengine.tools.development.DevAppServerMain.run(DevAppServerMain.java:225) at com.google.appengine.tools.development.DevAppServerMain.main(DevAppServerMain.java:216) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147) May 13, 2017 4:29:48 PM com.google.appengine.tools.development.ApiProxyLocalImpl log SEVERE: javax.servlet.ServletContext log: unavailable javax.servlet.UnavailableException: com.google.api.server.spi.SystemServiceServlet at org.mortbay.jetty.servlet.Holder.doStart(Holder.java:79) at org.mortbay.jetty.servlet.ServletHolder.doStart(ServletHolder.java:242) at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:50) at org.mortbay.jetty.servlet.ServletHandler.initialize(ServletHandler.java:685) at org.mortbay.jetty.servlet.Context.startContext(Context.java:140) at org.mortbay.jetty.webapp.WebAppContext.startContext(WebAppContext.java:1250) at org.mortbay.jetty.handler.ContextHandler.doStart(ContextHandler.java:517) at org.mortbay.jetty.webapp.WebAppContext.doStart(WebAppContext.java:467) at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:50) at org.mortbay.jetty.handler.HandlerWrapper.doStart(HandlerWrapper.java:130) at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:50) at org.mortbay.jetty.handler.HandlerWrapper.doStart(HandlerWrapper.java:130) at org.mortbay.jetty.Server.doStart(Server.java:224) at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:50) at com.google.appengine.tools.development.JettyContainerService.startContainer(JettyContainerService.java:271) at com.google.appengine.tools.development.AbstractContainerService.startup(AbstractContainerService.java:284) at com.google.appengine.tools.development.AutomaticInstanceHolder.startUp(AutomaticInstanceHolder.java:26) at com.google.appengine.tools.development.AbstractModule.startup(AbstractModule.java:87) at com.google.appengine.tools.development.Modules.startup(Modules.java:105) at com.google.appengine.tools.development.DevAppServerImpl.doStart(DevAppServerImpl.java:262) at com.google.appengine.tools.development.DevAppServerImpl.access$000(DevAppServerImpl.java:45) at com.google.appengine.tools.development.DevAppServerImpl$1.run(DevAppServerImpl.java:217) at com.google.appengine.tools.development.DevAppServerImpl$1.run(DevAppServerImpl.java:215) at java.security.AccessController.doPrivileged(Native Method) at com.google.appengine.tools.development.DevAppServerImpl.start(DevAppServerImpl.java:215) at com.google.appengine.tools.development.DevAppServerMain$StartAction.apply(DevAppServerMain.java:277) at com.google.appengine.tools.util.Parser$ParseResult.applyArgs(Parser.java:48) at com.google.appengine.tools.development.DevAppServerMain.run(DevAppServerMain.java:225) at com.google.appengine.tools.development.DevAppServerMain.main(DevAppServerMain.java:216) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147) May 13, 2017 4:29:48 PM com.google.apphosting.utils.jetty.JettyLogger warn WARNING: failed SystemServiceServlet: java.lang.NullPointerException May 13, 2017 4:29:48 PM com.google.apphosting.utils.jetty.JettyLogger warn WARNING: Failed startup of context com.google.appengine.tools.development.DevAppEngineWebAppContext@2e3967ea{/,/home/mdc/aw/CloudApp/backend/build/exploded-backend} java.lang.NullPointerException at java.lang.Class.isAssignableFrom(Native Method) at org.mortbay.jetty.servlet.ServletHolder.doStart(ServletHolder.java:256) at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:50) at org.mortbay.jetty.servlet.ServletHandler.initialize(ServletHandler.java:685) at org.mortbay.jetty.servlet.Context.startContext(Context.java:140) at org.mortbay.jetty.webapp.WebAppContext.startContext(WebAppContext.java:1250) at org.mortbay.jetty.handler.ContextHandler.doStart(ContextHandler.java:517) at org.mortbay.jetty.webapp.WebAppContext.doStart(WebAppContext.java:467) at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:50) at org.mortbay.jetty.handler.HandlerWrapper.doStart(HandlerWrapper.java:130) at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:50) at org.mortbay.jetty.handler.HandlerWrapper.doStart(HandlerWrapper.java:130) at org.mortbay.jetty.Server.doStart(Server.java:224) at org.mortbay.component.AbstractLifeCycle.start(AbstractLifeCycle.java:50) at com.google.appengine.tools.development.JettyContainerService.startContainer(JettyContainerService.java:271) at com.google.appengine.tools.development.AbstractContainerService.startup(AbstractContainerService.java:284) at com.google.appengine.tools.development.AutomaticInstanceHolder.startUp(AutomaticInstanceHolder.java:26) at com.google.appengine.tools.development.AbstractModule.startup(AbstractModule.java:87) at com.google.appengine.tools.development.Modules.startup(Modules.java:105) at com.google.appengine.tools.development.DevAppServerImpl.doStart(DevAppServerImpl.java:262) at com.google.appengine.tools.development.DevAppServerImpl.access$000(DevAppServerImpl.java:45) at com.google.appengine.tools.development.DevAppServerImpl$1.run(DevAppServerImpl.java:217) at com.google.appengine.tools.development.DevAppServerImpl$1.run(DevAppServerImpl.java:215) at java.security.AccessController.doPrivileged(Native Method) at com.google.appengine.tools.development.DevAppServerImpl.start(DevAppServerImpl.java:215) at com.google.appengine.tools.development.DevAppServerMain$StartAction.apply(DevAppServerMain.java:277) at com.google.appengine.tools.util.Parser$ParseResult.applyArgs(Parser.java:48) at com.google.appengine.tools.development.DevAppServerMain.run(DevAppServerMain.java:225) at com.google.appengine.tools.development.DevAppServerMain.main(DevAppServerMain.java:216) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
This is due to the new classes used of the servlet. Navigate into the webapp/WEB-INF/web.xml File and replace the contents as follows:
<?xml version="1.0" encoding="utf-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"> <servlet> <servlet-name>EndpointsServlet</servlet-name> <servlet-class>com.google.api.server.spi.EndpointsServlet</servlet-class> <init-param> <param-name>services</param-name> <param-value>ch.nexuscomputing.cloudapp.backend.MyEndpoint</param-value> </init-param> <init-param> <param-name>restricted</param-name> <param-value>false</param-value> </init-param> </servlet> <servlet-mapping> <servlet-name>EndpointsServlet</servlet-name> <url-pattern>/_ah/api/*</url-pattern> </servlet-mapping> </web-app>
Note that the classname “ch.nexuscomputing.cloudapp.backend.MyEndpoint” needs to match your namespace in your backend module.
Hit Clean Project, Rebuild the backend and hit run once more.
Now if all goes well you can navigate in your browser to http://localhost:9292/ and see the web application running.
One more pro tip for debugging: don’t right away start coding the Android part, but try to access the web application from your android device’s browser via http://YOURIP:9292/, this will already rule out connection issues within your LAN.
This will set you up for starting to code your Android application and make full use of the “Cloud Endpoints Framework v2.0” features.
If you liked this tutorial make sure to give it a +1 and share it among your friends, we greatly appreciate all your support! Of course if you have questions or if a step in the guide is not working out for you, please let us know about it so we can keep the article up to date.
Thank you for the tutorial! This helped me get started! It was exactly what I needed!
I tried to add authentication to the cloud endpoints project as shown here: https://cloud.google.com/endpoints/docs/authenticating-users-frameworks
When running the server I always get an error though: “Failed to fetch service config (status code 403)”
Do you have any idea how to fix this? thanks in advance!
Hi João!
Glad the tutorial helped you to get started!
Actually my plan was to continue and get into authentication with a next post, however I too came to realize it is even more complicated and convoluted to get a working hello world done than the enpoints framework.
I too am struggling with errors such as that the service cannot be found. As far as I understand, the web application will check the google cloud endpoint for the openapi.json.
It can be generated running gradle:
and then deployed
also I had to change my config files at several locations, notably
seems to be mismatched with the url we get from appengine is more like “${YOUR-PROJECT-ID}.appspot.com”.
Further down, when adding the “management” dependencies, the web application complains that the env variable
is not set, so I had to add it to my ~/.profile which is really dumb, imagine you have multiple projects, do you need now to log into the environment every time again to change the env var? I did not find a solution how to pass environment variables to the web server, I did however find a couple of issues on github where people complain about the same thing.
Going through the config and replacing the occurrence and adding the env var finally got me to run the server again, but you guess it, everything works *but* the endpoints that require authentication.
I am pretty sure it is due to the fact that the web application does not know about the firebase project ID or reverse, however after losing a lot of time dealing with these rather dumb issues, the entire premise of having a web application that has a straight forward integration of authentication falls completely apart.
Just imagine if Google or Firebase change anything with their configuration or way to set up the project, this would most probably entail a lot more tedious config work.
However if you manage to get a hello world running, please let me know, I am pretty sure I was 90% there but at this point the needless complexity kind of beats the purpose of choosing appengine over something much more flexible like spring.io with an auth service.
Dear Regards
Manuel
Yeah, that’s what I thought too! I have multiple projects, so how will this ever work for me if I have to set the environment variable to different values for each one?
What did you end up setting your ENDPOINTS_SERVICE_NAME variable to though? that seems to be my last missing piece.
I’ve uploaded the openapi.json file and all, but it seems that the service can never be found and so I always get the 403 error mentioned above. I *think* it’s because of that path, but I don’t know what the “echo-api” part’s equivalent is in my project!
Thanks again for your help, this should be the tutorial on Google’s page, not the one that exists there now 😛
Absolutely, I believe if Google was serious there anyways should be a tutorial project including all the Android, Endpoints and Auth parts, as it looks now it is just a fragmented heap of snippets that are supposed to get you through the configuration.
Currently I have the env var set to
On the other hand, when running the server now I am getting
Possibly because I messed around again with some other project or config files. I do remember though being able to connect to the endpoints not requiring auth and getting a response with a 200 code. On auth endpoints I received an error (possibly also a 403, but I did not write it down at the time).
I know this is not much to go on, but maybe it will work on your side 😀
Thank you! 🙂
But what does the “hello” there correspond to?
For example, in the google example they set it to “echo-api” but that is not the name of anything else in the project. What is that part supposed to represent exactly?
do you know?
Thanks again!
Thanks! I had an Android app with Google App Engine backend I had not touched in two years. After getting back to it, I have had the hardest time adapting to all the changes. Your post has helped me tremendously.
Thank you.
Hi Manuel,
I was having the same error as you described
Error:Execution failed for task ‘:backend:endpointsDiscoveryDocs’.
> com.google.common.reflect.TypeToken.isSubtypeOf(Ljava/lang/reflect/Type;)Z
This is for any project, for the one that I`m working on and even for the sample app from google site. I tried your suggestion to add dependencies to upper-level build.gradle and after Clean, I`m not getting the error, but if I click the “Run” button, I`m still getting the same error with isSubtypeOf.
Do you have any other suggestions about how to fix this?
Thanks,
Andrii.