Android Aplicación completa Parte V: Lanzando nuevas activities con intents

En esta parte, veremos como lanzar una nueva Activity y cómo transferir datos de una Activity a otra.

Google ha publicado Guias de diseño de Task y Activities que describen los principios más importantes del framework de una aplicación Android, desde el alto nivel, perspectiva centrada en el usuario lo cual es útil para la interacción y los diseñadores de aplicaciones , así como para los programadores:

http://developer.android.com/guide/practices/ui_guidelines/activity_task_design.html

Deberíamos echar un vistazo a estas guias de diseño, pero por ahora solo comentaremos unas cuantas cosas sobre las Activities:

  • Las Activities son los bloques de construcción principales de las aplicaciones Android
  • Mientras el usuario se mueve por el interfaz, lo que hacen es iniciar Activities una tras otra
  • Cada activity tiene una ciclo de vida que es independiente de otras activities en su misma aplicación o tarea.
  • Android guarda un histórico de navegación lineal de activities que el usuario ha visitado (la pila de Activities)
  • Datos pueden ser intercambiados entre Activities utilizando la clase Intent.

Hasta ahora en esta serie de tutoriales, hemos realizado la búsqueda de películas dentro de la main activity y solo notificamos a los usuarios sobre los resultados encontrados utilizando Toasts. Ahora vamos a ir un paso más allá y vamos a utilizar una nueva Activity en la cual los resultados serán presentados como una lista. Por esta razón, la nueva Activity extenderá la clase ListActivity de Android, que es una clase especial de Activity que muestra una lista de campos enlazándose a un data source como puede ser un array o Cursor de un resultset.

Esta nueva activity será iniciada desde la activity principal por medio de un Intent. Utilizaremos el constructor Intent que se le pasa un Context y una clase target. En la documentación de referencia, dice que este constructor
proporciona una forma conveniente de crear un intent que se usará para ejecutar un nombre de clase hard-coded , en vez de que el sistema tenga que encontrar la clase apropiado por ti. Como sabemos cuál Activity queremos que maneje el Intent, este constructor nos sirve.

Para transferir datos entre dos activities, utilizaremos uno de los métodos sobrecargados de putExtra que ofrece la clase Intent. Los datos transferibles tienen que ser Serializable y como el Arraylist lo es por defecto, también tenemos que marcar nuestras clases de modelo (Movie/Person/Image) con el interface Serailizable. Finalmente, utilizaremos el método startActivity para lanzar la nueva activity.

Así, dentro de la clase “PerformMovieSearchTask” (que está definida como una inner class dentro de la activity principal) , utilizaremos el siguiente código para iniciar la nueva activity que manejará los resultados de la búsqueda:

...
Intent intent = new Intent(MovieSearchAppActivity.this, MoviesListActivity.class);
intent.putExtra("movies", result);
startActivity(intent);
...

No olvidemos marcar las clases de modelo como serializable:

...
public class Movie implements Serializable
...
public class Person implements Serializable
...
public class Image implements Serializable
...

1.- El nuevo activity

Para que el sistema encuentre la nueva activity, tenemos que declararla en nuestro fichero “AndroidManifest.xml”. Esto lo hacemos añadiendo la siguiente línea dentro del elemento “application”:

...
<activity android:name=".MoviesListActivity" />
...

La nueva ListActivity recuperará los datos del método onCreate. Para hacer esto, primero tomará una referencia de el intent que lanzó la actividad utilizando el método getIntent que retorna el intent original. Desde ahí , recuperamos los datos extendidos a través del método getSerializableExtra, donde utilizamos el nombre del ítem que fue previamente añadido con el método putExtra. Debido a que el método retorna un objeto Serializable, realizamos el casting necesario.

Para presentar los resultados de la lista necesitamos utilizar un ArrayAdapter, es decir un ListAdapter que maneja un ListView soportado por un array de objetos arbitrarios. El ArrayAdapter será inicializado con la lista de películas que viene del mani activity. Entonces utilizaremos el método setListAdapter para asociar nuestra listActivity con el ArrayAdapter.

