Use Android Studio to stub web API; simple JWT login as an example

Ali Muzaffar
BCG Digital Ventures Engineering
10 min readMay 8, 2016

--

When documenting API or stubbing end-points for web API, Apiary has been the go-to solution for a while. However, there are times when you need to returns results based on users input and then, we very quickly hit up against the limitations of Apiary.

While most developers are comfortable with using Tomcat, Apache and Nginx with a back-end language, there is some overhead to downloading the new tools and setting up a new environment. Luckily for Android developers, there is a better way.

It’s pretty easy to dismiss Android Studio as nothing more than an Android Development environment. Lets face it, as far as development environments for Android go, it’s great! However, remember that it built on top of Intellij IDEA which is a very powerful development environment and that Android Studio is also a development platform for the Google Cloud Platform. I touched upon this power of Android Studio once before when I talked about How to create a mobile game with backend API’s in 5 minutes.

Why would you stub or build your own web API?

Off the top of my head, I can think of 2 main reasons to stub or build your own web API end-points.

  • Just using a static stubbed web API end-point is not enough.
    In my experience, mobile developers can move a lot faster on project that web API developers. The reason for this is often that web API developers have to wait for the infrastructure to be available or at least spend some time and effort setting it up.
  • You are tasked with creating the API as well as the app.
    I don’t think this one requires much explanation or justification. It can happen.

Building a simple JWT login web API to develop against

Setup the app

Start by creating a new Activity in your existing app, or by creating a new Android app with a LoginActivity. Effectively, when you see the screen below, select LoginActivity.

After the activity is added to your project, the next step for me is to clean out the code and get rid of a lot of stuff that I don’t need. The end result is that LoginActivity looks like this:

public class LoginActivity extends AppCompatActivity {
// UI references.
private AutoCompleteTextView mEmailView;
private EditText mPasswordView;
private View mProgressView;
private View mLoginFormView;
private Button mEmailSignInButton;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
// Set up the login form.
mEmailView = (AutoCompleteTextView)
findViewById(R.id.email);

mPasswordView = (EditText) findViewById(R.id.password);
mPasswordView.setOnEditorActionListener(
new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView textView,
int id, KeyEvent keyEvent) {
if (id == R.id.login || id == EditorInfo.IME_NULL) {
attemptLogin();
return true;
}
return false;
}
});

mEmailSignInButton = (Button)
findViewById(R.id.email_sign_in_button);
mEmailSignInButton.setOnClickListener(
new OnClickListener() {
@Override
public void onClick(View view) {
attemptLogin();
}
});

mLoginFormView = findViewById(R.id.login_form);
mProgressView = findViewById(R.id.login_progress);
}

private void attemptLogin() {
if (!mEmailSignInButton.isEnabled()) {
return;
}

// Reset errors.
mEmailView.setError(null);
mPasswordView.setError(null);

// Store values at the time of the login attempt.
String email = mEmailView.getText().toString();
String password = mPasswordView.getText().toString();

boolean cancel = false;
View focusView = null;

// Check for a valid password, if the user entered one.
if (!TextUtils.isEmpty(password) &&
!isPasswordValid(password)) {
mPasswordView.setError(
getString(R.string.error_invalid_password));
focusView = mPasswordView;
cancel = true;
}

// Check for a valid email address.
if (TextUtils.isEmpty(email)) {
mEmailView.setError(
getString(R.string.error_field_required));
focusView = mEmailView;
cancel = true;
} else if (!isEmailValid(email)) {
mEmailView.setError(
getString(R.string.error_invalid_email));
focusView = mEmailView;
cancel = true;
}

if (cancel) {
focusView.requestFocus();
} else {
showProgress(true);
}
}

private boolean isEmailValid(String email) {
return email.contains("@");
}

private boolean isPasswordValid(String password) {
return password.length() > 4;
}

@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
private void showProgress(final boolean show) {
// On Honeycomb MR2 we have the ViewPropertyAnimator APIs, which allow
// for very easy animations. If available, use these APIs to fade-in
// the progress spinner.
if (Build.VERSION.SDK_INT >=
Build.VERSION_CODES.HONEYCOMB_MR2) {
int shortAnimTime = getResources().getInteger(
android.R.integer.config_shortAnimTime);

mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
mLoginFormView
.animate()
.setDuration(shortAnimTime)
.alpha(show ? 0 : 1)
.setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
}
});

mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
mProgressView.animate().setDuration(shortAnimTime).alpha(
show ? 1 : 0).setListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
}
});
} else {
// The ViewPropertyAnimator APIs are not available, so simply show
// and hide the relevant UI components.
mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
}
}
}

