Algunos repositorios interesantes en Github para Android


Hace tiempo que no escribo y para volver a escribir, se me ocurrió empezar con un artículo mas sencillo (aunque no por eso menos interesante)

Parece ser que últimamente el sistema de control de versiones de moda es git y su “versión web” mas popular: github. Tanto es así que últimamente dos grandes como Spring y Django se han pasado a este sistema de control de versiones.

De vez en cuando, me gusta mirar por los repositorios para ver que puede haber interesante, y que puede ser aprovechable para futuros proyectos. Aparte de por supuesto, de aprender un montón leyendo código de terceros (lo recomiendo enormemente). Y en uno de estos barridos de los repositorios de github encontré varios proyectos open-source bastante interesantes y que son menos conocidos que los habituales en el mundo Android.

If you know any project, framework or library that has to be include here, let me know and I’ll put it on the list =)


Integrando ZXing en nuestra aplicación Android: Lectura de códigos


Hace un tiempo, me planteé como integrar Zxing barcode scanner en un proyecto propio para no tener que llegar a tener instalado ninguna aplicación extra (a pesar de la posibilidad que ofrecen los propios desarrolladores del proyecto para utilizar intents, y de que uno de sus creadores nos advierte de las desventajas de usar esta posibilidad)

Sin embargo, mi curiosidad me llevó a plantearme como podría llegar a integrar por completo la aplicación dentro de un proyecto propio. Algunos de los motivos por los que creo que podría ser útil son:

  • Posibilidad de modificar la UI a tu gusto
  • Cuando el proyecto llegue a tener una envergadura lo suficientemente grande, puede ser mas fácil compartir el modelo de datos diseñado con ZXing (Esto creedme que es mas importante de lo que parece…)

Todo esto es posible gracias a Sean Owen (sino me equivoco es el creador), y a la licencia Apache 2.0

Primero de todo debemos descargar el código fuente de ZXing para integrarlo con nuestra aplicación. Esto podemos realizarlo aquí

Una vez descargado, lo descomprimimos, y tendremos que meter en nuestro proyecto tanto el código fuente de la carpeta android como enlazar la librería core.jar.

Crearemos una nueva carpeta de fuentes en la que meteremos el proyecto ZXing, y otra para meter las librerías (en este caso solo las necesarias por ZXing). Nuestro workspace debería quedar algo parecido a esto:

IMAGEN:article06-01.png!

Podemos ver como hay muchos errores aún… Es sobretodo debido a que los resources de ZXing aún no están declarados

A partir de aquí viene la parte mas "entretenida". Declararemos la Activity de Zxing en el AndroidManifest.xml tal y como vemos en el siguiente código:

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

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

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

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:name=".ZxingExampleActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name="com.google.zxing.client.android.CaptureActivity"
            android:label="ZXing"/>
    </application>

</manifest>

Además del permiso de la cámara, Zxing usa muchos mas pero para este ejemplo no son necesarios. Cada uno tendrá que explorar ZXing y ver que partes le interesa utilizar y que permisos puede quitar.

En este punto ya podríamos utilizar la Activity para el escaneo de códigos QR... sino fuera porque aún tenemos gran cantidad de errores en el proyecto...

Lo siguiente a solucionar es la parte mas fácil de todas (o no, dependiendo del tamaño del proyecto): Debemos mezclar los resources de ZXing con los de nuestro proyecto. Es decir, todos los layouts, strings.xml, attrs.xml, etc... deben ser mezclados con los nuestros. Aquí lo que debemos tener en cuenta es que puede haber nombres de resources que estén en ambos proyectos. En este caso, simplemente deberemos borrar uno de los dos. En el caso de que el proyecto sea completamente nuevo será mucho mas fácil: simplemente copiamos la carpeta res de ZXing a nuestro proyecto, y este paso estará completado.

Por último deberemos cambiar algo en el código fuente de ZXing. Las referencias a la clase R están mal (import com.google.zxing.client.android.R;
) y deberemos hacerlas por nosotros mismos. Así, en mi caso por ejemplo, tendré que incluir en los archivos que sea necesario el siguiente trozo de código para poder importarlo correctamente:

import es.jmanzano.R;

Para poder utilizar la activity hemos creado un simple main.xml y una activity que lo usa.

package es.jmanzano;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;

import com.google.zxing.client.android.CaptureActivity;

