Accessing Weather Informations with GSON and Picasso

Dealing with network connections is a crucial point in Android development. Downloading and parsing data from original format is a common task for network-connected apps and generally consists in two steps. The first step is to connect the application to remote source data. This operation has to be performed on a separate thread to avoid any communication delay which may cause a weak user experience. The next step is to read data from source, typically by using Java Streams, and convert them from original format to Java objects. In this tutorial, you will learn how to download and parse data from OpenWeatherMap.org , an online service that stores informations about world-wide weather and offers them to developers as a web service in JSON, XML and HTML format.

The following example will show how to interact with this service taking advantage of two awesome Java libraries: GSON (for JSON parsing) and Picasso (for efficient image downloading).

From data to layout

To download data about current weather, you have to connect to OpenWeatherMap.org using the http address http://api.openweathermap.org/data/2.5/weather. When contacting the URL, it will be concatenated with a querystring containing a parameter that indicates the name of the city you are interested in.
For example, if I would like to know the current weather in London, I would send a GET request to the address http://api.openweathermap.org/data/2.5/weather?q=London.

In the example, we will download data about some important cities. We’ll perform a different HTTP request for every city and all the requests will be formatted as said before.

Gathered informations will be shown in a ListView as you can see in the following picture :

screenshot

All the informations (temperature, icon on the left and description of the weather) are provided from OpenWeatherMap.org in a JSON format like this:

{
   "coord":{
      "lon":-0.13,
      "lat":51.51
   },
   "sys":{
      "type":1,
      "id":5091,
      "message":0.0192,
      "country":"GB",
      "sunrise":1425623634,
      "sunset":1425664170
   },
   "weather":[
      {
         "id":800,
         "main":"Clear",
         "description":"Sky is Clear",
         "icon":"01d"
      }
   ],
   "base":"cmc stations",
   "main":{
      "temp":283.2,
      "pressure":1030,
      "humidity":57,
      "temp_min":280.93,
      "temp_max":285.37
   },
   "wind":{
      "speed":5.7,
      "deg":210
   },
   "clouds":{
      "all":0
   },
   "dt":1425664129,
   "id":2643743,
   "name":"London",
   "cod":200
}

The libraries: GSON and Picasso