Set-up the Web API

To set-up the web API, go to File > New > New Module.

When you see the screen above, select “Google Cloud Module” and click Next.

On the next screen, fill in all the details as you like, or you can leave the default options. The only thing you have to make sure to select is the “Module type”

The “Module type” contains three values, you need to pick one that suits your needs best.

You can find more information on each of the options at the links above. I've chosen to use “App Engine Java Servlet Module” because the code for the Servlets is very succinct and makes it easier to copy paste.

When you finish, the Servlet generated will look like this:

public class MyServlet extends HttpServlet {
@Override
public void doGet(HttpServletRequest req, HttpServletResponse resp)
throws IOException {
resp.setContentType("text/plain");
resp.getWriter().println("Please use the form to POST to this url");
}

@Override
public void doPost(HttpServletRequest req, HttpServletResponse resp)
throws IOException {
String name = req.getParameter("name");
resp.setContentType("text/plain");
if(name == null) {
resp.getWriter().println("Please enter a name");
}
resp.getWriter().println("Hello " + name);
}
}

If you look for the file web.xml, you’ll see that MyServlet is mapped to /hello. You can change this if you like. For my needs, I've changed it to /login.

<?xml version="1.0" encoding="utf-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee" version="2.5">
<servlet>
<servlet-name>MyServlet</servlet-name>
<servlet-class>com.example.AliMuzaffar.myapplication.backend.MyServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>MyServlet</servlet-name>
<url-pattern>/login</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
</web-app>

You also want to find index.html and update the following line (this step is optional, fixing this means we can use the index page to confirm our api is running):


<form action="/hello" method="POST">
<!-- to -->
<form action="/login" method="POST">

Now select “backend” from the run menu and hit the run button.

You should see output as shown below in red. Don’t panic, nothing is wrong, the console is just outputting the log in red. I don’t know why.

May 08, 2016 12:16:59 PM com.google.apphosting.utils.jetty.JettyLogger info
INFO: jetty-6.1.x
May 08, 2016 12:16:59 PM com.google.apphosting.utils.jetty.JettyLogger info
INFO: Started SelectChannelConnector@localhost:8080
May 08, 2016 12:16:59 PM com.google.appengine.tools.development.AbstractModule startup
INFO: Module instance default is running at http://localhost:8080/
May 08, 2016 12:16:59 PM com.google.appengine.tools.development.AbstractModule startup
INFO: The admin console is running at http://localhost:8080/_ah/admin
May 08, 2016 12:16:59 PM com.google.appengine.tools.development.DevAppServerImpl doStart
INFO: Dev App Server is now running

If you now go to http://localhost:8080, you should see index.html.

Now, lets modify our Servlet so that it takes a username and password and returns a JWT token.

Setting up backend for JWT

First find build.gradle for the backend module and add a dependency on the JWT library we will use. You can use any library you want, just head over to http://jwt.io and select one.

I’ve chosen to use JJWT because it has a very complete implementation of JWT standards and I like it’s syntax more than Jose4J. However, different libraries are easier or harder to use and you should pick one that suits you best.

Start by adding JJWT to your back-end module.

compile 'io.jsonwebtoken:jjwt:0.6.0'

The modify your Servlet to generate a JWT token when a username and password is provided. Do note, that we don’t really care if the username and password are correct at this point.

public class MyServlet extends HttpServlet {
private final String VALID_USER = "user@domain.com:password";

@Override
public void doGet(HttpServletRequest req, HttpServletResponse resp)
throws IOException {
resp.setContentType("text/plain");
resp.sendError(HttpServletResponse.SC_FORBIDDEN, "No GET access.");
}

@Override
public void doPost(HttpServletRequest req, HttpServletResponse resp)
throws IOException {
String name = req.getParameter("username");
String pass = req.getParameter("password");
resp.setContentType("application/json");

if (name == null) {
resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
String json = String.format("{\"error\": \"%s\"}", "Username is required");
resp.getWriter().print(json);
return;
}
if (pass == null) {
resp.setStatus(HttpServletResponse.SC_BAD_REQUEST);
String json = String.format("{\"error\": \"%s\"}", "Password is required");
resp.getWriter().print(json);
return;
}

if ((name + ":" + pass).equals(VALID_USER)) {
String token = createJWT("0", "My Company", name, 10000);
String json = String.format("{\"token\": \"%s\"}", token);
resp.getWriter().print(json);
} else {
resp.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
String json = String.format("{\"error\": \"%s\"}", "Username or Password is invalid");
resp.getWriter().print(json);
}
}

private String createJWT(String id, String issuer, String audience, long ttlMillis) {

//The JWT signature algorithm we will be using to sign the token
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);

//We will sign our JWT with our ApiKey secret
byte[] apiKeySecretBytes = DatatypeConverter.parseBase64Binary("MySecret");

//Let's set the JWT Claims
JwtBuilder builder = Jwts.builder().setId(id)
.setIssuedAt(now)
.setIssuer(issuer)
.setAudience(audience)
.signWith(SignatureAlgorithm.HS256, apiKeySecretBytes);

//if it has been specified, let's add the expiration
if (ttlMillis >= 0) {
long expMillis = nowMillis + ttlMillis;
Date exp = new Date(expMillis);
builder.setExpiration(exp);
}

//Builds the JWT and serializes it to a compact, URL-safe string
return builder.compact();
}
}

