Unless you are writing a Hello World Android application, chances are your application would need to connect to the outside world to fetch some data, such as live currency exchange rates, weather information, records from databases, etc. One of the easiest ways for your application to connect to the outside world is to use web services.
For the past few years, XML web services have dominated the arena for web services, as XML was touted as the ubiquitous medium for data exchange. However, using XML as the medium for your data payload suffers from the following problems:
1. XML representation is inherently heavy. The use of opening and closing tags add a lot of unnecessary weight to the payload. In the world of mobile applications, shaving a few bytes off the payload will dramatically improve the performance of applications, not to mention the reduction of data transferred over the expensive 3G and LTE wireless networks. This translates into cost savings for both application developers (who need to subscribe to expensive networks for their web servers) and users (who has limited amount of bandwidth to use per subscription).
2. XML representation is difficult to parse. While on the desktop, the DOM (Document Object Model) and SAX (Simple APIs for XML) are the two commonly used method for parsing XML Documents; on the mobile platform using DOM and SAX are very expensive, both computationally and in terms of memory requirements.
In recent years, another data interchange format has been gaining in popularity – JSON, or JavaScript Object Notation. Like XML, JSON is a text-based open standard for representing data, and it uses characters such as brackets “[{]}”, colon “:” and comma “,”, to represent data. Data are represented using simple key/value pairs, and more complex data are represented as associative arrays.
In this article, I will walk you through on how to consume a JSON service in your Android application.
Creating the Project
For this project, I will be using Eclipse with the Android 4.1 SDK. To start off, create an Android application project and name it as shown in Figure 1.
In the res/layout folder, add in the following statements to the activity_main.xml file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_alignParentLeft="true" android:layout_alignParentRight="true" android:layout_alignParentTop="true" android:orientation="vertical" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Latitude" /> <EditText android:id="@+id/txtLat" android:layout_width="320dp" android:layout_height="wrap_content" android:ems="10" android:inputType="numberDecimal" android:text="37.77493" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Longitude" /> <EditText android:id="@+id/txtLong" android:layout_width="match_parent" android:layout_height="wrap_content" android:ems="10" android:inputType="numberDecimal" android:text="-122.419416" /> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Get Weather" android:onClick="btnGetWeather" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Postal Code" /> <EditText android:id="@+id/txtPostalCode" android:layout_width="320dp" android:layout_height="wrap_content" android:ems="10" android:inputType="number" android:text="89118" /> <Button android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Get Places" android:onClick="btnGetPlaces" /> </LinearLayout> |
This will create the UI as shown in Figure 2. As you can see, there are actually two parts to the UI:
1. The first part allows the user to enter a pair of latitude and longitude information. Click on the Get Weather button and you will be able to get information about the weather information for that particular location.
2. The second part allows the user to enter a postal code. Clicking the Get Places button will search for all the towns and cities in the world with this postal code.
In both cases, the data would be retrieved from web services hosted by GeoNames Web Services (http://www.geonames.org/export/web-services.html).
Creating the Helper Method
To connect to a web service, your application needs to first of all connect to the server using HTTP. You need to also determine if you will be using HTTP GET or HTTP POST. Once that is determined, you will fetch the data from the server and get ready for the next step, which is parsing. For this article, the GeoNames Web Services that you will be using uses HTTP GET, and hence, you will first of all create the helper method readJSONFeed() in the MainActivity.java file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
package net.learn2develop.json; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.StatusLine; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.DefaultHttpClient; import android.app.Activity; import android.os.Bundle; import android.util.Log; public class MainActivity extends Activity { public String readJSONFeed(String URL) { StringBuilder stringBuilder = new StringBuilder(); HttpClient httpClient = new DefaultHttpClient(); HttpGet httpGet = new HttpGet(URL); try { HttpResponse response = httpClient.execute(httpGet); StatusLine statusLine = response.getStatusLine(); int statusCode = statusLine.getStatusCode(); if (statusCode == 200) { HttpEntity entity = response.getEntity(); InputStream inputStream = entity.getContent(); BufferedReader reader = new BufferedReader( new InputStreamReader(inputStream)); String line; while ((line = reader.readLine()) != null) { stringBuilder.append(line); } inputStream.close(); } else { Log.d("JSON", "Failed to download file"); } } catch (Exception e) { Log.d("readJSONFeed", e.getLocalizedMessage()); } return stringBuilder.toString(); } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } } |
The readJSONFeed() method takes in a string representing the URL of the web service and then connects to the server using HTTP GET. You make use of the HttpClient class to connect to the server, the HttpGet class to specify the URL of the server, and the HttpResponse class to get the connection status from the server. Once the connection is established successfully, you all use the BufferedReader and InputStreamReader classes to download the result (which is a JSON string in this example) from the server. The readJSONFeed() method then returns the JSON string.
As you need Internet access for this project to work, remember to add the INTERNET permission in the AndroidManifest.xml file:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="net.learn2develop.json" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="15" /> <uses-permission android:name="android.permission.INTERNET"/> <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name=".MainActivity" android:label="@string/title_activity_main" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest> |
Getting Weather Information
Now that you can connect to the server to download the JSON result, it is now time to connect to the GeoNames Web Services to get weather information. In particular, to get the weather information of a particular location, you will use the following URL (replace the <lat> and <lng> with the actual latitude and longitude):
http://ws.geonames.org/findNearByWeatherJSON?lat=<lat>&lng=<lng>
A sample result from the server looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
{ "weatherObservation": { "clouds":"scattered clouds", "weatherCondition":"n/a", "observation":"KCFV 090852Z AUTO 06005KT 10SM SCT090 SCT110 24/20 A3000 RMK AO2 SLP148 T02390200 53002", "windDirection":60, "ICAO":"KCFV", "seaLevelPressure":1014.8, "elevation":225, "countryCode":"US", "lng":-95.56666666666666, "temperature":"23.9", "dewPoint":"20", "windSpeed":"05", "humidity":78, "stationName":"Coffeyville, Coffeyville Municipal Airport", "datetime":"2012-07-09 08:52:00", "lat":37.083333333333336 } } |
If you observe, you have a primary key – weatherObservation. Its value is a collection of key/value pairs, such as clouds, weatherCondition, etc.
In order to use the readJSONFeed() method, you need to call it asynchronously as beginning with Android 3.0 you can no longer call network operations from within your UI thread (such as within an activity). The easiest way to do this is to wrap it using an AsyncTask class. Hence, add the following statements to the MainActivity class:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
package net.learn2develop.json; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.StatusLine; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.DefaultHttpClient; import org.json.JSONObject; import android.app.Activity; import android.os.AsyncTask; import android.os.Bundle; import android.util.Log; import android.widget.Toast; public class MainActivity extends Activity { public String readJSONFeed(String URL) { StringBuilder stringBuilder = new StringBuilder(); HttpClient httpClient = new DefaultHttpClient(); HttpGet httpGet = new HttpGet(URL); try { HttpResponse response = httpClient.execute(httpGet); StatusLine statusLine = response.getStatusLine(); int statusCode = statusLine.getStatusCode(); if (statusCode == 200) { HttpEntity entity = response.getEntity(); InputStream inputStream = entity.getContent(); BufferedReader reader = new BufferedReader( new InputStreamReader(inputStream)); String line; while ((line = reader.readLine()) != null) { stringBuilder.append(line); } inputStream.close(); } else { Log.d("JSON", "Failed to download file"); } } catch (Exception e) { Log.d("readJSONFeed", e.getLocalizedMessage()); } return stringBuilder.toString(); } private class ReadWeatherJSONFeedTask extends AsyncTask <String, Void, String> { protected String doInBackground(String... urls) { return readJSONFeed(urls[0]); } protected void onPostExecute(String result) { try { JSONObject jsonObject = new JSONObject(result); JSONObject weatherObservationItems = new JSONObject(jsonObject.getString("weatherObservation")); Toast.makeText(getBaseContext(), weatherObservationItems.getString("clouds") + " - " + weatherObservationItems.getString("stationName"), Toast.LENGTH_SHORT).show(); } catch (Exception e) { Log.d("ReadWeatherJSONFeedTask", e.getLocalizedMessage()); } } } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } } |
In the ReadWeatherJSONFeedTask class, you have two methods:
1. The doInBackGround() method is executed asynchronously. Here, you call the readJSONFeed() method to get the weather information. When the result is obtained, it is returned to the onPostExecute() method.
2. The onPostExecute() method takes the JSON result and parses it.
a. First, the JSON string is passed as the argument to the constructor of the JSONObject class. This creates a new JSONObject object (jsonObject) with key/value mappings from the JSON string.
b. You then get the value of the weatherObservation key by using the getString() method of jsonObject. The values are then passed as the constructor of the JSONObject class, creating another JSONObject – weatherObservationItems.
c. Finally, you extract the value of the clouds and stationName keys by calling the getString() method of weatherObservationItems.
To wire up the event handler for the Get Weather button in your UI, add the btnGetWeather() method to MainActivity.java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
package net.learn2develop.json; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.StatusLine; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.DefaultHttpClient; import org.json.JSONObject; import android.app.Activity; import android.os.AsyncTask; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.EditText; import android.widget.Toast; public class MainActivity extends Activity { public String readJSONFeed(String URL) { ... } private class ReadWeatherJSONFeedTask extends AsyncTask <String, Void, String> { ... } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } public void btnGetWeather(View view) { EditText txtLat = (EditText) findViewById(R.id.txtLat); EditText txtLong = (EditText) findViewById(R.id.txtLong); new ReadWeatherJSONFeedTask().execute( "http://ws.geonames.org/findNearByWeatherJSON?lat=" + txtLat.getEditableText().toString() + "&lng=" + txtLong.getText().toString()); } } |
You can now test the application on an Android emulator. Figure 3 shows the result.
Getting Places using Postal Code
Now that you have managed to consume the first JSON service, let’s take a look at the second example. This time, you will consume a service that returns a list of city names using a given postal code. You can get the result from the following URL (replace the <postal_code> with the actual postal code):
http://api.geonames.org/postalCodeSearchJSON?postalcode=<postal_code>&maxRows=10&username=demo
A sample result from the server looks like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
{ "postalCodes": [ { "adminCode3":"3203", "adminName2":"Wahlkreis St. Gallen", "adminName3":"St. Gallen", "adminCode2":"1721", "adminCode1":"SG", "postalCode":"9011", "countryCode":"CH", "lng":9.399858534040646, "placeName":"St. Gallen", "lat":47.414775328611945, "adminName1":"Kanton St. Gallen" }, { "adminCode1":"GS", "postalCode":"9011", "countryCode":"HU", "lng":17.781944437499998, "placeName":"Gyor", "lat":47.607638900000005, "adminName1":"Gyor-Moson-Sopron" }, { "adminName2":"Tromsø", "adminCode2":"1902", "adminCode1":"19", "postalCode":"9011", "countryCode":"NO", "lng":18.95508, "placeName":"Tromsø", "lat":69.6489, "adminName1":"Troms" }, { ... ... } ] } |
If you observe, you have a primary key – postalCodes. Its value is an array of objects, with each object containing a collection of key/value pairs, such as adminCode1, lat, lng, etc.
Like the previous example, you will add a ReadPlacesFeedTask class to connect to the server asynchronously:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
package net.learn2develop.json; import java.io.BufferedReader; import java.io.InputStream; import java.io.InputStreamReader; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.StatusLine; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.DefaultHttpClient; import org.json.JSONArray; import org.json.JSONObject; import android.app.Activity; import android.os.AsyncTask; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.EditText; import android.widget.Toast; public class MainActivity extends Activity { public String readJSONFeed(String URL) { ... } private class ReadWeatherJSONFeedTask extends AsyncTask <String, Void, String> { ... } private class ReadPlacesFeedTask extends AsyncTask <String, Void, String> { protected String doInBackground(String... urls) { return readJSONFeed(urls[0]); } protected void onPostExecute(String result) { try { JSONObject jsonObject = new JSONObject(result); JSONArray postalCodesItems = new JSONArray(jsonObject.getString("postalCodes")); //---print out the content of the json feed--- for (int i = 0; i < postalCodesItems.length(); i++) { JSONObject postalCodesItem = postalCodesItems.getJSONObject(i); Toast.makeText(getBaseContext(), postalCodesItem.getString("postalCode") + " - " + postalCodesItem.getString("placeName") + ", " + postalCodesItem.getString("countryCode"), Toast.LENGTH_SHORT).show(); } } catch (Exception e) { Log.d("ReadPlacesFeedTask", e.getLocalizedMessage()); } } } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } public void btnGetWeather(View view) { ... } } |
As in the previous example, the result is first converted into a JSONObject. The values of the postalCode key is then converted into a JSONArray object – postalCodesItems, which contains an array of JSONObject objects. You then iterate through the array and extracted each object and print out the values of the postalCode, placeName, and countryCode keys.
Finally, wire the event handler for the Get Places button with the btnGetPlaces() method in the MainActivity.java file:
1 2 3 4 5 6 7 8 9 10 11 |
public void btnGetWeather(View view) { ... } public void btnGetPlaces(View view) { EditText txtPostalCode = (EditText) findViewById(R.id.txtPostalCode); new ReadPlacesFeedTask().execute( "http://api.geonames.org/postalCodeSearchJSON?postalcode=" + txtPostalCode.getEditableText().toString() + "&maxRows=10&username=demo"); } |
Figure 4 shows the sample result.
Summary
In this article, you have seen how to consume a JSON service from within your Android application. You have seen two examples on how to parse a JSON string using the JSONObject and JSONArray classes available in the org.json package.