public class ZxingExampleActivity extends Activity {
	/** Called when the activity is first created. */
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
	}

	public void onClick(View view) {
		Intent intent = new Intent(getApplicationContext(),
				CaptureActivity.class);
		startActivity(intent);
	}
}
<?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="ZXing launcher!" />

    <Button
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:onClick="onClick"
        android:text="start ZXing activity"/>

</LinearLayout>

Una vez realizados todo esto, podemos ponernos a explorar el código de ZXing y modificarlo a nuestro gusto. En partícular, las clases que mas nos interesará investigar son:

  • CaptureActivity: Activity principal encargada de mostrar la pantalla de escaneo de códigos QR. Debemos prestar especial atención al método handleDecodeExternally() en el que podremos realizar lo que necesitemos con lo leído del código QR.
  • ViewfinderView: Este será la capa que superpondremos a la preview de la cámara, y en la que podemos realizar todas los cambios que queramos para que quede a nuestro gusto la lectura de códigos QR

Espero que os haya gustado el artículo y cualquier duda que tengáis podéis preguntarmela por aquí directamente, correo electrónico o twitter


Haciendo mas fáciles las conexiones a SQLite 3 en Android: ORMLite


En uno de los artículos anteriores uno de los lectores del blog preguntó por algún ORM que facilitara el acceso a base de datos en Android. Debido a que desconocía en ese momento ninguno, me propuse buscar alguno que pudiera cumplir con los objetivos.

He de reconocer que tampoco busqué mucho, y al final dí con ORMLite que cuenta con una versión para Android. De este modo, y basándome en uno de los múltiples ejemplos de los que posee el proyecto creé un simple proyecto que simula una agenda de contactos que almacena por cada contacto el nombre, email y número de teléfono.

Tanto la parte visual como el control de excepciones no está muy trabajado, ya que consideraba que no era el objetivo del artículo y quería ir directo al grano.

Para empezar, lo primero que debemos hacer es descargas las librerías y enlazarlas en nuestro proyecto. Básicamente, debería bastar con ormlite-core-4.28.jar y ormlite-android-4.28.jar (como podemos observar, la versión actual es la 4.28)

A continuación mostramos dos de los archivos responsables del funcionamiento del ejemplo, aunque no van a ser explicados ya que son bastante autoexplicativos (AndroidManifest.xml y main.xml):

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

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

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:name=".OrmliteActivity"
            android:label="ORML" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <ScrollView
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" >

        <TextView
            android:id="@+id/queryResults"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:text="Welcome to the ORMLite example" />
    </ScrollView>

    <TableLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:background="#FFFFFF" >

        <TableRow>

            <TextView
                android:text="Name:"
                android:width="100dp" />

            <EditText
                android:id="@+id/etName"
                android:width="190dp" />
        </TableRow>

        <TableRow>

            <TextView android:text="Email:" />

            <EditText android:id="@+id/etEmail" />
        </TableRow>

        <TableRow>

            <TextView android:text="Telephone number:" />

            <EditText android:id="@+id/etTelephoneNumber" />
        </TableRow>

        <TableRow android:id="@+id/TableRow03" >

            <Button
                android:id="@+id/bShowContacts"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:onClick="onClick"
                android:text="Show contacts" />

            <Button
                android:id="@+id/bAdd"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:onClick="onClick"
                android:text="Add Contact" />
        </TableRow>
    </TableLayout>

</RelativeLayout>

A continuación podemos ver la clase principal de nuestro modelo de datos: Contact. En ella por medio de las anotación @DatabaseField determinaremos los miembros que van a ser almacenados en base de datos

package es.jmanzano.ormlite.example;

import com.j256.ormlite.field.DatabaseField;

/**
 * A simple model object to store a Contact
 */
public class Contact {

	@DatabaseField(generatedId = true)
	int id;
	@DatabaseField
	String name;
	@DatabaseField
	Integer telephoneNumber;
	@DatabaseField
	String email;

	Contact() {
		// needed by ormlite
	}

	public Contact(String name, Integer telephoneNumber, String email) {
		this.name = name;
		this.telephoneNumber = telephoneNumber;
		this.email = email;
	}

	@Override
	public String toString() {
		return name + ": " + telephoneNumber + ", " + email;
	}

}

