Android >> Notificaciones en Android ( Parte 2 – Dialogs II )

android-dialogs-2

Vamos a ver en este tutorial los distintos mecanismos que nos proporciona Android para mostrar mensajes al usuario. Principalmente, estos mecanismo son tres:

En la primera parte de este tutorial vimos el mecanismo más sencillo: los mensajes Toast. En la segunda parte vamos a ver el uso de los cuadros de diálogos (Dialogs) en Android. En la primera parte sobre diálogos vimos como crear cuadros de diálogos y en la segunda parte vamos a centrarnos en diálogos con una función más específica. Vamos a hablar a continuación de tres tipos especiales de diálogos:

Vamos a empezar hablando sobre los dos primeros: los pickers. Un picker es un cuadro de diálogo que nos permite seleccionar una hora o una fecha. Proporciona un interfaz que nos permite seleccionar cada una de los elementos de una hora (hora, minutos, AM/PM) o de una fecha (día, mes, año). Usando un picker nos aseguramos que la hora o fecha introducida sea válida, en un formato correcto y ajustado a la zona geográfica del usuario.

pickers

Para implementar un picker para la hora o la fecha en Android se recomienda usar la clase DialogFragment, al igual que hacemos para crear diálogos básicos. En esta guía vamos a usar las clase DialogFragment proporcionada por la Librería de soporte de Android, la cual debes utilizar si usas una versión inferior a Android 3.0 (API 11) como versión mínima de tu aplicación:

import android.support.v4.app.DialogFragment;

Si vas a usar como versión mínima de tu aplicación la 11 o superior, deberás usar la versión por defecto de la clase DialogFragment:

import android.app.DialogFragment;

Creando un Time Picker

Para mostrar un TimePicker, necesitamos definir un fragment que extienda de DialogFragment y devuelva un TimePickerDialog en el método onCreateDialog(). Además debemos implementar el interfaz TimePickerDialog.OnTimeSetListener para capturar la hora seleccionada por el usuario:

TimePickerFragment.java

package com.amatellanes.android;

import java.util.Calendar;

import android.app.Dialog;
import android.app.TimePickerDialog;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.text.format.DateFormat;
import android.widget.TimePicker;

public class TimePickerFragment extends DialogFragment implements
		TimePickerDialog.OnTimeSetListener {

	@Override
	public Dialog onCreateDialog(Bundle savedInstanceState) {

		// Use the current time as the default values for the picker
		final Calendar c = Calendar.getInstance();
		int hour = c.get(Calendar.HOUR_OF_DAY);
		int minute = c.get(Calendar.MINUTE);

		// Create a new instance of TimePickerDialog and return it
		return new TimePickerDialog(getActivity(), this, hour, minute,
				DateFormat.is24HourFormat(getActivity()));
	}

	@Override
	public void onTimeSet(TimePicker view, int hourOfDay, int minute) {
		// Do something with the time chosen by the user
	}

}

Una vez definido nuestro diálogo, lo mostramos creando una instancia de la clase que acabamos de crear e invocar el método show(), al igual que hacemos con los diálogos básicos. Podemos definir en nuestro layout un botón que al hacer clic sobre él abra el picker:

 <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:clickable="true"
        android:onClick="showTimePickerDialog"
        android:text="Time Picker" />

Al hacer clic se invocará al método showTimePickerDialog() que será el encargado de mostrar el picker:

public class MainActivity extends FragmentActivity {
	// If you're using Android API 11 o lower, your Activity must extend FragmentActivity

	public void showTimePickerDialog(View v) {
		DialogFragment dialog = new TimePickerFragment();
		dialog.show(getSupportFragmentManager(), TAG);
	}

}

Lo primero que debemos de tener en cuenta es que, al estar usando la Librería de soporte de Android, nuestra actividad debe heredar de la clase FragmentActivity. En nuestro método, creamos el picker y lo mostramos usando una instancia de la clase FragmentManager que conseguimos con el método getSupportFragmentManager(). Si estamos usando Android 3.0 (API 11) o superior, deberemos usar el método getFragmentManager() ya que nuestra actividad heredará de la clase Activity.

dialog-picker-time

Creando un Date Picker

Crear un DatePickerDialog es similar a la creación de un TimePickerDialog. En esta ocasión el método onCreateDialog() devolverá un objeto de la clase DatePickerDialog. En esta ocasión debemos implementar el método DatePickerDialog.OnDateSetListener para capturar el momento en el que el usuario ha seleccionado una fecha. Al igual que al crear un TimePickerDialog asegúrate de tener la Librería de soporte de Android instalada:

DatePickerFragment.java

import java.util.Calendar;

import android.app.DatePickerDialog;
import android.app.Dialog;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.widget.DatePicker;

