Smartphones

Calling RESTful services from your Android app

Mobile developer William J. Francis demonstrates how easy it is to consume a RESTful service from your Android device.

RESTful services are well suited for providing content to small footprint devices like smartphones and tablets. In fact, if you've interacted with any cloud-based APIs in the last couple of years, there is a strong chance that API was exposed via a REST interface. There are a number of documents available on the web explaining what constitutes a REST service and how to implement one. There are also a number of write-ups explaining how to consume REST services from a client. Yet a common request I see in Android forums is for examples of Java-based Android specific REST consumers.

Recently my son and I released an update for our game, which included an online leaderboard. The server code is pretty simple -- it keeps track of the five best scores on each of the 40 levels. Because of its light-weight and relative ease of implementation, I exposed the leaderboard to my app (and anyone else who is interested) via a RESTful HTTP GET call. For instance to see the top five players on level #1, just pop this URL into any browser: http://www.cheesejedi.com/rest_services/get_big_cheese?level=1.

Figure A

Calling a REST endpoint from within an Android application requires pushing an HTTP request to the background thread and then parsing the results on the UI thread. It's not very difficult once you see it laid out in front of you, but getting started can sometimes be a bit of a hurdle.

This tutorial includes a pared down version of the code actually used in my production application. Hopefully it will prove useful to you in getting started with your own Android REST consumers. You can follow along with the step-by-step tutorial, or download the entire project to import into Eclipse.

1. Create a new Android project in Eclipse. Target SDK 1.6 or greater, and be sure to rename the startup activity to Main.java.

2. In your /res/layout folder, create a main.xml resource that will define a button and an edit box.

main.xml

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="fill_parent"

android:layout_height="fill_parent"

android:orientation="vertical" >

<TextView

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:text="Http GET Demo"/>

<Button

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:layout_gravity="center"

android:text="GET" android:id="@+id/my_button"/> <EditText android:layout_margin="20dip"

android:layout_width="fill_parent"

android:layout_height="wrap_content"

android:minLines="15"

android:maxLines="15"

android:textSize="12sp"

android:editable="false"

android:id="@+id/my_edit"/> </LinearLayout>
3. Because our application will be making HTTP calls, we will have to declare the uses internet permission in the manifest.
AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

package="com.authorwjf.http_get"

android:versionCode="1"

android:versionName="1.0" >

<uses-sdk android:minSdkVersion="4" />

<uses-permission android:name="android.permission.INTERNET"/>

<application

android:icon="@drawable/ic_launcher"

android:label="@string/app_name" >

<activity

android:name=".Main"

android:label="@string/app_name" >

<intent-filter>

<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />

</intent-filter>

</activity>

</application>

</manifest>
4. The Main.java file in our /src directory contains the standard on create override and implements an on click listener for the button.
Main.java
package com.authorwjf.http_get;
import java.io.IOException;
import java.io.InputStream;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HttpContext;
import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
public class Main extends Activity implements OnClickListener {

@Override

public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);

setContentView(R.layout.main);

findViewById(R.id.my_button).setOnClickListener(this);

}

@Override

public void onClick(View arg0) {

Button b = (Button)findViewById(R.id.my_button);

b.setClickable(false); new LongRunningGetIO().execute();

}

}

5. Our Main.java file references something called LongRunningGetIO; this is something we need to implement as well, and for the sake of our example, we will do so as an inner class inside our Main. The inner class extends AsyncTask. If you aren't familiar with how it works, you can get up to speed by reading my post on Long Running I/O.

Main.java
private class LongRunningGetIO extends AsyncTask <Void, Void, String> {
protected String getASCIIContentFromEntity(HttpEntity entity) throws IllegalStateException, IOException {

InputStream in = entity.getContent();

StringBuffer out = new StringBuffer(); int n = 1; while (n>0) { byte[] b = new byte[4096];

n =  in.read(b);

if (n>0) out.append(new String(b, 0, n));

}

return out.toString();

}

@Override

protected String doInBackground(Void... params) { HttpClient httpClient = new DefaultHttpClient(); HttpContext localContext = new BasicHttpContext(); HttpGet httpGet = new HttpGet("http://www.cheesejedi.com/rest_services/get_big_cheese.php?puzzle=1"); String text = null; try {

HttpResponse response = httpClient.execute(httpGet, localContext);

HttpEntity entity = response.getEntity();

text = getASCIIContentFromEntity(entity);

} catch (Exception e) { return e.getLocalizedMessage();

}

return text;

}

protected void onPostExecute(String results) { if (results!=null) {

EditText et = (EditText)findViewById(R.id.my_edit);

et.setText(results);

}

Button b = (Button)findViewById(R.id.my_button);

b.setClickable(true);

}

}

The demo should be ready for you to try. Upload the APK to an emulator and tap the button. Providing you have an Internet connection and the server isn't overloaded with requests, you should get something back. The demo doesn't do any parsing on the result string, but it should be clear how easy it would be to encompass the results in a JSON array and display them in a meaningful manner.

Figure B

Congratulations -- you've just consumed a RESTful service from your Android device!

About

William J Francis began programming computers at age eleven. Specializing in embedded and mobile platforms, he has more than 20 years of professional software engineering under his belt, including a four year stint in the US Army's Military Intellige...

6 comments
jacobcat-380df
jacobcat-380df

Hi, I've been reading in other places and have bumped over the use of StringBuilder instead of StringBuffer for the getASCIIContentFromEntity() method. It says that the StringBuilder is faster than StringBuffer because it's unsynchronized. Since we're on an AsyncTask, do you think it would actually be better to use StringBuilder for this code?


Hoping for your insights.

imenot
imenot

Thanks ! I wonder if i want to convert HttpEntity to a List Object. May you show me how to do that ? Thanks

Jimitdamania
Jimitdamania

Hello William, 


Just want to know how to upload the data to same server with Android app.


Thanks in advance!!

moums
moums

Such a great tutorial. Thanks man

autefrum
autefrum

When I tried to follow this tutorial, I found that in onPostExecute, the section Button b = (Button)findViewById(R.id.my_button); b.setClickable(true); resulted in an exception: "Only the original thread that created a view hierarchy can touch its views." When I googled this error message, it seems to be a documented limitation, but perhaps that is only a recently introcuced one?