GSON is a Java library that supports the conversion from JSON to Java objects and viceversa. Developers can benefit from GSON when they have to convert JSON file with a complex structure. GSON can do it automatically by using Java reflection. It can handle simple data type, common Java objects and collections.
Picasso is a libray created and mantained by Square Open Source (http://square.github.io/). It’s useful for fetching images from Internet, caching them to optimize network usage and linking them to an ImageView widget.
Both the libraries can be included in Android projects by using Maven or downloading them as a JAR archive from the following links:
Picasso: http://square.github.io/picasso/#download
GSON: http://code.google.com/p/google-gson/

The code

First of all, the structure of every row in the User Interface is designed by the following layout:

row.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="#CCFFDD"
    android:padding="5dp" >

    <ImageView
        android:id="@+id/img"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true" />

    <TextView
        android:id="@+id/city"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignTop="@id/img"
        android:layout_marginLeft="10dp"
        android:layout_toRightOf="@+id/img"
        android:textSize="20sp"
        android:textStyle="bold|italic" />

    <TextView
        android:id="@+id/description"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@id/city"
        android:layout_below="@id/city"
        android:textSize="18sp" />

    <TextView
        android:id="@+id/temperature"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_centerVertical="true"
        android:textSize="25sp" />

</RelativeLayout>

Output :

row

It correspondes to the file /res/layout/row.xml.

The following code is the MainActivity class:

MainActivity.java

package com.androidbegin.weather;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.MalformedURLException;
import java.net.URL;

import com.google.gson.Gson;

import android.app.ListActivity;
import android.os.AsyncTask;
import android.os.Bundle;

public class MainActivity extends ListActivity {
	// Base address for informations download
	private final static String BASE_ADDR = "http://api.openweathermap.org/data/2.5/weather";

	// The following array contains the names of the cities we are interested in
	private String[] cities = new String[] { "Tokyo", "London", "Moscow",
			"Ottawa", "Madrid", "Lisboa", "Zurich" };

	// Reference to the Adapter object. WeatherAdapter is a custom class,
	// defined in a separate file

	private WeatherAdapter adapter;

	/*
	 * Mixing the String object containing the name of the city with the base
	 * address, we can generate the complete HTTP address to contact. Its format
	 * should be like this:
	 * http://api.openweathermap.org/data/2.5/weather?q=London This is the
	 * purpose of the getDataAddress method.
	 */
	private String getDataAddress(String city) {
		return BASE_ADDR + "?q=" + city;
	}

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);

		// The adapter object is instantiated
		adapter = new WeatherAdapter(this);

		// The adapter is linked to the ListView
		setListAdapter(adapter);

		// We request informations separately via HTTP for every city in the
		// array
		for (String c : cities)
			loadJson(c);
	}

	// This method performs the remote request and uses GSON to parse JSON data
	protected void loadJson(String selected) {

		// We extend the AsyncTask class and instance an object directly.
		new AsyncTask<String, Void, Data>() {

			// The doInBackground method contains the slower operations we want
			// to perform on a separate thread
			@Override
			protected Data doInBackground(String... params) {
				// Params array contains only one object and its value is the
				// string representing the name of the city
				String selectedCity = params[0];

				// URL object we'll use to connect to remote host
				URL url;
				try {
					// URL object is created. The input parameter we pass is the
					// URL to contact
					url = new URL(getDataAddress(selectedCity));

					// After connection, url provides the stream to the remote
					// data. Reader object can be used to read them
					Reader dataInput = new InputStreamReader(url.openStream());

					// GSON needs a stream to read data. We pass two parameters
					// to fromJson method: the stream to read and the Data class
					// structure for automatic parsing
					Data data = new Gson().fromJson(dataInput, Data.class);
					return data;
				} catch (MalformedURLException e1) {
					return null;
				} catch (IOException e1) {
					return null;
				}
			}

			// The onPostExecute method receives the return value of
			// doInBackground. Remember that onPostExecute works on the main
			// thread of the application
			protected void onPostExecute(Data result) {
				if (result != null) {
					// If not null, Data object is passed to the Adapter
					adapter.add(result);
				}
			};

			// The execute method is invoked to activate the background
			// operation
		}.execute(selected);

	}

}

MainActivity is a subclass of ListActivity, thus it already includes a ListView. In the onCreate method we’ll initialize the adapter, that is a custom class named WeatherAdapter.
The method called loadJson performs the real network interaction. It invokes the doInBackground of an AsyncTask-derived object that opens a connection to the HTTP address which will provide data and will submit the input stream to GSON as a Reader object.
This is when GSON downloads and converts JSON data in a Data object. The Data class is defined in the project and its properties reflect the JSON ones. GSON will only convert automatically the properties for which it can find a correspondent key in JSON file:

Data.java

package com.androidbegin.weather;

import java.util.List;

// GSON will convert JSON data to a Data object
class Data {
	// Base address for icon download
	private final static String ICON_ADDR = "http://openweathermap.org/img/w/";

	static class Weather {
		String description;
		String icon;
	}

	static class Main {
		float temp;
	}

	List<Weather> weather;

	Main main;

	String name;

	// A method that converts temperature from Kelvin degrees to Celsius
	String getTemperatureInCelsius() {
		float temp = main.temp - 273.15f;
		return String.format("%.2f", temp);
	}

	// getIconAddress concatenates the base address and the specific code for
	// the icon
	public String getIconAddress() {
		return ICON_ADDR + weather.get(0).icon + ".png";
	}

	public String getDescription() {
		if (weather != null && weather.size() > 0)
			return weather.get(0).description;
		return null;
	}
}

The WeatherAdapter class extends BaseAdapter and overrides four methods: getCount, getItem, getItemId and getView.