public class DatePickerFragment extends DialogFragment implements
		DatePickerDialog.OnDateSetListener {

	@Override
	public Dialog onCreateDialog(Bundle savedInstanceState) {
		// Use the current date as the default date in the picker
		final Calendar c = Calendar.getInstance();
		int year = c.get(Calendar.YEAR);
		int month = c.get(Calendar.MONTH);
		int day = c.get(Calendar.DAY_OF_MONTH);

		// Create a new instance of DatePickerDialog and return it
		return new DatePickerDialog(getActivity(), this, year, month, day);
	}

	public void onDateSet(DatePicker view, int year, int month, int day) {
		// Do something with the date chosen by the user
	}
}

Una vez definido nuestro diálogo, lo mostramos creando una instancia de la clase que acabamos de crear e invocar el método show(), al igual que hacemos con los diálogos básicos. Podemos definir en nuestro layout un botón que al hacer clic sobre él abra el picker:

   <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:clickable="true"
        android:onClick="showDatePickerDialog"
        android:text="Date Picker" />

Al hacer clic se invocará al método showDatePickerDialog() que será el encargado de mostrar el picker:

public class MainActivity extends FragmentActivity {
    // If you're using Android API 11 o lower, your Activity must extend FragmentActivity
 
	public void showDatePickerDialog(View v) {
		DialogFragment newFragment = new DatePickerFragment();
		newFragment.show(getSupportFragmentManager(), TAG);
	}
 
}

Lo primero que debemos de tener en cuenta es que, al estar usando la Librería de soporte de Android, nuestra actividad debe heredar de la clase FragmentActivity. En nuestro método, creamos el picker y lo mostramos usando una instancia de la clase FragmentManager que conseguimos con el método getSupportFragmentManager(). Si estamos usando Android 3.0 (API 11) o superior, deberemos usar el método getFragmentManager() ya que nuestra actividad heredará de la clase Activity.

dialog-picker-date

Creando un Progress Dialog

Como ultima parte de esta guía sobre diálogos en Android vamos a ver como crear un cuadro de diálogo para mostrar el progreso de alguna acción que estemos realizando en nuestra aplicación, como por ejemplo la descarga de información desde Internet. Para realizar este tipo de operación se hacen uso de hilos (threads), creamos un hilo nuevo y ejecutamos la tarea en él.

El uso de hilos en Android tiene un problema: no podemos acceder a la interfaz gráfica (UI) desde otro hilo que no sea el mismo que gestiona la UI, por lo que no podemos mostrar ningún cambio en el UI (mostrar un diálogo, modificar elementos de la vista, etc) desde cualquier hilo adicional si no usamos alguna de las alternativas que nos proporciona Android para tal fin, como por ejemplo el uso de la función runOnUiThread() o de la clase Handler.

En esta ocasión vamos a usar las tareas asíncronas que implementamos en Android con la clase AsyncTask. La clase AsyncTask nos permite trabajar de una manera limpia y sencilla con el hilo responsable del UI. Esta clase nos permite realizar tareas en segundo plano y mostrar los resultados en el hilo UI sin tener que crear hilos y/o handlers. El uso de la clase AsyncTask está recomendado para acciones que no tarden más de unos pocos segundos para evitar que el hilo se este ejecutando durante largos periodos de tiempo, en tal caso es recomendable el uso del paquete java.util.concurrent, que nos proporciona el interfaz Executor o las clases ThreadPoolExecutor y FutureTask.

Uso de la clase AsyncTask

Para hacer uso de la clase AsyncTask debemos crear una clase que herede de ella y sobrescribir al menos el método doInBackground(). Además debemos seleccionar los tres tipos que vamos a usar:

private class MyTask extends AsyncTask<Params, Progress, Result>{ ... }
  • Params: Tipo de los parámetros que pasaremos cuando la tarea comience su ejecución.
  • Progress: Tipo que se usará para actualizar el progreso de la tarea mientras se está ejecutando.
  • Result: Tipo del resultado obtenido cuando la tarea termina.

Veamos un ejemplo: Supongamos que vamos a descargar algún archivo de Internet, a la tarea que creemos, debemos pasar la dirección (URL), mientras se descarga el archivo se irá mostrando el porcentaje de la descarga (Float) y una vez descargado devolverá el número de bytes descargados (Integer). La definición de esta tarea sería la siguiente:

private class MyTask extends AsyncTask<URL, Float, Integer> { ... }

Si no queremos usar alguno de los tipos anteriores usaremos el tipo Void:

private class MyTask extends AsyncTask<Void, Void, Void> { ... }