You can now use Postman to validate whether the API end-point works, you will get a token back if your username and passwords are correct or else, you’ll get an error.

Invalid input
Valid input

You can head over to http://jwt.io and test your token on that site.

Already we can see a benefit over using a static stub. We can enforce our contracts for what should happen if the input is invalid and we can test/develop against it.

Update your app to handle the token

I’m going to use koush’s Ion library to handle networking. IMHO it’s the fastest way to implement networking and image handling in any app. Simply add the dependency (1 line) and start coding.

Add the dependency in your apps build.gradle file.

compile 'com.koushikdutta.ion:ion:2.1.7'

Then in your LoginActivity, in attemptLogin(), post the username and password to our API end-point. Just remember that on your phone or tablet, you can’t use localhost and should replace it with the TCP/IP address of your machine, or if you use the emulator, you can use 10.0.2.2; if you use Genymotion you can use 10.0.3.2.

We update the code in our LoginActivity.attemptLogin() method to make the login call.

showProgress(true);
Ion.with(this)
.load("POST", "http://10.0.3.2:8080/login")
.setBodyParameter("username", mEmailView.getText().toString())
.setBodyParameter("password", mPasswordView.getText().toString())
.asString()
.setCallback(new FutureCallback<String>() {
@Override
public void onCompleted(Exception e, String result) {
if (e != null) {
JSONObject o = silentStringToJson(result);
String error = o.optString("error");
String token = o.optString("token");
if (TextUtils.isEmpty(error)) {
//TODO: Save token
} else {
//Show error
Snackbar.make(mEmailView,
error,
Snackbar.LENGTH_SHORT)
.show();
}
} else {
Snackbar.make(mEmailView,
"ERROR",
Snackbar.LENGTH_SHORT)
.show();
}
showProgress(false);
}
});

We are now able to write code to not only handle a successful login, but also to handle error conditions.

Next steps

Javadoc

If you want to use this as your primary form of stubbing or building a web API, I would use the Javadoc standard to document your API.

Distributing your Web API

Anyone with Android Studio should be able to run your project without any grief. However, if you need to distribute your API to allow others to develop on their own computers, you can generate a war using a simple command:

> ./gradlew war

This should generate a war file under <your project>/<backend-folder-name>/build/libs

This war should work in a Tomcat or Jetty without any problems.

Stubbing a database

Some times, you need to be able to stub a database or even persist one for stubbing or API development purposes. My personal recommendation here would be to use HyperSQL Database (HSQLDB) (only in the development environment). While you can’t do anything too complex with HSQLDB, you can run it as an in-memory database or as a database back-end by filter persistence. This allows you to create a fresh database from scratch and load it with data that is reset every time you restart the server, or even persist the data on disk if you like.

Admittedly, if you are doing this, you’re no longer stubbing API fast. However, you are making a very powerful and useful stubbed endpoint. If the database isn't too complex, moving your stubs to a MySQL back-end would be rather fast and simple. There are some benefits to use HSQLDB on Android as well, hopefully I’ll cover this in the future. Needless to say that if your back-end is ultimately going to be a NoSQL DB, this is not the best option.

You can also use something like Firebase, however, you’ll need to be on-line to use Firebase so, off-line development is not possible. However, you get some other nice features like data being pushed when it changes. With Firebase, each developer can use their own account in development or share an account and developers can progammatically reset the state of the database or persist it. The downside of using Firebase is that the code doesn’t port too well, if you want to switch to a SQL db.

Finally

Please check out some of my articles on Medium. And, feel free to follow me on Google+, Twitter or LinkedIn.

--

--

Ali Muzaffar
BCG Digital Ventures Engineering

A software engineer, an Android, and a ray of hope for your darkest code. Residing in Sydney.