Y comenzando con la configuración de la Base de datos por medio de ORMLite, debemos declarar el fichero ormlite_config.txt en la carpeta raw

#
# --table-start--
dataClass=es.jmanzano.ormlite.example.Contact
tableName=contact
# --table-fields-start--
# --field-start--
fieldName=id
generatedId=true
# --field-end--
# --field-start--
fieldName=name
# --field-end--
# --field-start--
fieldName=telephoneNumber
# --field-end--
# --field-start--
fieldName=email
# --field-end--
# --table-fields-end--
# --table-end--
#################################

Aquí declararemos la clase que persistiremos, en este caso Contact y los campos correspondientes con table-field-start y table-field-end.

Por último nos queda por definir el DatabaseHelper (conocido por todos los que hemos definido alguna vez alguna interacción con la BBDD de Android).

package es.jmanzano.ormlite.example;

import java.sql.SQLException;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;

import com.j256.ormlite.android.apptools.OrmLiteSqliteOpenHelper;
import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.dao.RuntimeExceptionDao;
import com.j256.ormlite.support.ConnectionSource;
import com.j256.ormlite.table.TableUtils;

/**
 * Database helper class used to manage the creation and upgrading of your database. This class also usually provides
 * the DAOs used by the other classes.
 */
public class DatabaseHelper extends OrmLiteSqliteOpenHelper {

	// name of the database file for your application
	private static final String DATABASE_NAME = "contact.db";

	// any time you make changes to your database objects, you may have to increase the database version
	private static final int DATABASE_VERSION = 1;

	// the DAO object we use to access the SimpleData table
	private Dao<Contact, Integer> simpleDao;

	private RuntimeExceptionDao<Contact, Integer> simpleRuntimeDao;

	public DatabaseHelper(Context context) {
		super(context, DATABASE_NAME, null, DATABASE_VERSION, R.raw.ormlite_config);
	}