WeatherAdapter.java

package com.androidbegin.weather;

import java.util.ArrayList;

import com.squareup.picasso.Picasso;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;

public class WeatherAdapter extends BaseAdapter {

	// Source data: each of Data objects contains the informations about a city
	private ArrayList<Data> cities = new ArrayList<Data>();
	private Context context;

	// A ViewHolder object stores each of the widgets reference in the layout.
	// It avoids a frequent use of findViewById
	private static class ViewHolder {
		ImageView imgv;
		TextView city;
		TextView description;
		TextView temperature;
	}

	public WeatherAdapter(Context context) {
		this.context = context;
	}

	// A Data object is added to List<Data>.
	// The invocation of notifyDataSetChanged implies the ListView refresh
	void add(Data c) {
		cities.add(c);
		notifyDataSetChanged();
	}

	// getCount returns the number of elements in the data structure
	@Override
	public int getCount() {
		return cities.size();
	}

	// getItem returns the element stored in a specific position of the data
	// structure
	@Override
	public Object getItem(int pos) {

		return cities.get(pos);
	}

	/*
	 * getItemId performs the same work of getItem but returns the object id
	 * instead of the reference to the object
	 */
	@Override
	public long getItemId(int pos) {

		return pos;
	}

	/*
	 * getView creates a View object that displays informations stored in a Data
	 * object at a specific position in the ArrayList
	 */
	@Override
	public View getView(int pos, View v, ViewGroup vg) {

		ViewHolder vh = null;

		// If v is null, a new View is instantiated
		if (v == null) {
			// The LayoutInflater creates the View using a XML layout as a model
			v = LayoutInflater.from(context).inflate(R.layout.row, null);

			// References to internal widgets are stored in a ViewHolder
			vh = new ViewHolder();
			vh.imgv = (ImageView) v.findViewById(R.id.img);
			vh.description = (TextView) v.findViewById(R.id.description);
			vh.city = (TextView) v.findViewById(R.id.city);
			vh.temperature = (TextView) v.findViewById(R.id.temperature);
			v.setTag(vh);
		} else
			// v is not null so we don't need to invoke the LayoutInflater
			vh = (ViewHolder) v.getTag();

		// We retrieve the object in a specific position
		Data res = (Data) getItem(pos);

		// Picasso library downloads the icon and set it as ImageView source
		Picasso.with(context).load(res.getIconAddress()).into(vh.imgv);

		// Other TextViews are filled with Data object informations
		vh.description.setText(res.getDescription());
		vh.city.setText(res.name);
		vh.temperature.setText(res.getTemperatureInCelsius() + "°C");

		return v;
	}

}

In particular, the getView method is called when a Data object has to be converted in a View. In the getView code is created the structure of the View by inflating the layout from row.xml file. After that, we create a ViewHolder object whose purpose is to store the references of the views in the layout, TextViews and the ImageView. Then, the ViewHolder is linked to the View that will use it by invoking the setTag method.
In the getView method, we can see Picasso library at work. This tool already includes everything we need to fetch images. The operation will be performed asynchronously and Picasso will bear all the tasks related to downloading and caching images.

Furthermore, the syntax for Picasso invocation is very straightforward requiring a single code line:

Picasso.with(context).load(res.getIconAddress()).into(vh.imgv);

Once downloaded, the icon will be shown in the ImageView referenced by vh.imgv.

Finally, in your AndroidManifest.xml, we need to declare permissions to allow the application to connect to the Internet. Open your AndroidManifest.xml and paste the following code.

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.androidbegin.weather"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="14"
        android:targetSdkVersion="21" />

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

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            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>

Output:

screenshot_weatherapp

Source Code

[purchase_link id=”8036″ text=”Purchase to Download Source Code” style=”button” color=”green”]

Latest comments

Thanks for the great tutorial, please do not take long time for the next tutorial, thx :))

مبرمج اندرويد

Accessing Weather Informations with GSON and Picasso