Cuando ejecutamos una tarea asíncrona se ejecutarán cuatro métodos:

  1. onPreExecute() : Es invocado en el hilo UI antes de que la tarea sea ejecutada. Se suele usar este método para inicializar la tarea, por ejemplo incializando la barra de progreso que se va a mostrar.
  2. doInBackground(Params...) : Es invocado inmediatamente después de la finalización del método onPreExecute(). En este método se debe implementar la tarea que se va a llevar a cabo en segundo plano. Este método recibe los parámetros que se le pasan a la tarea y devuelve el resultado obtenido. Desde este método podemos invocar a otros métodos, como por ejemplo publishProgress(Progress...) para mostrar el progreso de la tarea.
  3. onProgressUpdate(Progress...) : Es invocado cuando se llama al método publishProgress(Progress...). Es invocado cuando se quiere mostrar el progreso de la tarea en el interfaz de usuario, por ejemplo, actualizando una barra de progreso o mostrando algún mensaje en algún campo de texto.
  4. onPostExecute(Result) : Es invocado cuando finaliza la tarea en segundo plano. El resultado obtenido en el método doInBackground(Params...) es pasado como parámetro.

Vamos a ver a continuación como crear una tarea asíncrona que muestre y actualice un diálogo de progreso Empezamos definiendo en nuestro layout un botón que inicie la tarea y muestre el diálogo de progreso:

<Button
	android:layout_width="wrap_content"
	android:layout_height="wrap_content"
	android:clickable="true"
	android:onClick="showProgressBar"
	android:text="Progress Bar" />

A continuación definimos la función showProgressBar() que va a inicializar el diálogo (ProgressDialog) y la tarea MyTask que va a ir actualizando la barra de progreso mostrada en dicho diálogo:

MainActivity.java

public class MainActivity extends FragmentActivity {

	private ProgressDialog progressDialog;

	private MyTask task;

	public void showProgressBar(View v) {

		progressDialog = new ProgressDialog(this);
		progressDialog.setMessage("Downloading...");
		progressDialog.setTitle("Progress");
		progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
		progressDialog.setCancelable(false);

		try {
			URL url = new URL(
					"http://developer.android.com/downloads/design/Android_Design_Icons_20120814.zip");
			task = new MyTask();
			task.execute(url);

		} catch (MalformedURLException e) {
			e.printStackTrace();
		}
	}

	private class MyTask extends AsyncTask<URL, Float, Integer> {

		@Override
		protected void onPreExecute() {
			progressDialog.setProgress(0);
			progressDialog.setMax(100);
			progressDialog.show();
		}

		@Override
		protected Integer doInBackground(URL... params) {
			for (int i = 0; i < SIZE_DOWNLOAD; i++) {
				try {
					Thread.sleep(200);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				publishProgress(i / (float) SIZE_DOWNLOAD);
			}

			return SIZE_DOWNLOAD;
		}

		protected void onProgressUpdate(Float... progress) {
			int p = Math.round(100 * progress[0]);
			progressDialog.setProgress(p);
		}

		protected void onPostExecute(Integer result) {
			progressDialog.dismiss();
		}
	}
}

En el método showProgressBar() creamos el diálogo de progreso. La clase ProgressDialog proporciona una serie de setters para modificar varios parámetros y la apariencia del diálogo. En esta ocasión hemos usado los métodos setTitle() y setMessage() para modificar el título y el mensaje del diálogo. Es imprescindible definir el método setProgressStyle() indicando el valor ProgressDialog.STYLE_HORIZONTAL para que se muestre el progreso de la tarea como una barra. El otro valor posible es ProgressDialog.STYLE_SPINNER, lo que hará que el progreso de la tarea se muestre como un spinner de carga. Además, hemos usado el método setCancelable() para que el diálogo no se pueda cerrar el diálogo.

Para ejecutar la tarea se crea un nuevo objeto de la clase MyTask que hemos definido y se invoca al método execute() indicando los parámetros de entrada de la tarea. Dentro de la tarea definimos los métodos que hemos comentado anteriormente:

Cancelando la tarea

Si queremos cancelar una tarea que se está ejecutando en segundo plano usamos el método cancel(). Este método provocará la llamada del método onCancelled() en vez del método onPostExecute() cuando el método doInBackground() haya acabado. Para asegurarnos que la tarea se cancele tan pronto como sea posible, debemos comprobar el valor de retorno del método isCancelled() regularmente en el método doInBackground().

Vamos a empezar añadiendo un botón en el diálogo que cancele la tarea, para ello tenemos que utilizar el comando setButton() para añadir un botón al diálogo, y a continuación implementar un listener para que al hacer clic sobre el botón se cancele la tarea:

progressDialog.setButton(ProgressDialog.BUTTON_NEUTRAL, "Cancel",
		new OnClickListener() {

			@Override
			public void onClick(DialogInterface dialog, int which) {
				task.cancel(true);
			}
		});

Por último añadimos la comprobación con el método isCancelled() en el método doInBackground():

@Override
protected Integer doInBackground(URL... params) {
	for (int i = 0; i < SIZE_DOWNLOAD; i++) {
		try {
			Thread.sleep(200);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		publishProgress(i / (float) SIZE_DOWNLOAD);

		if (isCancelled())
			break;
	}

	return SIZE_DOWNLOAD;
}

dialog-progress

Más información | Pickers | Android Developers & Progress & Activity | Android Developers

Descargar código | GitHub