También queremos que cada elemento de la lista sea clicable para que al pincharlos se abra el navegador de internet con el enlace correspondiente de la página IMDB para esa peli. Asi , sobreescribimos el método onListItemClick, el cual se llama cuando un ítem de la lista es seleccionado.Dentro de ese método, recuperamos el objeto Movie que es asociado con la lista de ítem utilizando el ArrayAdapter, encontramos el ID de IMDB y entonces construimos la URL que apunta al IMDB, que es al como esto:

Base URL: http://m.imdb.com/title/

Y detrás añadiremos el ID de la película correspondiente. Entonces, crearemos un nuevo Intent con el action set puesto a ACTION_VIEW.Este es el Activity Action que muestra datos al usuario. En caso de un URL, el navegador por defecto será iniciado y redirigido a la página target.La nueva activity que maneja el intent es iniciada con el método startActivity normal.

Veamos el código de la clase “MoviesListActivity”:

package com.javacodegeeks.android.apps.moviesearchapp;

import java.util.ArrayList;
import android.app.ListActivity;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;
import com.javacodegeeks.android.apps.moviesearchapp.model.Movie;

public class MoviesListActivity extends ListActivity {
	private static final String IMDB_BASE_URL = "http://m.imdb.com/title/";
	private ArrayList<Movie> moviesList;
	private ArrayAdapter<Movie> moviesAdapter;

	@SuppressWarnings("unchecked")
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.movies_layout);
		moviesList = (ArrayList<Movie>) getIntent().getSerializableExtra("movies");
		moviesAdapter = new ArrayAdapter<Movie>(this, android.R.layout.simple_list_item_1, moviesList);
		setListAdapter(moviesAdapter);
	}

	@Override
	protected void onListItemClick(ListView l, View v, int position, long id) {
		super.onListItemClick(l, v, position, id);
		Movie movie = moviesAdapter.getItem(position);
		String imdbId = movie.imdbId;
		if (imdbId==null || imdbId.length()==0) {
			longToast(getString(R.string.no_imdb_id_found));
			return;
		}
		String imdbUrl = IMDB_BASE_URL + movie.imdbId;
		Intent imdbIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(imdbUrl));
		startActivity(imdbIntent);
	}

	public void longToast(CharSequence message) {
		Toast.makeText(this, message, Toast.LENGTH_LONG).show();
	}

}

Observa que el ArrayAdapter utiliza el método toString para cada uno de los objetos encapsulados, así sobreescribiremos el método y proporcionamos una representación con más significado (o más presentable):

@Override
public String toString() {
	StringBuilder builder = new StringBuilder();
	builder.append("Movie [name=");
	builder.append(name);
	builder.append("]");
	return builder.toString();
}

2.-La vista del nuevo activity

El fichero layout que describe la vista para la nueva actividad necesita tener una declaración ListView con el id “@+id/android:list”. Además declaramos un TextView con id “@+id/android:empty” que aparecerá en el caso que la lista no incluya resultados. Estas vistas están incluidas dentro de un LinearLayout, que es un layout que coloca sus elementos hijos en una sola columna o en una sola fila. El fichero XML lo ponemos en la carpeta “res/layout”, o llamamos “movies_layout.xml” y tiene el siguiente contenido:

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

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

	<TextView
		android:id="@+id/android:empty"
		android:layout_width="fill_parent"
		android:layout_height="fill_parent"
		android:text="@string/no_movies_found"
	/>

</LinearLayout>

Ahora, ejecutamos el emulador , realizamos una búsqueda y esperamos los resultados.

Una vez recuperado, la clase “MoviesListActivity” se invoca y los resultados son mostrados en una lista (invocando el método toString como dijimos).

Puedes hacer scroll hacia abajo (esto lo maneja Android) y hacemos click en la película que deseas para encontrar más detalles. Esto disparará un evento que abrirá el navegador del dispositivo apuntando a la página web de la película en el IMDB: