Aplicación completa II : Utilizando el API de HTTP

(Fuente: http://www.javacodegeeks.com/2010/10/android-full-app-part-2-using-http-api.html)

En la primera parte de esta serie hemos creado el interfaz básico para la actividad principal de la aplicación. En esta parte vamos a ver cómo utilizar un API HTTP externo y cómo integrar las posibilidades de búsqueda dentro de nuestra aplicación.

El TMDb API

Para la búsqueda de actores y películas vamos a utilizar el TMDb API (http://api.themoviedb.org/2.1). Según la definición oficial:“El TMDbAPI es un recurso potente para todos los programadores que quieren integrar datos de películas junto con posters o chismes de películas. Todos los métodos API están disponibles en XML, YAML y JSON”.

Como la mayoría de los APIs disponibles, necesitas un valid key para poder utilizar el API. El primer paso para eso es crearse un cuenta gratuito en la página http://www.themoviedb.org/account/signup. Después de registrarte, haz logging en tu cuenta y encuentra el link para generar un API key. En mi caso me registro ,y después de rellenar un formulario me dicen que me la enviaran en 3 días laborables.

La lista de los métodos disponibles en el API los puedes encontrar en la documentación del TMDb aPI y los más importantes son las siguientes:

  • Movie.search: proporciona la forma más fácil y rápida de buscar una película.Para buscar info sobre la peli “Transformers” ejemplo de URL es el siguiente:

http://api.themoviedb.org/2.1/Movie.search/en/xml/APIKEY/Transformers

  • Person.search: se utiliza para buscar por un actor, actriz o miembro de producción, un ejemplo de búsqueda de actores:

http://api.themoviedb.org/2.1/Person.search/en/xml/APIKEY/Brad+Pitt

(donde APIKEY debe ser reemplazado por un API key válida)

Como puedes ver, el API es fácil de utilizar. Sólo hay que hacer la request HTTP con las URL especificadas y entonces recuperar las respuestas en un formato predefinido. Proximamente, vamos a ver las posibilidades de networking del Android para utilizar el API y recoger y presentar los datos proporcionados. Observese que utilizaremos el formato XML para las respuestas, aunque esto lo veremos en el siguiente artículo de la serie.

Haciendo peticiones HTTP

Para manipular las HTTP requests/responses en un entorno Android, las clases estándar del paquete java.net pueden ser utilizadas. Asi, clases como URL, URLConnection, HttpURLConnection etc todas pueden ser utilizadas de la forma ya conocida. Sin embargo, si queremos despreocuparnos por los detalles de bajo nivel podemos utilizar las Apache HTTP Client libraries. Estas librerías están basadas en el ya conocido Apache Commons HTTP Client frameworkhttp://hc.apache.org/

Empezemos con el código. Crearemos una clase llamada “HttpRetriever” que será responsable de realizar todas las HTTP requests y retornará las respuestas tanto en formato texto como en stream (para manipulación de imágenes). El código para esta clase es el siguiente:

package com.jes;

import java.io.IOException;
import java.io.InputStream;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Log;

import com.jes.io.FlushedInputStream;
import com.jes.util.Utils;

public class HttpRetriever {

	private DefaultHttpClient client = new DefaultHttpClient();

	public String retrieve(String url) {
		HttpGet getRequest = new HttpGet(url);

		try {
			HttpResponse getResponse = client.execute(getRequest);
			final int statusCode = getResponse.getStatusLine().getStatusCode();

			if (statusCode != HttpStatus.SC_OK) {
				Log.w(getClass().getSimpleName(), "Error " + statusCode + " for URL " + url);
				return null;
			}

			HttpEntity getResponseEntity = getResponse.getEntity();
			if (getResponseEntity != null) {
				return EntityUtils.toString(getResponseEntity);
			}
		} catch (IOException e) {
			getRequest.abort();
			Log.w(getClass().getSimpleName(), "Error for URL " + url, e);
		}

		return null;

	}

	public InputStream retrieveStream(String url) {
		HttpGet getRequest = new HttpGet(url);
		try {
			HttpResponse getResponse = client.execute(getRequest);
			final int statusCode = getResponse.getStatusLine().getStatusCode();
			if (statusCode != HttpStatus.SC_OK) {
				Log.w(getClass().getSimpleName(), "Error " + statusCode + " for URL " + url);
				return null;
			}

			HttpEntity getResponseEntity = getResponse.getEntity();
			return getResponseEntity.getContent();
		}catch (IOException e) {
			getRequest.abort();
			Log.w(getClass().getSimpleName(), "Error for URL " + url, e);
		}

		return null;
	}

	public Bitmap retrieveBitmap(String url) throws Exception {
		InputStream inputStream = null;
		try {
			inputStream = this.retrieveStream(url);
			final Bitmap bitmap = BitmapFactory.decodeStream(new FlushedInputStream(inputStream));
			return bitmap;
		}finally {
			Utils.closeStreamQuietly(inputStream);
		}	

	}

}

Para la ejecución de las peticiones HTTP, estamos utilizando una instancia de la clase DefaultHttpClient, la cual , como su nombre indica, es la implementación por defecto de un cliente HTTP, es decir, la implementación por defecto del interface HttpClient. El HTTP client ejecuta la request y proporciona un objeto HttpResponse que contiene la respuesta del servidor además de cualquier otra información.

Por ejemplo, podemos recuperar el response status code y compararlo el código con el código HttpStatus.SC_OK para ver si la petición ha ido bien. Cuando la petición ha ido bien, tomamos la referencia del objeto HttpEntity que está anidado desde el cual podemos tener acceso a los datos de la respuesta.

Para respuestas de texto convertimos la entity a String utilizando un método de la EntityUtils. Si deseamos recuperar los datos como un byte stream (por ejemplo para cuando nos bajemos un binario), utilizaremos el método getContent de la clase HttpEntity, que crea un nuevo objeto InputStream de la entity.

Observese que hay también un tercer método para retornar directamente objetos Bitmap. Esto puede ser útil en las últimos artículos de esta serie, donde descargaremos imágenes de internet. En ese método, ejecutaremos una GET request y recuperaremos un InputStream como siempre. Entonces, utilizaremos un método decodeStream de la clase BitmapFactory para crear un nuevo objeto Bitmap. Noteseque no proporcionamos directamente el InputStream descargado, sino que primero lo envolvemos en una clase FlushedInputStream. (En los foros de Android developers , se comenta que hay un bug en versiones previas del método decodeStream que puede causar problemas a la hora de descargar una imagen en una conexión lenta. Con la clase FlushedInputStream, que extiende FilterInputStream, se utiliza para saltarnos ese bug). El código de FlushedInputStream es el siguiente:

package com.javacodegeeks.android.apps.moviesearchapp.io;

import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;

public class FlushedInputStream extends FilterInputStream {
	public FlushedInputStream(InputStream inputStream) {
		super(inputStream);
	}

	@Override
	public long skip(long n) throws IOException {
			long totalBytesSkipped = 0L;
			while (totalBytesSkipped < n) {
				long bytesSkipped = in.skip(n - totalBytesSkipped);
				if (bytesSkipped == 0L) {
				int b = read();
				if (b < 0) {
					break;  // we reached EOF
				} else {
					bytesSkipped = 1; // we read one byte
				}
			}
			totalBytesSkipped += bytesSkipped;
		}
		return totalBytesSkipped;
	}

}

Con el método skip() sobrecargado nos aseguramos que de hecho se coge el número proporcionado de bytes, a menos que se llegue al final del fichero. Finalmente, utilizaremos el método closeStreamQuietly de la clase Utils para manejar las excepciones que pueden ocurrir cuando cerramos un InputStream. El código es el siguiente:

package com.javacodegeeks.android.apps.moviesearchapp.util;

import java.io.IOException;
import java.io.InputStream;

public class Utils {
	public static void closeStreamQuietly(InputStream inputStream) {
		try {
			if (inputStream != null) {
				inputStream.close();
			}
		} catch (IOException e) {
			// ignore exception
		}
	}
}

Dando permisos para realizar HTTP requests

Finalmente, para realizar las HTTP requests deben darse los permisos correspondientes. Asi , añadimos el android.permission.INTERNET al AndroidManifest.xml del proyecto, quedando el archivo como sigue:

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

<application android:icon="@drawable/icon" android:label="@string/app_name">
	<activity android:name=".MovieSearchAppActivity" 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>

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

</manifest>

Asi , tenemos preparada la infraestructura para ejecutar HTTP GET requests, En los siguiente tutos, utilizaremos estas clases para recuperar datos XML e imágenes según las necesidades de nuestra aplicación.