miércoles, 10 de julio de 2013

Referencia a la clase contenedora en una clase anónima

Al programar interfaces gráficas, es muy habitual el uso de clases anónimas para definir el listener de algún componente.

JButton button = new JButton("OK");
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e)
{
   System.out.println("Ok!");
}});      
 
Tampoco es raro que desde la clase anónima, queramos acceder a algún elemento de la clase contenedora. En ese caso, podemos vernos tentados de usar this; pero, obviamente, si estamos dentro de la clase anónima, this hará referencia a la instancia de esa clase, no a la de la contenedora.

En este caso podemos usar como alternativa ClaseContenedor.this, quedando algo así.

public class ClaseContenedora extends JPanel{
   int value = 0;

   public ClaseContenedora() {
      ...

      JButton button = new JButton("OK");
      button.addActionListener(new ActionListener() {
         public void actionPerformed(ActionEvent e)
         {
            ClaseContenedora.this.value++;
         }});    

      ...
   } 
}

domingo, 7 de julio de 2013

Un único Toast en Android

Los Toasts de Android son una opción muy socorrida cuando se quieren mostrar mensajes al usuario sin molestar demasiado. Por ejemplo, cada vez que el usuario modifica una opción de la aplicación.

Los Toast se pueden crear mediante la llamada:

Toast.makeText(context, context.getString(id), Toast.LENGTH_SHORT)

El último parámetro indica el tiempo durante el que el Toast será visible en pantalla, y generalmente se usan las contastes LENGTH_SHORT y LENGTH_LONG de la propia clase Toast para darle valor.

Hasta aquí todo muy bien, pero un problema habitual se produce cuando se quieren mostrar varios Toast de forma consecutiva. En ese caso el sistema encola los Toast, y cada uno se va mostrando cuando desaparece el anterior. Esto implica que, seguramente, el usuario esté viendo en pantalla un mensaje correspondiente a una acción que no es la última que ha realizado.

Ante esto, yo prefiero la opción de que cada nuevo Toast se presente inmediatamente en pantalla, haciendo desaparecer al anterior. Así, adaptamos la información mostrada al ritmo al que el usuario utiliza la aplicación.

Viendo el API de la clase Toast, parece que la opción más lógica para conseguir esto es utilizar el método cancel. Pero la verdad es que por lo que lo he podido probar, no siempre da el resultado esperado.

Así que la solución que me he planteado es hacer una especie de Singleton sobre los Toast. La idea es que sólo exista un Toast en mi aplicación; así, cada vez que tengo que mostrar un mensaje, en lugar de crear una nueva instancia, que acabaría encolada si había otras anteriores, cojo mi única instancia, le cambio el texto, y la vuelvo a mostrar.

El resultado es una clase tal que así.

public class Toasts {

private static Toast myToast = null;

public static void showToast(Context context, int id) {
if (myToast != null) {
myToast.setText(context.getString(id));
} else {
myToast = Toast.makeText(context, context.getString(id),
Toast.LENGTH_SHORT);
}
myToast.show();
}
}

Esta clase puedo usarla de forma sencilla desde cualquier actividad de mi aplicación, simplificando además la  llamada al necesitar sólo el contexto y el texto.

Toasts.showToast(this, R.string.text);

Una posible mejora sería crear un segundo método que permitiese especificar también la duración del Toast, pero eso ya depende de las necesidades de cada uno.

martes, 2 de julio de 2013

Android Tip: setVisibility y Animations

En los últimos días he tenido que lidiar con uno de esos problemas simplotes que te cuesta horas solucionar.

Tengo un RelativeLayout, invoco el método setVisibility(View.INVISIBLE) sobre él, y no desaparece.

En mi caso esto se debía a que había ejecutado previamente una animación sobre ese RelativeLayout y, al parecer, la visibilidad de una vista queda bloqueada hasta que las animaciones son limpiadas.

Con añadir una llamada a clearAnimation() sobre el RelativeLayout se soluciona el problema.

miércoles, 12 de junio de 2013

Key Adapter y JPanel

El método addKeyListener de la clase Component de Swing nos permite agregar oyentes de teclado a cualquier componente. Por tanto, si estamos creando un JPanel, perfectamente podríamos intentar hacer algo así:

public class MyPanel extends JPanel {

public MyPanel() {
MyKeyAdapter keyAdapter = new MyKeyAdapter();
this.addKeyListener(keyAdapter);
}
...
}

Siendo MyKeyAdapter algo de este estilo.

public MyKeyAdapter extends KeyAdapter {

public void onKeyPressed(KeyEvent event) {
...
}

...
}

Pero si probamos este código, nos encontramos que por más que aporreemos el teclado, los eventos no se lanzan. El motivo es simple. Los eventos de teclado se lanzan siempre sobre el elemento de la interfaz que tiene el foco, y los JPanel, por defecto, no son enfocables (focusables). Así que una solución rápida al problema sería hacer el JPanel focusable de la siguiente manera.

public MyPanel() {
this.setFocusable(true);

MyKeyAdapter keyAdapter = new MyKeyAdapter();
this.addKeyListener(keyAdapter);
}
...
}

Ahora, al hacer click sobre el panel, ganará el foco. Si queremos dárselo desde algún rincón de nuestro código, también podemos hacerlo con la llamada panel.requestFocus().

sábado, 1 de junio de 2013

Android Tip: NullPointerException en android.widget.ArrayAdapter.createViewFromResource(ArrayAdapter.java:355)

Este error me ha traído de cabeza más de una vez, y tanto su causa como su solución son muy simples. Seguramente tengas en tu aplicación un ListView con un ArrayAdapter, y le estés añadiendo un valor nulo.

En mi caso suelo usar esta solución para mostrar valores sacados de la base de datos como, por ejemplo, una lista de jugadores. Si hay algún registro con el valor vacío, se recuperará como null, y al agregarlo al ArrayAdapter provocará la excepción.

Basta con controlar los valores nulos con algo tan simple como esto:

if (player != null) {
     playersArrayAdapter.add(player);
}