	/**
	 * This is called when the database is first created. Usually you should call createTable statements here to create
	 * the tables that will store your data.
	 */
	@Override
	public void onCreate(SQLiteDatabase db, ConnectionSource connectionSource) {
		try {
			TableUtils.createTable(connectionSource, Contact.class);
		} catch (SQLException e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * This is called when your application is upgraded and it has a higher version number. This allows you to adjust
	 * the various data to match the new version number.
	 */
	@Override
	public void onUpgrade(SQLiteDatabase db, ConnectionSource connectionSource, int oldVersion, int newVersion) {
		try {
			TableUtils.dropTable(connectionSource, Contact.class, true);
		} catch (SQLException e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * Returns the Database Access Object (DAO) for our SimpleData class. It will create it or just give the cached
	 * value.
	 */
	public Dao<Contact, Integer> getDao() throws SQLException {
		if (simpleDao == null) {
			simpleDao = getDao(Contact.class);
		}
		return simpleDao;
	}

	/**
	 * Returns the RuntimeExceptionDao (Database Access Object) version of a Dao for our SimpleData class. It will
	 * create it or just give the cached value. RuntimeExceptionDao only through RuntimeExceptions.
	 */
	public RuntimeExceptionDao<Contact, Integer> getSimpleDataDao() {
		if (simpleRuntimeDao == null) {
			simpleRuntimeDao = getRuntimeExceptionDao(Contact.class);
		}
		return simpleRuntimeDao;
	}

	/**
	 * Close the database connections and clear any cached DAOs.
	 */
	@Override
	public void close() {
		super.close();
		simpleRuntimeDao = null;
	}
}

Aquí podemos ver los distintos métodos típicos de los SqliteOpenHelper: onCreate() y onUpgrade(). Como podemos ver, nos ayudaremos de la clase TableUtils para create las tablas con createTable() y dropTable() para poder borrarlas. Debemos tener en cuenta que la calse debe extender a OrmLiteSqliteopenHelper, y que en el constructor de nuestra clase debemos pasarle al constructor de la superclase el id del archivo de configuración.

Por último, mostramos la definición de la activity principal: OrmliteActivity. Aquí realizaremos tanto la creación de los registros en BBDD como la visualización:

package es.jmanzano.ormlite.example;

import java.util.List;

import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;

import com.j256.ormlite.android.apptools.OrmLiteBaseActivity;
import com.j256.ormlite.dao.RuntimeExceptionDao;

/**
 * Sample Android UI activity which displays a text window when it is run.
 */
public class OrmliteActivity extends OrmLiteBaseActivity<DatabaseHelper> {

	private EditText etName;
	private EditText etTelephoneNumber;
	private EditText etEmail;
	private TextView tvResults;

	private RuntimeExceptionDao<Contact, Integer> dao;

	/**
	 * Called when the activity is first created.
	 */
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		tvResults = (TextView) findViewById(R.id.queryResults);
		etName = (EditText) findViewById(R.id.etName);
		etEmail = (EditText) findViewById(R.id.etEmail);
		etTelephoneNumber = (EditText) findViewById(R.id.etTelephoneNumber);
		dao = getHelper().getRuntimeExceptionDao(Contact.class);

		queryForAll();
	}

	/**
	 * Query for all the regs
	 */
	private void queryForAll() {
		// query for all of the data objects in the database
		List<Contact> list = dao.queryForAll();
		// our string builder for building the content-view
		StringBuilder sb = new StringBuilder();

		sb.append("Found " + list.size() + " results\n");
		for (Contact contact : list) {
			sb.append(contact.toString() + "\n");
		}
		sb.append("-------------------------------------------------\n");

		tvResults.setText(sb.toString() + tvResults.getText());
	}

	private void addContact() {
		StringBuilder sb = new StringBuilder();
		sb.append("Added contact \"" + etName.getText().toString() + "\" to the address book\n");
		dao.create(
				new Contact(
						etName.getText().toString(),
						Integer.parseInt(etTelephoneNumber.getText().toString()),
						etEmail.getText().toString())
				);
		sb.append("-------------------------------------------------\n");

		tvResults.setText(sb.toString() + tvResults.getText());
	}

	public void onClick(View view) {
		int id = view.getId();
		if (id == R.id.bShowContacts) {
			queryForAll();
		} else if (id == R.id.bAdd) {
			addContact();
		}

	}

}

Básicamente debemos comentar dos métodos, el de inserción y el de visualización, así como extender de la clase OrmliteBaseActivity parametrizándola con el database helper que hayamos definido.

  • En el método queryForAll() vemos que simplemente, teniendo una referencia al dao que estemos usando (que hemos obtenido previamente con getHelper().getRuntimeExceptionDao(Contact.class), podemos realizar una llamada al método queryForAll() para obtener todos los registros.
  • Por otra parte, en el método addContact() simplemente deberemos pasar al método create() el objeto que queremos crear, en este caso un Contact

Como hemos podido observar… en tan solo unos pasos hemos podido obtener una base de datos y realizar las operaciones de inserción y visualización de una manera muy sencilla. ¿Que os parece? ¿Os parece claro el código? ¿Usais otro ORM distinto para Android?


Peticiones REST eficientes con Spring Android


En artículos anteriores me recomendaron que usara Spring Android para poder realizar peticiones REST de forma mas eficiente, así que me puse manos a la obra.

Para ello usaremos los ejemplos anteriores (1,2) solo que con una pequeña modificación. El objeto JSON que devolverá ahora el servidor será de la siguiente forma:

[
	{"emails":["email.2@yahoo.com","emailnew@gmail.com"],"name":"Contacto 1","numbers":["78789987","78789227","44449987"]},
	{"emails":["contacto2@gmail.com","contacto3@gmail.com"],"name":"Contacto 2","numbers":["22449987","29393291"]},
	{"emails":["contacto3@gmail.com","contacto3@gmail.com"],"name":"Contacto 3","numbers":["2222287","27283931"]}
]

En los ejemplos anteriores, hacíamos peticiones con los clientes de Android, pero ahora vamos a ir un paso mas allá usando Spring Android. Lo primero que debemos hacer es bajarnos las librerías correspondientes: las librerías de Spring y las librerías de Jackson para el mapeo de objetos JSON.

Una vez tengamos listo el proyecto, lo único que tenemos que hacer es utilizar la librería de Spring utilizando la clase RestTemplate:

/**
* Get method for the JSON Contacts
*/
public List<Contact> getContacts() {
	HttpHeaders requestHeaders = new HttpHeaders();
	requestHeaders.setAccept(Collections.singletonList(new MediaType("application","json")));
	HttpEntity<?> requestEntity = new HttpEntity<Object>(requestHeaders);

	String url = "http://domain/resources/api/json";

	MappingJacksonHttpMessageConverter messageConverter = new MappingJacksonHttpMessageConverter();
	List<HttpMessageConverter<?>> messageConverters = Lists.newArrayList();
	messageConverters.add(messageConverter);

	RestTemplate restTemplate = new RestTemplate();
	restTemplate.setMessageConverters(messageConverters);

	ResponseEntity<Contact[]> responseEntity = restTemplate.exchange(url, HttpMethod.GET, requestEntity, Contact[].class);
	Contact[] result= responseEntity.getBody();

	return Arrays.asList(result);

}

Como podemos ver, el método getContacts() ha cambiado completamente y se ha simplificado mucho todo (con respecto a este ejemplo). Lo único que tenemos que hacer es seleccionar los requestHeaders que queremos usar (en este caso estableceremos el Accept para recibir objetos JSON), y establecer los conversores que usaremos (en este caso usaremos el MappingJacksonHttpMessageConverter, aunque podríamos haber usado el GsonHttpMessageConverter incluyendo las librerías necesarias).

Por último, usaremos el método exchange especificando todos los parámetros necesarios para recuperar la entidad, y posteriormente el método getBody() para recuperar la lista de contactos.

Como se puede observar, se ha simplificado enormemente la obtención y el parseo de objetos JSON. De este modo, podemos eliminar todas las clases que anteriormente usamos para el parseo de los objetos JSON.


ListViews eficientes en Android: Patrón View Holder


When we’re going to implement a good adapter, we have to take into account two things:

    - Reuse the convertView variable of the getView() method to avoid inflating new View objects when it’s not needed
    - Use the ViewHolder pattern, which is what we’re going to explain in this little article

Not taking into account those advices may cause, sooner or later, the following findViewById() calls to provoke performance problems. This could be solved with the View Holder pattern.

To apply this pattern, we could simply create a nested class into the adapter that we’re using for the ListViews’ rows.

For example, if each of these rows contains 3 TextView (as we saw in the example: RESTFul app), we should include the class into ContactsArrayAdapter class

static class ContactsViewHolder {
	TextView txName;
	TextView txEmails;
	TextView txPhones
}

For the getView() method we have to make modifications. We’ve decided to separate the ContactsArrayAdapter class in a new file:

package es.jmanzano.json;

import java.util.List;

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

/**
 * Adapter class to show the contacts
 */
public class ContactsArrayAdapter extends ArrayAdapter<Contact> {

	/** Contacts list */
	private List<Contact> contacts;

	/** Context */
	private Context context;

	public ContactsArrayAdapter(Context context, int textViewResourceId,
			List<Contact> contacts) {
		super(context, textViewResourceId, contacts);
		this.context = context;
		this.contacts = contacts;
	}

	@Override
	public View getView(int position, View v, ViewGroup parent) {
		// Keeps reference to avoid future findViewById()
		ContactsViewHolder viewHolder;

		if (v == null) {
			LayoutInflater li = (LayoutInflater) getContext().getSystemService(
					Context.LAYOUT_INFLATER_SERVICE);
			v = li.inflate(R.layout.contact_row, parent, false);

			viewHolder = new ContactsViewHolder();
			viewHolder.txName = (TextView) v.findViewById(R.id.tvName);
			viewHolder.txEmails = (TextView) v.findViewById(R.id.tvEmails);
			viewHolder.txPhones = (TextView) v.findViewById(R.id.tvNumbers);

			v.setTag(viewHolder);
		} else {
			viewHolder = (ContactsViewHolder) v.getTag();
		}

		Contact contact = contacts.get(position);
		if (contact != null) {
			viewHolder.txName.setText(contact.getName());
			viewHolder.txEmails.setText(contact.getEmails().toString());
			viewHolder.txPhones.setText(contact.getNumbers().toString());
		}
		return v;
	}

	static class ContactsViewHolder {
		TextView txName;
		TextView txEmails;
		TextView txPhones;
	}
}

Here we can see how to use the setTag() method to set and get the ContactsViewHolder. This will manage all the visual data in our ListView and, as we can see, we can avoid the performance problem findViewById() due to we’re reusing the views thanks to ContactsViewHolder.


Aplicación RESTful: GAE + Android (II)


Aquí tenemos la segunda parte del artículo anterior (ha sido actualizado desde la última vez que lo leisteis probablemente). En esta segunda entrega realizaremos la aplicación de Android que nos ayudará a acceder a los datos del servidor (en mi caso lo subí a GAE) y poder mostrarlos en un ListView en Android. Así que, resumiendo, en esta aplicación tendremos un ejemplo de como utilizar un adaptador customizado para un ListView en Android y como poder parsear JSON.

Lo primero que debemos hacer es crear la aplicación y darle los permisos necesarios para acceder a los datos que encontremos en la red. Para ello incluiremos el siguiente permiso en el AndroidManifest.xml

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

Para poder utilizar la lista en la activity principal, deberemos hacer que extienda de ListActivity, y definiremos el fichero main.xml de la siguiente manera:

<?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" >

    <ListView
        android:id="@+android:id/list"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" >
    </ListView>

</LinearLayout>

Mostraremos los contactos en la lista utilizando el siguiente layout:

<?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:id="@+id/tvName"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/tvEmails"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/tvNumbers"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content" />

</LinearLayout>

Como podemos ver, no tiene nada de particular. Es un simple Layout con tres TextView para mostrar el contenido de cada contacto.

Evidentemente, deberemos disponer de una clase que represente a los contactos, tal y como lo hacía en la parte servidora. Simplemente deberíamos copiarla y simplificarla un poco, quedando como podemos ver:

package es.jmanzano.json;

import java.util.ArrayList;
import java.util.List;

public class Contact {

	/** User name */
	private String name;

	/** user's image */
	private String urlImage;

	/** List of numbers */
	private List<String> numbers;

	/** List of emails */
	private List<String> emails;

	public Contact() {
		numbers = new ArrayList<String>();
		emails = new ArrayList<String>();
	}

	/**
	 * Getters and setters
	 */
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getUrlImage() {
		return urlImage;
	}

	public void setUrlImage(String urlImage) {
		this.urlImage = urlImage;
	}

	public List<String> getNumbers() {
		return numbers;
	}

	public void setNumbers(List<String> numbers) {
		this.numbers = numbers;
	}

	public List<String> getEmails() {
		return emails;
	}

	public void setEmails(List<String> emails) {
		this.emails = emails;
	}

}

Nota: En este caso no hemos incluido Guava para la creación de las listas por simplificar el artículo, pero recomiendo su uso.

Para terminar el ejercicio solo nos quedan dos clases mas: ContactsJSONParser (para parsear los objetos JSON) y ExampleJSONActivity (la activity principal en la que leeremos los contactos).

package es.jmanzano.json;

import java.util.ArrayList;
import java.util.List;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

public class ContactsJSONParser {

	/** Array JSON Parser */
	private JSONArray arrayParser;

	public ContactsJSONParser(String json) {
		try {
			JSONObject jsonResponse = new JSONObject(json);
			this.arrayParser = jsonResponse.getJSONArray("contacts");
		} catch (JSONException e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * Get the i contact json
	 */
	private JSONObject getContactJSON(int i) {
		try {
			return arrayParser.getJSONObject(i);
		} catch (JSONException e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * Get the i contact
	 */
	private Contact getContact(int i) {
		try {
			JSONObject contactJSON = getContactJSON(i);
			Contact contact = new Contact();

			// Get the name
			String name = contactJSON.getString("name");
			// Get the emails
			List<String> emails = new ArrayList<String>();
			JSONArray emailsJson = contactJSON.getJSONArray("emails");
			for (int n = 0; n < emailsJson.length(); n++) {
				emails.add(emailsJson.getString(n));
			}

			// Get the numbers
			List<String> numbers = new ArrayList<String>();
			JSONArray numbersJson = contactJSON.getJSONArray("numbers");
			for (int n = 0; n < numbersJson.length(); n++) {
				numbers.add(numbersJson.getString(n));
			}
			contactJSON.getJSONArray("numbers");

			// Set the properties
			contact.setName(name);
			contact.setEmails(emails);
			contact.setNumbers(numbers);

			return contact;
		} catch (JSONException e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * Get all the contacts
	 */
	public List<Contact> getContacts() {
		List<Contact> contacts = new ArrayList<Contact>();
		for (int i = 0; i < arrayParser.length(); i++) {
			contacts.add(getContact(i));
		}
		return contacts;
	}
}

El funcionamiento de esta clase es bastante simple. Debemos crear una instancia pasándole el objeto JSON a parsear. Luego de eso, la manera de usarlo es bastante sencilla: simplemente llamaremos al método getContacts() y nos devolverá la lista de contactos que podemos encontrar en el servidor. Podemos ver como con los métodos getString() o getJSONArray() podemos obtener los campos que consideremos necesarios.

Por último utilizaremos la clase ExampleJSONAdapter para obtener los datos del objeto JSON

package es.jmanzano.json;

import java.io.IOException;
import java.util.List;

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.util.EntityUtils;

import android.app.ListActivity;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.TextView;

public class ExampleJSONActivity extends ListActivity {

	/** Called when the activity is first created. */
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		List<Contact> contacts = getContacts();
		ContactsArrayAdapter adapter = new ContactsArrayAdapter(
				getApplicationContext(), R.layout.contact_row, contacts);
		setListAdapter(adapter);
	}

	/**
	 * Get method for the JSON Contacts
	 */
	public List<Contact> getContacts() {
		try {
			// Get the JSON
			HttpClient client = new DefaultHttpClient();
			HttpGet request = new HttpGet(
					"http://domain/resources/api/json");
			HttpResponse response = client.execute(request);
			String jsonString = EntityUtils.toString(response.getEntity());
			// Parse the response
			ContactsJSONParser parser = new ContactsJSONParser(jsonString);

			return parser.getContacts();
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

	/**
	 * Adapter class to show the contacts
	 */
	private class ContactsArrayAdapter extends ArrayAdapter<Contact> {

		/** Contacts list */
		private List<Contact> contacts;

		/** Context */
		private Context context;

		public ContactsArrayAdapter(Context context, int textViewResourceId,
				List<Contact> contacts) {
			super(context, textViewResourceId, contacts);
			this.context = context;
			this.contacts = contacts;
		}

		@Override
		public View getView(int position, View convertView, ViewGroup parent) {
			View v = convertView;
			if (v == null) {
				LayoutInflater li = (LayoutInflater) getContext()
						.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
				v = li.inflate(R.layout.contact_row, parent, false);
			}

			Contact contact = contacts.get(position);
			if (contact != null) {
				TextView tvName = (TextView) v.findViewById(R.id.tvName);
				TextView tvEmails = (TextView) v.findViewById(R.id.tvEmails);
				TextView tvNumbers = (TextView) v.findViewById(R.id.tvNumbers);

				tvName.setText(contact.getName());
				tvEmails.setText(contact.getEmails().toString());
				tvNumbers.setText(contact.getNumbers().toString());
			}
			return v;
		}

	}
}

Aquí podemos ver definido tanto la ListActivity (que es la clase en sí misma), y el adapter para la lista.

En el método getContacts() está la parte mas interesante de la ListActivity. En este método creamos la conexión con el servidor y parseamos el objeto JSON por medio de
ContactsJSONParser
.

Debemos tener en cuenta que a la hora de hacer la petición GET, debereis poner vuestro propio dominio.

Por último nos queda por comentar la clase ContactsArrayAdapter que únicamente lo que hace es sobreescribir el método getView(), en el que asignamos los valores que queramos a cada uno de los TextView. Esto es posible gracias a que hemos llamado al constructor de la clase ArrayAdapter incluyendo la lista de contactos. De este modo nos evitamos tener que sobreescribir métodos como getCount() o getItem()


Aplicación RESTfull: GAE + Android (I)


Para empezar con este blog decidí escribir un post que fuera a la vez interesante y práctico. La situación de una aplicación Android accediendo a datos web de un servidor es bastante común, y hoy vamos a ver como preparar un servidor en Google App Engine (GAE) para poder acceder a través de peticiones REST.

En este caso vamos a realizar un simple servicio para el cual tendremos una lista de usuarios generada estáticamente, y a la que podremos acceder gracias a Jersey. Para ello, en este caso solo usaremos peticiones GET (aunque podríamos realizar también PUT, POST o DELETE con Jersey).

Lo primero que deberemos hacer es crear un proyecto de GAE (lo crearemos sin enlazar las librerías GWT, ya que en este caso no son necesarias), así como referenciar las librerías necesarias: En este caso estamos utilizando Jersey y Guava.

Como ejemplo crearemos una aplicación para manejar nuestros contactos con sus números de teléfono e emails. Para ello utilizaremos la clase Contact y la clase ContactsDatastore.

package example.json;

import java.util.List;

import javax.persistence.Column;
import javax.xml.bind.annotation.XmlRootElement;

import com.google.common.collect.Lists;

@XmlRootElement(name="contacts")
public class Contact {

	/** User name */
	@Column(nullable = false, length = 255)
	private String name;

	/** user's image */
	private String urlImage;

	/** List of numbers */
	private List<String> numbers = Lists.newArrayList();

	/** List of emails */
	private List<String> emails = Lists.newArrayList();

	/**
	 * Getters and setters
	 */
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getUrlImage() {
		return urlImage;
	}

	public void setUrlImage(String urlImage) {
		this.urlImage = urlImage;
	}

	public List<String> getNumbers() {
		return numbers;
	}

	public void setNumbers(List<String> numbers) {
		this.numbers = numbers;
	}

	public List<String> getEmails() {
		return emails;
	}

	public void setEmails(List<String> emails) {
		this.emails = emails;
	}

}

Y aquí podemos ver la clase que nos dará los resultados de ejemplo:

package example.json;

import java.util.List;

import com.google.common.collect.Lists;

public class ContactsDatastore {

	private static List<Contact> contacts = Lists.newArrayList();

	private ContactsDatastore() {
	}

	public static List<Contact> getContacts() {
		if (contacts.size() == 0) {
			Contact c1 = new Contact();
			c1.setEmails(Lists.newArrayList("email.2@gmail.com",
					"emailnew@gmail.com"));
			c1.setName("Contacto 1");
			c1.setNumbers(Lists
					.newArrayList("78789987", "78789227", "44449987"));
			c1.setUrlImage(null);
			Contact c2 = new Contact();
			c2.setEmails(Lists.newArrayList("contacto2@gmail.com"));
			c2.setName("Contacto 2");
			c2.setNumbers(Lists.newArrayList("22449987"));
			c2.setUrlImage(null);
			Contact c3 = new Contact();
			c3.setEmails(Lists.newArrayList("contacto3@gmail.com"));
			c3.setName("Contacto 3");
			c3.setNumbers(Lists.newArrayList("2222287", "27283931"));
			c3.setUrlImage(null);
			contacts = Lists.newArrayList(c1, c2, c3);
		}
		return contacts;
	}

}

Una vez definidos estas clases, debemos pasar a la definición del fichero web.xml:

<?xml version="1.0" encoding="utf-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
	<servlet>
		<servlet-name>ExampleJSON</servlet-name>
		<servlet-class>example.json.ExampleJSONServlet</servlet-class>
	</servlet>

	<servlet>
        <servlet-name>jersey</servlet-name>
        <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
        <init-param>
            <param-name>com.sun.jersey.config.property.packages</param-name>
            <param-value>example.json</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

	<servlet-mapping>
		<servlet-name>ExampleJSON</servlet-name>
		<url-pattern>/examplejson</url-pattern>
	</servlet-mapping>

	<servlet-mapping>
        <servlet-name>jersey</servlet-name>
        <url-pattern>/resources/*</url-pattern>
    </servlet-mapping>

	<welcome-file-list>
		<welcome-file>index.html</welcome-file>
	</welcome-file-list>
</web-app>

En este punto debemos definir el servlet de jersey, y lo mapeamos a la dirección /resources/

Y por último, definiremos una clase RESTApi en la que definirimos por medio de Jersey como y cuando devolver el objeto JSON:

package example.json;

import java.util.List;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;

@Path("api")
public class RESTApi {

	@GET
	@Path("json")
	@Produces("application/json")
	public List<Contact> json() {
		return ContactsDatastore.getContacts();
	}
}

Por medio de la primera anotación @Path definimos cual será el directorio raiz a partir del cual definiremos las acciones REST, y por medio de la segunda (para el método json) definimos en que lugar, del camino definido anteriormente, recogeremos el objeto JSON al hacer la petición GET. Con @Produces(“application/json”) conseguimos tranformar el “objeto java” en un “objeto json”.

Finalmente, tendremos disponible al ejecutar la applicación con Eclipse, o subirla a app engine, la dirección http://domain/resources/api/json para realizar una petición GET y obtener el objeto JSON.

En el próximo artículo, veremos como aprovecharnos de esto con una aplicación Android para poder ver los datos siempre que queramos en nuestro smartphone.