Archivos para la Categoría 'Java ME'

Nueva API para interfaces de usuario JavaME: LWUIT

Una de las partes menos agradecidas de la programación en JavaME es el diseño de la interfaz de usuario. El paquete javax.microedition.lcdui de MIDP está orientado a la creacion de unas interfaces de usuario muy básicas, que se adecuan bien a las capacidades de los móviles de hace unos años pero que ahora mismo suponen una limitación para la potencia de cualquier dispositivo actual.

Como alternativa, Sun ha liberado una librería llamada LWUIT (Lightweight UI Toolkit), basada en Swing. Esta librería propociona un nuevo conjunto de componentes visuales, y ofrece la posibilidad de incorporar animaciones, temas y transiciones en nuestros programas JavaME.

En java.net podemos encontrar un tutorial para ir viendo el funcionamiento de esta API.

Vía: Lightweight UI Toolkit (LWUIT) for Java ME

Nueva versión de NetBeans Mobility: 6.1

No soy usuario de NetBeans, la principal alternativa a Eclipse para programar con Java ME, pero para quien sí lo sea aquí está la nueva versión: NetBeans IDE 6.1 Download.

Por lo poco que he visto, la principal novedad es el soporte SVG mejorado.

Vía: NetBeans Mobility 6.1 (final) is now available

Leyendo nuestro GPS desde Java con la JavaME Location API (JSR-179) - Parte II

Esta entrada completa la explicación del ejemplo tratado en la serie de entradas Un ejemplo de aplicación Java para BlackBerry. Ver enlaces al final.

Recapitulando

GPSDemo es una aplicación para BlackBerry que hace uso de la API JSR-179 para mostrar información de la posición y velocidad obtenidas de un dispositivo GPS o similar (ver la entrada Leyendo nuestro GPS desde Java con la JavaME Location API (JSR-179) - Parte I para una explicación de las posibles fuentes de información).

Habíamos dejado pendiente de describir el método startLocationUpdate() que se llama desde el constructor de la clase GPSDemo:

private void startLocationUpdate()
{
try
{
_locationProvider = LocationProvider.getInstance(null);
if ( _locationProvider == null )
{
Dialog.alert(”GPS is not supported on this platform, exiting…”);
System.exit(0);
}
_locationProvider.setLocationListener(new LocationListenerImpl(), _interval, 1, 1);
}
catch (LocationException le)
{
System.err.println(”Failed to add a location listener. Exiting…”);
System.err.println(le);
System.exit(0);
}
}

Lo primero que hacemos es obtener un proveedor de contenidos, con los criterios por defecto. En caso de no poder obtener uno, salimos de la aplicación.
La otra cosa que hace este método es establecer el objeto responsable de escuchar la información que llega del GPS. La variable _internal se estableció previamente al valor 1, con lo que la información se actualizará aproxamadamente una vez por segundo. Los dos últimos parámetros establecen el timeout y la caducidad de los datos.
Para actuar como listener del GPS una clase debe implementar la interfaz LocationListener.

La clase LocationListenerImpl

De forma más bien descriptiva, la clase listener de nuestro ejemplo se llama LocationListenerImpl:

private class LocationListenerImpl implements LocationListener
{
// Members. ————————————————————–
private int captureCount;

public void providerStateChanged(LocationProvider provider, int newState)
{
// No operation defined.
}

La interfaz tiene dos métodos. providerStateChanged nos avisa de los cambios de estado de nuestro proveedor de localización, básicamente cuando deja de estar disponible por algún tipo de problema; como estamos pensando sólo en el GPS, que no va a quedar fuera de servicio, no implementamos este método.
El otro método es el que nos avisa de que el GPS ha suministrado una nueva localización:

public void locationUpdated(LocationProvider provider, Location location)
{
if(location.isValid())
{
float heading = location.getCourse();
double longitude = location.getQualifiedCoordinates().getLongitude();
double latitude = location.getQualifiedCoordinates().getLatitude();
float altitude = location.getQualifiedCoordinates().getAltitude();
float speed = location.getSpeed();
// Horizontal distance.
float horizontalDistance = speed * _interval;
_horizontalDistance += horizontalDistance;
// Horizontal distance for this waypoint.
_wayHorizontalDistance += horizontalDistance;
// Distance over the current interval.
float totalDist = 0;
// Moving average grade.
for(int i = 0; i 0) _verticalDistance = _verticalDistance + altGain;
captureCount += _interval;
// If we’re mod zero then it’s time to record this data.
captureCount %= CAPTURE_INTERVAL;
// Information to display on the device.
StringBuffer sb = new StringBuffer();
sb.append(”Longitude: ” + longitude+ “\n”);
sb.append(”Latitude: ” + latitude+ “\n”);
sb.append(”Altitude: ” + altitude + ” m\n”);
sb.append(”Heading relative to true north: ” + heading + “\n”);
sb.append(”Speed : ” + speed + +” m/s\n”);
sb.append(”Grade : “);
if(Float.isNaN(grade))
sb.append(” Not available”);
else
sb.append(grade+” %”);
GPSDemo.this.updateLocationScreen(sb.toString());
}
}

Mucho código, pero muy simple. Se nos pasa como parámetros el LocationProvider, que no necesitamos, y un objeto Location que contiene, además de las coordenadas, la velocidad, la dirección y un método (isValid()) que nos dice si el objeto Location tiene coordenadas o no. En caso de utilizar como proveedor de contenidos un servicio de pago del operador, podríamos tener información adicional en el mismo objeto, como una dirección postal.

Código de GPSDemo.java

/**
* A GPS sample application using the JSR 179 APIs.
*
* Copyright (C) 2005 Research In Motion Limited.
*/
package com.rim.samples.docs.gpsdemo;
import net.rim.device.api.ui.*;
import net.rim.device.api.ui.component.*;
import net.rim.device.api.ui.container.*;
import com.rim.samples.docs.baseapp.*;
import net.rim.device.api.io.*;
import net.rim.device.api.system.*;
import net.rim.device.api.i18n.*;
import javax.microedition.io.*;
import java.util.*;
import java.io.*;
import javax.microedition.location.*;
import net.rim.device.api.util.*;
import com.rim.samples.docs.resource.*;
/* This application acts as a simple travel computer, recording route coordinates,
* speed, and altitude.
* Recording begins as soon as the application is invoked.
*/
public class GPSDemo extends BaseApp implements GPSDemoResResource
{
// Constants. —————————————————————-
// The number of updates in seconds over which the altitude is calculated.
private static final int GRADE_INTERVAL=5;
// com.rim.samples.docs.gpsdemo.GPSDemo.ID
private static final long ID = 0×4e94d9bc9c54fed3L;
private static final int CAPTURE_INTERVAL=10;
// Statics. ——————————————————————
private static ResourceBundle _resources =
ResourceBundle.getBundle(GPSDemoResResource.BUNDLE_ID, GPSDemoResResource.BUNDLE_NAME);
// The period of the position query in seconds.
private static int _interval = 1;
private static Vector _previousPoints;
private static float[] _altitudes;
private static float[] _horizontalDistances;
private static PersistentObject _store;
// Initialize or reload the persistent store.
static
{
_store = PersistentStore.getPersistentObject(ID);
if(_store.getContents()==null)
{
_previousPoints= new Vector();
_store.setContents(_previousPoints);
}
_previousPoints=(Vector)_store.getContents();
}
private long _startTime;
private float _wayHorizontalDistance;
private float _horizontalDistance;
private float _verticalDistance;
private ListField _listField;
private EditField _status;
private StringBuffer _messageString;
private String _oldmessageString;
private LocationProvider _locationProvider;
/* Instantiate the new application object and enter the event loop.
* @param args unsupported. no args are supported for this application
*/
public static void main(String[] args)
{
new GPSDemo().enterEventDispatcher();
}
// Constructors. ————————————————————-
public GPSDemo()
{
// Used by waypoints; represents the time since the last waypoint.
_startTime = System.currentTimeMillis();
_altitudes=new float[GRADE_INTERVAL];
_horizontalDistances=new float[GRADE_INTERVAL];
_messageString= new StringBuffer();
MainScreen screen = new MainScreen();
screen.setTitle(new LabelField(_resources.getString(GPSDEMO_TITLE), LabelField.USE_ALL_WIDTH));
_status = new EditField();
screen.add(_status);
screen.addKeyListener(this);
screen.addTrackwheelListener(this);
// Start the GPS thread that listens for updates.
startLocationUpdate();
// Render our screen.
pushScreen(screen);
}
/* Update the GUI with the data just received.
*/
private void updateLocationScreen(final String msg)
{
invokeLater(new Runnable()
{
public void run()
{
_status.setText(msg);
}
});
}
// Menu items. —————————————————————
// Cache the markwaypoint menu item for reuse.
private MenuItem _markWayPoint = new MenuItem(_resources, GPSDEMO_MENUITEM_MARKWAYPOINT, 110, 10)
{
public void run()
{
GPSDemo.this.markPoint();
}
};
// Cache the view waypoints menu item for reuse.
private MenuItem _viewWayPoints = new MenuItem(_resources, GPSDEMO_MENUITEM_VIEWWAYPOINTS, 110, 10)
{
public void run()
{
GPSDemo.this.viewPreviousPoints();
}
};
// Cache the close menu item for reuse.
private MenuItem _close = new MenuItem(_resources, GPSDEMO_MENUITEM_CLOSE, 110, 10)
{
public void run()
{
System.exit(0);
}
};
protected void makeMenu(Menu menu, int instance)
{
menu.add( _markWayPoint );
menu.add( _viewWayPoints );
menu.add( _close );
menu.addSeparator();
super.makeMenu(menu, instance);
}
/* Invokes the Location API with the default criteria.
*/
private void startLocationUpdate()
{
try
{
_locationProvider = LocationProvider.getInstance(null);
if ( _locationProvider == null )
{
Dialog.alert(”GPS is not supported on this platform, exiting…”);
System.exit(0);
}
// A single listener can be associated with a provider,
// and unsetting it involves the same call but with null,
// so there is no need to cache the listener instance.
// Request an update every second.
_locationProvider.setLocationListener(new LocationListenerImpl(), _interval, 1, 1);
}
catch (LocationException le)
{
System.err.println(”Failed to add a location listener. Exiting…”);
System.err.println(le);
System.exit(0);
}
}
/* Marks a point in the persistent store. Calculations are based on
* all data collected since the previous way point, or from the start
* of the application if no previous waypoints exist.
*/
private void markPoint()
{
long current = System.currentTimeMillis();
WayPoint p= new WayPoint(_startTime, current, _wayHorizontalDistance, _verticalDistance);
addWayPoint(p);
// Reset the waypoint variables.
_startTime = current;
_wayHorizontalDistance = 0;
_verticalDistance = 0;
}
// View the saved waypoints.
private void viewPreviousPoints()
{
PointScreen pointScreen = new PointScreen(_previousPoints, _resources);
pushScreen(pointScreen);
}
// Called by the framework when this application is losing focus.
protected void onExit()
{
if ( _locationProvider != null )
{
_locationProvider.reset();
_locationProvider.setLocationListener(null, -1, -1, -1);
}
}
/* Adds a new WayPoint and commits the set of saved waypoints
* to flash memory.
* @param p The point to add.
*/
/*package*/
synchronized static void addWayPoint(WayPoint p)
{
_previousPoints.addElement(p);
commit();
}
/* Removes a waypoint from the set of saved points and
* commits the modifed set to flash memory.
* @param p the point to remove
*/
/*package*/
synchronized static void removeWayPoint(WayPoint p)
{
_previousPoints.removeElement(p);
commit();
}
// Commit the waypoint set to flash memory.
private static void commit()
{
_store.setContents(_previousPoints);
_store.commit();
}
/* Implementation of the LocationListener interface.
*/
private class LocationListenerImpl implements LocationListener
{
// Members. ————————————————————–
private int captureCount;
// Methods. ————————————————————–
public void locationUpdated(LocationProvider provider, Location location)
{
if(location.isValid())
{
float heading = location.getCourse();
double longitude = location.getQualifiedCoordinates().getLongitude();
double latitude = location.getQualifiedCoordinates().getLatitude();
float altitude = location.getQualifiedCoordinates().getAltitude();
float speed = location.getSpeed();
// Horizontal distance.
float horizontalDistance = speed * _interval;
_horizontalDistance += horizontalDistance;
// Horizontal distance for this waypoint.
_wayHorizontalDistance += horizontalDistance;
// Distance over the current interval.
float totalDist = 0;
// Moving average grade.
for(int i = 0; i 0) _verticalDistance = _verticalDistance + altGain;
captureCount += _interval;
// If we’re mod zero then it’s time to record this data.
captureCount %= CAPTURE_INTERVAL;
// Information to display on the device.
StringBuffer sb = new StringBuffer();
sb.append(”Longitude: ” + longitude+ “\n”);
sb.append(”Latitude: ” + latitude+ “\n”);
sb.append(”Altitude: ” + altitude + ” m\n”);
sb.append(”Heading relative to true north: ” + heading + “\n”);
sb.append(”Speed : ” + speed + +” m/s\n”);
sb.append(”Grade : “);
if(Float.isNaN(grade))
sb.append(” Not available”);
else
sb.append(grade+” %”);
GPSDemo.this.updateLocationScreen(sb.toString());
}
}
public void providerStateChanged(LocationProvider provider, int newState)
{
// No operation defined.
}
}
/* WayPoint describes a way point, a marker on a journey or point of interest.
* WayPoints are persistable.
* package
*/
static class WayPoint implements Persistable
{
public long _startTime;
public long _endTime;
public float _distance;
public float _verticalDistance;
public WayPoint(long startTime,long endTime,float distance,float verticalDistance)
{
_startTime=startTime;
_endTime=endTime;
_distance=distance;
_verticalDistance=verticalDistance;
}
}
}

Entradas relacionadas:
Un ejemplo de aplicación Java para BlackBerry - Parte I
Un ejemplo de aplicación Java para BlackBerry - Parte II
Un ejemplo de aplicación Java para BlackBerry - Parte III
Un ejemplo de aplicación Java para BlackBerry - Parte IV y final
Leyendo nuestro GPS desde Java con la JavaME Location API (JSR-179) - Parte I

Tutoriales de Java ME en symbianrecources.com

Symbianresources.com ha actualizado su lista de tutoriales Java ME. Symbianresources.com es un sitio de la Universidad de Ciencias Aplicadas de Hagenbert, en Austria, y publica tutoriales y ejercicios sobre programación de sistemas Symbian, tanto en Java como en C++. El material está sacado de los cursos impartidos en dicha universidad.

Un ejemplo de aplicación Java para BlackBerry - Parte IV y final

Para la gestión de los waypoints vamos a crear una nueva ventana. Para ello crearemos una nueva clase en el archivo PointScreen.java.

La clase PointScreen

public class PointScreen extends MainScreen implements ListFieldCallback, GPSDemoResResource
{
private Vector _points;
private ListField _listField;
private ResourceBundle _resources;

PointScreen hereda de MainScreen, la clase que proporciona la funcionalidad estándar de las ventanas de las aplicaciones BlackBerry. La ventana contendrá una referencia al ResourceBundle para la internacionalización de los textos, un objeto Vector que almacenará los waypoints y un objeto ListField, que se encarga de mostrar en pantalla una lista vertical de items seleccionables. La clase PointScreen debe implementar ListFieldCallback para manejar los eventos del ListField.

public PointScreen(Vector points, ResourceBundle resources)
{
_resources = resources;
LabelField title = new LabelField(resources.getString(GPSDEMO_POINTSCREEN_TITLE),
LabelField.ELLIPSIS | LabelField.USE_ALL_WIDTH);
setTitle(title);
_points = points;
_listField = new ListField();
_listField.setCallback(this);
add(_listField);
reloadWayPointList();
}

private void reloadWayPointList()
{
// Refreshes wayPoint list on screen.
_listField.setSize(_points.size());
}

El constructor recibe un objeto Vector con los waypoints a mostrar, así como la referencia al ResourceBundle. Establece el título de la ventana (los parámetros del constructor del LabelField indican que se usa todo el ancho de la pantalla, y que si no hay sitio para el texto se acorte con puntos suspensivos), crea el objeto ListField y establece al objeto ventana como manejador de los eventos, añade el ListField a la ventana y llama al método reloadWayPointList().
Este método establece el número de objetos en el ListField, provocando que el mismo se redibuje.

La interfaz ListFieldCallback

public void drawListRow(ListField listField, Graphics graphics, int index, int y, int width)
{
if ( listField == _listField && index < _points.size())
{
String name = _resources.getString(GSPDEMO_POINTSCREEN_LISTFIELD_ROWPREFIX) + index;
graphics.drawText(name, 0, y, 0, width);
}
}

El método drawListRow se invoca cada vez que es necesario redibujar el una fila del control ListField. Tras cerciorarnos de que el control está creado y de que la fila a dibujar existe, nos limitamos a sacar el texto de ResourceBundle (en este caso el texto es “Waypoint“) y añadirle el índice que tiene en el Vector. Luego dibujamos el texto en su posición correspondiente.

public Object get(ListField listField, int index)
{
if ( listField == _listField )
{
// If index is out of bounds an exception is thrown,
// but that’s the desired behavior in this case.
return _points.elementAt(index);
}
return null;
}

El método get permite recuperar un objeto del ListField a partir de su índice. Está implementado en el ejemplo pese a que no se usa.

public int getPreferredWidth(ListField listField)
{
// Use the width of the current LCD.
return Graphics.getScreenWidth();
}

public int indexOfList(ListField listField, String prefix, int start)
{
return -1; // Not implemented.
}

Los otros dos métodos de la interfaz devuelven el ancho preferido del control (que será el ancho de la pantalla) y el índice del primer objeto que comience por el prefijo indicado (este método no está implementado).

El menú

Para el menú, se crea una clase para cada una de los dos opciones que queremos añadir:

private class ViewPointAction extends MenuItem
{
private int _index;
public ViewPointAction( int index )
{
super(PointScreen.this._resources.getString(GPSDEMO_POINTSCREEN_MENUITEM_VIEW), 100000, 10);
_index = index;
}
public void run()
{
ViewScreen screen = new ViewScreen( (WayPoint)_points.elementAt(_index), _index, _resources );
UiApplication.getUiApplication().pushModalScreen( screen );
}
}

private class DeletePointAction extends MenuItem
{
private int _index;
public DeletePointAction( int index )
{
super(PointScreen.this._resources.getString(GPSDEMO_POINTSCREEN_MENUITEM_DELETE), 100000, 10);
_index = index;
}

public void run()
{
GPSDemo.removeWayPoint((WayPoint)_points.elementAt(_index));
}
}

protected void makeMenu(Menu menu, int instance)
{
if( _points.size() > 0 )
{
ViewPointAction viewPointAction = new ViewPointAction( _listField.getSelectedIndex() );
menu.add( viewPointAction );
menu.addSeparator();
DeletePointAction deletePointAction = new DeletePointAction( _listField.getSelectedIndex() );
menu.add( deletePointAction );
}
super.makeMenu(menu, instance);
}

Las dos clases heredan de MenuItem e implementan el método run que se ejecutará al invocar la opción de menú correspondiente. El motivo para crear las clases es almacenar, en el momento de creación de las instancias, el índice del elemento del ListField seleccionado al invocar el menú. No es la forma más óptima pero sí la más explícita.
Cada vez que pulsamos el botón de menú para que este se despliegue, se invoca el método makeMenu que construye el mismo. En él creamos los objetos ViewPointAction y DeletePointAction que sirven de opciones de menú para ver y borrar los waypoints. Así, cada vez que invocamos el menú las opciones del mismo hacen referencia al waypoint seleccionado en el ListField.
La ejecución de DeletePointAction se limitá a eleminar el waypoint de la colección (lo hace el método removeWayPoint de la clase GPSDemo). ViewPointAction muestra la información del waypoint seleccionado.

La clase ViewScreen
La información del waypoint se muestra en un nuevo tipo de ventana, la cual se invoca de forma modal a través de pushModalScreen.

private static class ViewScreen extends MainScreen
{
private ResourceBundle _resources;
private MenuItem _cancel;

public ViewScreen(WayPoint point, int count, ResourceBundle resources)
{
super();
_resources = resources;
LabelField title = new LabelField(resources.getString(GPSDEMO_VIEWSCREEN_TITLE) + count,
LabelField.ELLIPSIS | LabelField.USE_ALL_WIDTH);
setTitle(title);
Date date = new Date(point._startTime);
String startTime = date.toString();
date = new Date(point._endTime);
String endTime = date.toString();
float avgSpeed = point._distance/(point._endTime - point._startTime);
add(new BasicEditField(resources.getString(GPSDEMO_VIEWSCREEN_STARTFIELD), startTime, 30, Field.READONLY));
add(new BasicEditField(resources.getString(GPSDEMO_VIEWSCREEN_ENDFIELD), endTime, 30, Field.READONLY));
add(new BasicEditField(resources.getString(GPSDEMO_VIEWSCREEN_HORIZONTALDISTANCEFIELD), Float.toString(point._distance), 30, Field.READONLY));
add(new BasicEditField(resources.getString(GPSDEMO_VIEWSCREEN_VERTICALDISTANCEFIELD), Float.toString(point._verticalDistance), 30, Field.READONLY));
add(new BasicEditField(resources.getString(GPSDEMO_VIEWSCREEN_AVESPEEDFIELD), Float.toString(avgSpeed), 30, Field.READONLY));
}

El constructor recibe el waypoint y el número que lo identifica (su índice en la colección), construye las fechas, calcula la velocidad y muestra los datos en controles BasicEditField.
El menú de esta ventana sólo tiene un elemento Cancel. En él se llama a popScreen con la instancia de la ventana, ya que al ser una ventana modal no puede permanecer en el stack.

El código fuente de PointScreen.java

/*
* PointScreen.java
*
* Copyright (C) 2005 Research In Motion Limited.
*/
package com.rim.samples.docs.gpsdemo;
import net.rim.device.api.ui.*;
import net.rim.device.api.ui.component.*;
import net.rim.device.api.ui.container.*;
import com.rim.samples.docs.baseapp.*;
import net.rim.device.api.io.*;
import net.rim.device.api.system.*;
import com.rim.samples.docs.resource.*;
import net.rim.device.api.i18n.*;
import javax.microedition.io.*;
import java.util.*;
import java.io.*;
import javax.microedition.location.*;
import com.rim.samples.docs.gpsdemo.GPSDemo.WayPoint;
import com.rim.samples.docs.resource.*;
/*
* PointScreen is a screen derivative that renders the saved WayPoints.
*/
public class PointScreen extends MainScreen implements ListFieldCallback, GPSDemoResResource
{
private Vector _points;
private ListField _listField;
private ResourceBundle _resources;

public PointScreen(Vector points, ResourceBundle resources)
{
_resources = resources;
LabelField title = new LabelField(resources.getString(GPSDEMO_POINTSCREEN_TITLE),
LabelField.ELLIPSIS | LabelField.USE_ALL_WIDTH);
setTitle(title);
_points = points;
_listField = new ListField();
_listField.setCallback(this);
add(_listField);
reloadWayPointList();
}

private void reloadWayPointList()
{
// Refreshes wayPoint list on screen.
_listField.setSize(_points.size());
}

// ListFieldCallback methods ------------------------------------------------
public void drawListRow(ListField listField, Graphics graphics, int index, int y, int width)
{
if ( listField == _listField && index < _points.size())
{
String name = _resources.getString(GSPDEMO_POINTSCREEN_LISTFIELD_ROWPREFIX) + index;
graphics.drawText(name, 0, y, 0, width);
}
}

public Object get(ListField listField, int index)
{
if ( listField == _listField )
{
// If index is out of bounds an exception is thrown,
// but that’s the desired behavior in this case.
return _points.elementAt(index);
}
return null;
}

public int getPreferredWidth(ListField listField)
{
// Use the width of the current LCD.
return Graphics.getScreenWidth();
}

public int indexOfList(ListField listField, String prefix, int start)
{
return -1; // Not implemented.
}

// Menu items. ---------------------------------------------------------------
private class ViewPointAction extends MenuItem
{
private int _index;
public ViewPointAction( int index )
{
super(PointScreen.this._resources.getString(GPSDEMO_POINTSCREEN_MENUITEM_VIEW), 100000, 10);
_index = index;
}
public void run()
{
ViewScreen screen = new ViewScreen( (WayPoint)_points.elementAt(_index), _index, _resources );
UiApplication.getUiApplication().pushModalScreen( screen );
}
}

private class DeletePointAction extends MenuItem
{
private int _index;
public DeletePointAction( int index )
{
super(PointScreen.this._resources.getString(GPSDEMO_POINTSCREEN_MENUITEM_DELETE), 100000, 10);
_index = index;
}

public void run()
{
GPSDemo.removeWayPoint((WayPoint)_points.elementAt(_index));
}
}

protected void makeMenu(Menu menu, int instance)
{
if( _points.size() > 0 )
{
ViewPointAction viewPointAction = new ViewPointAction( _listField.getSelectedIndex() );
menu.add( viewPointAction );
menu.addSeparator();
DeletePointAction deletePointAction = new DeletePointAction( _listField.getSelectedIndex() );
menu.add( deletePointAction );
}
super.makeMenu(menu, instance);
}

/**
* Renders a particular Waypoint.
*/
private static class ViewScreen extends MainScreen
{
private ResourceBundle _resources;
private MenuItem _cancel;

public ViewScreen(WayPoint point, int count, ResourceBundle resources)
{
super();
_resources = resources;
LabelField title = new LabelField(resources.getString(GPSDEMO_VIEWSCREEN_TITLE) + count,
LabelField.ELLIPSIS | LabelField.USE_ALL_WIDTH);
setTitle(title);
Date date = new Date(point._startTime);
String startTime = date.toString();
date = new Date(point._endTime);
String endTime = date.toString();
float avgSpeed = point._distance/(point._endTime - point._startTime);
add(new BasicEditField(resources.getString(GPSDEMO_VIEWSCREEN_STARTFIELD), startTime, 30, Field.READONLY));
add(new BasicEditField(resources.getString(GPSDEMO_VIEWSCREEN_ENDFIELD), endTime, 30, Field.READONLY));
add(new BasicEditField(resources.getString(GPSDEMO_VIEWSCREEN_HORIZONTALDISTANCEFIELD), Float.toString(point._distance), 30, Field.READONLY));
add(new BasicEditField(resources.getString(GPSDEMO_VIEWSCREEN_VERTICALDISTANCEFIELD), Float.toString(point._verticalDistance), 30, Field.READONLY));
add(new BasicEditField(resources.getString(GPSDEMO_VIEWSCREEN_AVESPEEDFIELD), Float.toString(avgSpeed), 30, Field.READONLY));
}

private class CancelMenuItem extends MenuItem
{
public CancelMenuItem()
{
// Reuse an identical resource below.
super(ViewScreen.this._resources, GPSDEMO_OPTIONSSCREEN_MENUITEM_CANCEL, 300000, 10);
}
public void run()
{
UiApplication uiapp = UiApplication.getUiApplication();
uiapp.popScreen(ViewScreen.this);
}
};

protected void makeMenu( Menu menu, int instance )
{
if ( _cancel == null )
_cancel = new CancelMenuItem(); // Create on demand.
menu.add(_cancel);
super.makeMenu(menu, instance);
}
}
}

A partir de aquí, la descripción del código continúa en la serie Leyendo nuestro GPS desde Java con la JavaME Location API (JSR-179).

Entradas relacionadas:
Un ejemplo de aplicación Java para BlackBerry - Parte I
Un ejemplo de aplicación Java para BlackBerry - Parte II
Un ejemplo de aplicación Java para BlackBerry - Parte III
Leyendo nuestro GPS desde Java con la JavaME Location API (JSR-179) - Parte I

Un ejemplo de aplicación Java para BlackBerry - Parte III

Vames a ver ahora una descripción de la aplicación GPSDemo, que viene de ejemplo con la versión 4.1 del JDE de BlackBerry. Esta aplicación nos permite leer el GPS de nuestro dispositivo para mostrar en pantalla información sobre nuestra posición y velocidad, además de grabar durante nuestro recorrido una serie de waypoints que contienen la distancia recorrida, el tiempo y la velocidad media.
La aplicación GPSDemo consta de varias clases, agrupadas en dos archivos: GPSDemo.java y PointScreen.java. Comenzaremos por el archivo principal.

GPSDemo.java

La clase hereda de nuestra clase base BaseApp, e implementa la interfaz de los recursos (de la que ya hablamos en la entrada anterior):

public class GPSDemo extends BaseApp implements GPSDemoResResource
{

// Constants. —————————————————————-
// The number of updates in seconds over which the altitude is calculated.
private static final int GRADE_INTERVAL=5;
// com.rim.samples.docs.gpsdemo.GPSDemo.ID
private static final long ID = 0×4e94d9bc9c54fed3L;
private static final int CAPTURE_INTERVAL=10;
// Statics. ——————————————————————
private static ResourceBundle _resources =
ResourceBundle.getBundle(GPSDemoResResource.BUNDLE_ID, GPSDemoResResource.BUNDLE_NAME);
// The period of the position query in seconds.
private static int _interval = 1;
private static Vector _previousPoints;
private static float[] _altitudes;
private static float[] _horizontalDistances;

Obtenemos una referencia a los recursos e inicializamos varias variables que usaremos posteriormente. A continuación comprobamos si tenemos datos almacenados en el dispositivo:

private static PersistentObject _store;
// Initialize or reload the persistent store.
static
{
_store = PersistentStore.getPersistentObject(ID);
if(_store.getContents()==null)
{
_previousPoints= new Vector();
_store.setContents(_previousPoints);
}
_previousPoints=(Vector)_store.getContents();
}

El objeto PersistentStore nos permite persistir información en la BlackBerry a través de pares clave-valor; en este caso utilizamos como clave un ID definido por nostros. Usar este objeto nos obliga a firmar la aplicación para poder instalarla en el dispositivo. De la firma de aplicaciones BlackBerry hablaremos en el futuro.

Método main y constructor

private long _startTime;
private float _wayHorizontalDistance;
private float _horizontalDistance;
private float _verticalDistance;
private ListField _listField;
private EditField _status;
private StringBuffer _messageString;
private String _oldmessageString;
private LocationProvider _locationProvider;

/* Instantiate the new application object and enter the event loop.
* @param args unsupported. no args are supported for this application
*/
public static void main(String[] args)
{
new GPSDemo().enterEventDispatcher();
}

La aplicación se inicia en el método estático main, que sólo crea una instancia de GPSDemo y pone el thread a la espera de eventos mediante la llamada al método enterEventDispatcher()(que se hereda de BaseApp, quien lo hereda de UiApplication y este a su vez de Application, la clase base de las aplicaciones BlackBerry). Vayamos ahora con el constructor:

// Constructor
public GPSDemo()
{
// Used by waypoints; represents the time since the last waypoint.
_startTime = System.currentTimeMillis();
_altitudes=new float[GRADE_INTERVAL];
_horizontalDistances=new float[GRADE_INTERVAL];
_messageString= new StringBuffer();
MainScreen screen = new MainScreen();
screen.setTitle(new LabelField(_resources.getString(GPSDEMO_TITLE), LabelField.USE_ALL_WIDTH));
_status = new EditField();
screen.add(_status);
screen.addKeyListener(this);
screen.addTrackwheelListener(this);
// Start the GPS thread that listens for updates.
startLocationUpdate();
// Render our screen.
pushScreen(screen);
}

Se inicializan variables de instancia y se crea la pantalla principal, a la que añadimos un EditField, que es un campo de texto editable (si no decimos lo contrario en el constructor). Mediante las llamadas a screen.addKeyListener(this) y screen.addTrackwheelListener(this) le decimos a la pantalla que nosotros (GPSDemo) gestionaremos sus eventos de teclado y trackwheel, lo cual podemos hacer porque nuestra clase base BaseApp implementa las interfaces KeyListener y TrackwheelListener.
Finalmente, añadimos la pantalla a la pila de pantallas. La gestión de ventanas la realiza la clase UIApplication como una pila: cuando necesitamos una ventana la metemos en la pila (pushScreen) y cuando acabamos con ella la sacamos (popScreen). El método pushScreen provoca que se pinte la pantalla, así que es importante llamarlo al final, cuando la ventana ya está totalmente construida.

Podemos ver también en el constructor una llamada a startLocationUpdate(), pero dejaremos esa parte para más adelante.

El menú

Nuestra clase base implementaba una versión básica del método makeMenu(Menu menu, int instance). Sin embargo, nosotros vamos a sustituir (override) esta versión con nuestra propia implementación:

// Menu items.
// Cache the markwaypoint menu item for reuse.
private MenuItem _markWayPoint = new MenuItem(_resources, GPSDEMO_MENUITEM_MARKWAYPOINT, 110, 10)
{
public void run()
{
GPSDemo.this.markPoint();
}
};


// Cache the view waypoints menu item for reuse.
private MenuItem _viewWayPoints = new MenuItem(_resources, GPSDEMO_MENUITEM_VIEWWAYPOINTS, 110, 10)
{
public void run()
{
GPSDemo.this.viewPreviousPoints();
}
};

// Cache the close menu item for reuse.
private MenuItem _close = new MenuItem(_resources, GPSDEMO_MENUITEM_CLOSE, 110, 10)
{
public void run()
{
System.exit(0);
}
};

protected void makeMenu(Menu menu, int instance)
{
menu.add( _markWayPoint );
menu.add( _viewWayPoints );
menu.add( _close );
menu.addSeparator();
super.makeMenu(menu, instance);
}

Aparte de mostrar en la pantalla principal información de nuestra posición y velocidad, como veremos más adelante, GPSDemo nos permite crear y gestionar waypoints, puntos donde se almacena información sobre la distancia, el tiempo y la velocidad entre una posición y la anterior. Para ello añadimos una opción en el menú que nos permita marcar un waypoint y otra que nos permita acceder a la ventana de gestión de waypoints. Finalmente añadimos un separador y la opción de salir de la aplicación. Se llama después al método de la clase padre, que tenía el código que añadía menús contextuales en caso de hacer click en un control.

Marcar un waypoint
El método al que llama la opción de marcar un waypoint es el siguiente:

/* Marks a point in the persistent store. Calculations are based on
* all data collected since the previous way point, or from the start
* of the application if no previous waypoints exist.
*/
private void markPoint()
{
long current = System.currentTimeMillis();
WayPoint p= new WayPoint(_startTime, current, _wayHorizontalDistance, _verticalDistance);
addWayPoint(p);
// Reset the waypoint variables.
_startTime = current;
_wayHorizontalDistance = 0;
_verticalDistance = 0;
}

Se crea un objeto WayPoint con la hora en la que se grabó el waypoint anterior, la hora actual, y las distancias horizontal y vertical recorridas. La información de la distancia la establece el código que lee el GPS, que veremos más adelante. La clase WayPoint se define dentro de la propia clase GPSDemo:

/* WayPoint describes a way point, a marker on a journey or point of interest.
* WayPoints are persistable.
* package
*/
static class WayPoint implements Persistable
{
public long _startTime;
public long _endTime;
public float _distance;
public float _verticalDistance;


public WayPoint(long startTime,long endTime,float distance,float verticalDistance)
{
_startTime=startTime;
_endTime=endTime;
_distance=distance;
_verticalDistance=verticalDistance;
}
}

El wayPoint se añade al vector se declaró al principio de la clase y que se almacena en la flash de la BlackBerry:

/* Adds a new WayPoint and commits the set of saved waypoints
* to flash memory.
* @param p The point to add.
*/
/*package*/
synchronized static void addWayPoint(WayPoint p)
{
_previousPoints.addElement(p);
commit();
}
// Commit the waypoint set to flash memory.
private static void commit()
{
_store.setContents(_previousPoints);
_store.commit();
}

En la siguiente entrada hablaremos de la gestión de los waypoints y de la clase PointScreen.

Entradas relacionadas:
Un ejemplo de aplicación Java para BlackBerry - Parte I
Un ejemplo de aplicación Java para BlackBerry - Parte II

Un ejemplo de aplicación Java para BlackBerry - Parte II

Seguimos examinando la clase abstracta BaseApp.

Crear un menú

protected void makeMenu( Menu menu, int instance) {
Field focus = UiApplication.getUiApplication().getActiveScreen().getLeafFieldWithFocus();
if(focus != null) {
ContextMenu contextMenu = focus.getContextMenu();
if( !contextMenu.isEmpty()) {
menu.add(contextMenu);
menu.addSeparator();
}
}
menu.add(_closeItem);
}

La clase net.rim.device.api.ui.component.Menu representa un menú que se despliega desde la parte superior derecha de la pantalla. Está compuesto por objetos MenuItem, de los que ya hablamos en la entrada anterior.

El método makeMenu es invocado cuando se produce un evento que tenga que mostrar el menú, como pulsar el trackwheel. En la implementación de las interfaces veremos un ejemplo. Este método, a través del objeto aplicación (UiApplication.getUiApplication()), obtiene la pantalla activa (getActiveScreen()) y de ella el campo hoja que tiene el foco (getLeafFieldWithFocus()). Un campo hoja (Leaf Field) es aquel que no actúa de contenedor para otro campo.

Si este campo tiene un menú contextual se añade al menú de la pantalla (el objeto Menu, que puede tener ya otros elementos), y tras él un separador y el elemento _closeItem que vimos en la entrada anterior.

Implementación de interfaces

Ahora vamos a ver los métodos de las otras dos interfaces que implementa nuestra clase, KeyListener y TrackwheelListener.

/* Invoked when the trackwheel is clicked. */
public boolean trackwheelClick( int status, int time ) {
Menu menu = new Menu();
makeMenu( menu, 0);
menu.show();
return true;
}
/* Invoked when the trackwheel is released. */
public boolean trackwheelUnclick( int status, int time ) {
return false;
}
/* Invoked when the trackwheel is rolled. */
public boolean trackwheelRoll(int amount, int status, int time) {
return false;
}

No hacemos nada cuando se suelta el trackwheel o cuando se gira, pero cuando se pulsa creamos un menú, lo construimos y lo mostramos. Hacerlo en este evento es el comportamiento estándar en una BlackBerry.

public boolean keyChar(char key, int status, int time) {
/* Intercept the ESC key and exit the application. */
boolean retval = false;
switch (key) {
case Characters.ESCAPE:
onExit();
System.exit(0);
retval = true;
break;
}
return retval;
}
/* Implementation of KeyListener.keyDown(). */
public boolean keyDown(int keycode, int time) {
return false;
}
/* Implementation of KeyListener.keyRepeat(). */
public boolean keyRepeat(int keycode, int time) {
return false;
}
/* Implementation of KeyListener.keyStatus(). */
public boolean keyStatus(int keycode, int time) {
return false;
}
/* Implementation of KeyListener.keyUp(). */
public boolean keyUp(int keycode, int time) {
return false;
}

De los eventos de teclado (keyStatus se dispara cuando se pulsan Shift o Alt) sólo actuamos en keyChar, que se dispara cuando se genera un carácter. En concreto nos interesa comprobar si el carácter es un Escape. Si es así, terminamos la aplicación.

Listado completo de BaseApp

A continuación el listado completo de la clase BaseApp, que se utilizará en una próxima entrada para la clase GPSDemo.

/*
* BaseApp.java
* Copyright (C) 2001-2004 Research In Motion Limited. All rights reserved.
*/

package com.rim.samples.docs.baseapp;

import net.rim.device.api.i18n.*;
import net.rim.device.api.system.*;
import net.rim.device.api.ui.container.*;
import net.rim.device.api.ui.*;
import net.rim.device.api.ui.component.*;

public abstract class BaseApp extends UiApplication implements BaseAppResource, KeyListener, TrackwheelListener {
private MenuItem _closeItem;
private static ResourceBundle _resources =
ResourceBundle.getBundle(BUNDLE_ID, BUNDLE_NAME);
/* Constructor for the abstract base class. */
public BaseApp() {
_closeItem = new MenuItem(_resources, MENUITEM_CLOSE, 200000, 10) {
public void run() {
onExit();
System.exit(0);
}
};
}
/* Override this method to add custom menu items. */
protected void makeMenu( Menu menu, int instance) {
Field focus = UiApplication.getUiApplication().getActiveScreen().getLeafFieldWithFocus();
if(focus != null) {
ContextMenu contextMenu = focus.getContextMenu();
if( !contextMenu.isEmpty()) {
menu.add(contextMenu);
menu.addSeparator();
}
}
menu.add(_closeItem);
}
/* Invoked when the trackwheel is clicked. */
public boolean trackwheelClick( int status, int time ) {
Menu menu = new Menu();
makeMenu( menu, 0);
menu.show();
return true;
}
/* Invoked when the trackwheel is released. */
public boolean trackwheelUnclick( int status, int time ) {
return false;
}
/* Invoked when the trackwheel is rolled. */
public boolean trackwheelRoll(int amount, int status, int time) {
return false;
}
public boolean keyChar(char key, int status, int time) {
/* Intercept the ESC key and exit the application. */
boolean retval = false;
switch (key) {
case Characters.ESCAPE:
onExit();
System.exit(0);
retval = true;
break;
}
return retval;
}
/* Implementation of KeyListener.keyDown(). */
public boolean keyDown(int keycode, int time) {
return false;
}
/* Implementation of KeyListener.keyRepeat(). */
public boolean keyRepeat(int keycode, int time) {
return false;
}
/* Implementation of KeyListener.keyStatus(). */
public boolean keyStatus(int keycode, int time) {
return false;
}
/* Implementation of KeyListener.keyUp(). */
public boolean keyUp(int keycode, int time) {
return false;
}
protected abstract void onExit();
}

Entradas relacionadas:
Un ejemplo de aplicación Java para BlackBerry - Parte I

Un ejemplo de aplicación Java para BlackBerry - Parte I

Introducción

Vamos a hacer un repaso de la estructura y funcionamiento de una aplicación simple para BlackBerry. Para ello nos vamos a basar en un ejemplo de la BlackBerry Application Developer Guide (Vol. 2), en concreto el ejemplo GPSDemo, que utilizaremos posteriormente para enlazar con la serie de entradas sobre programación de GPS.

El código fuente de dicho ejemplo, así como el de los demás ejemplos de la guía, está disponible aquí: BlackBerry Application Developer Guide Samples.

La clase BaseApp

Para el desarrollo de la aplicación se define una clase abstracta, llamada BaseApp:

package com.rim.samples.docs.baseapp;
import net.rim.device.api.i18n.*;
import net.rim.device.api.system.*;
import net.rim.device.api.ui.container.*;
import net.rim.device.api.ui.*;
import net.rim.device.api.ui.component.*;

public abstract class BaseApp extends UiApplication implements BaseAppResource, KeyListener, TrackwheelListener {

La clase hereda de net.rim.device.api.ui.UiApplication, que es la clase de la que heredan las aplicaciones que tienen interfaz de usuario. Implementa las interfaces KeyListener y TrackwheelListener, que veremos más tarde, y BaseAppResource, una interfaz especial creada por los archivos de recursos, que se usa para internacionalizar las aplicaciones. Esta interfaz tiene un aspecto como este:

package com.rim.samples.docs.baseapp;

public interface BaseAppResource {
// Hash of: “com.rim.samples.docs.baseapp.BaseApp”.
long BUNDLE_ID = 0xb2bbab4764b0c17eL;
String BUNDLE_NAME = “com.rim.samples.docs.baseapp.BaseApp”;
int MENUITEM_CLOSE = 0;
}

Internacionalización de la aplicación

Siguiendo con la clase BaseApp:

...
private MenuItem _closeItem;
private static ResourceBundle _resources =
ResourceBundle.getBundle(BUNDLE_ID, BUNDLE_NAME);

Declaramos una variable para una de las opciones del menú, en concreto la opción Close, que cerrará la aplicación. _resources es una variable estática que nos dará acceso a los recursos definidos para la internacionalización de la aplicación. Esta internacionalización consiste, básicamente, en un archivo .rrh donde definimos los recursos (cadenas de texto) que queremos internacionalizar, y un archivo .rrc por idioma donde mostramos el texto para el idioma correspondiente.

En nuestro caso, sólo vamos a hacer una versión inglesa, pero dejaremos abierta la opción a una futura internacionalización. Definimos un archivo con los recursos a internacionalizar (en este caso, sólo el texto del menú Close) en el archivo BaseApp.rhh:

package com.rim.samples.docs.baseapp;
MENUITEM_CLOSE#0=0;

Y definimos el texto en el archivo BaseApp.rrc:

MENUITEM_CLOSE#0="Close";

El entorno de desarrollo JDE nos proporciona una edición visual de estos archivos. Podemos encontrar una explicación detallada en la guía de desarrollo.

Constructor

public BaseApp() {
_closeItem = new MenuItem(_resources, MENUITEM_CLOSE, 200000, 10) {
public void run() {
onExit();
System.exit(0);
}
};
}

Este constructor crea un objeto MenuItem en la variable anteriormente declarada. A través del objeto _recources y la etiqueta MENUITEM_CLOSE definimos el texto del menú. El tercer parámetro sirve para ordenar los elementos en el menú: cuanto más bajo el número más arriba en el menú. El número es muy elevado porque querremos que el elemento Close esté el último en el menú.

Para explicar el último parámetro y el código posterior, hay que tener en cuenta que la clase MenuItem implementa la interfaz Runnable. O más bien la declara, porque es una clase abstracta, y la implementación de Runnable debemos proporcionarla nosotros. Típicamente crearíamos una subclase que heredase de MenuItem e implementase el método run() de Runnable, pero en este caso hemos optado por una solución más simple: implementar run() en la creación del objeto.

Es decir, lo que hace el constructor es crear un punto de entrada para la activación del objeto MenuItem que acaba de crear. Como queremos que sea una opción de Close, lo que haremos será llamar a un método onExit() y luego System.exit(0). ¿Por qué la llada a onExit()? Porque estamos en una clase abstracta, así que definimos un método onExit() abstracto que obligue a cualquier subclase a implementarlo con el código que quiera ejecutar a la salida de la aplicación. Si luego no quiere salir con System.exit(0), ya lo hacemos nosotros.

Seguiremos hablando de la clase BaseApp en una próxima entrada.

Leyendo nuestro GPS desde Java con la JavaME Location API (JSR-179) - Parte I

Introducción

La especificación JSR-179 Location API for J2ME pone a nuestra disposición una serie de clases que permiten acceder desde Java a información relacionada con la posición y el movimiento de nuestro dispositivo.

Lo que vamos a ver es una descripción básica de estas clases. Para probarlo necesitaremos un móvil que implemente al especificación JSR-179, como la mayoría de los Nokia o BlackBerry modernos.

Hay que tener en cuenta que no es necesario que el móvil disponga de GPS. La Location API nos aisla del origen de los datos, que puede ser un GPS o puede ser un servicio de pago proporcionado por la operadora. Debido a esto la API nos ofrece una serie de parámetros para filtrar qué tipo de servicios nos interesan de aquellos disponibles.

Fuentes de localización

En lo referente a localización, existen habitualmente tres fuentes posibles de datos:

- GPS: nuestro móvil incorpora un GPS, o está conectado a uno (habitualmente a través de Bluetooth).

- Assisted GPS: además de un GPS, nuestro móvil tiene acceso a información de localización que evita que el GPS pase hasta varios minutos obteniendo esa información de los satélites antes de dar la primera posición. Para ello es necesario un servicio específico ofrecido por algunas operadoras, habitualmente de pago.

- Cell Id: el móvil no tiene acceso a un GPS, pero sí a un servicio que le proporciona información basada en el identificador de la célula telefónica en la que se encuentra. También suele ser un servicio de pago por parte de las operadoras.

La Location API está preparada para el dispositivo tenga acceso a todo tipo de Location Providers o Proveedores de Localización, que es como se denominan a estas fuentes de datos. Para poder seleccionar entre las opciones actuales y futuras existe una clase que nos permite definir una serie de criterios que deba cumplir el proveedor que nos interese.

Estableciendo criterios

La clase javax.microedition.location.Criteria proporciona funciones que nos van a permitir filtrar los proveedores de localización disponibles. Veamos qué podemos definir en esta clase:

  • La precisión horizontal y vertical.
    Se establece mediante los métodos setHorizontalAccuracy y setVerticalAccuracy. La precisión de los datos depende de su origen. Así, si indicamos una precisión de 50 metros excluiremos los datos provenientes de localización por celda telefónica, que suelen tener precisiones de cientos de metros, pero aceptaremos los datos del GPS, con precisiones típicas de pocos metros.
  • El permiso para obtener datos a cambio de un precio.
    Mediante el método setCostAllowed(false) (el valor por defecto es true) impedimos que nos cobren por obtener datos de localización. Esto excluye habitualmente el A-GPS y la localización por celda telefónica, que necesitan comunicación con la red telefónica para obtener la información, además de un posible sobrecoste por el servicio.
  • El nivel de consumo de batería.
    A través del método setPreferredPowerConsumption podemos indicar si permitiremos un nivel de consumo bajo, medio o alto. Hay que tener en cuenta que es el fabricante del dispositivo quien decide qué es alto, medio o bajo, pero probablemente el uso de un dispositivo GPS interno o bluetooth tendrá un consumo medio o alto.
  • Otros criterios.
    El tiempo de respuesta, la velocidad o información relativa a la dirección postal. Para una descripción más extensa, véase la referencia de la clase Criteria.

Accediendo al GPS

Una vez definidos los criterios, obtendremos una referencia al proveedor de contenidos. La clase LocationProvider implementa un método estático getInstance(Criteria criteria) que devuelve un proveedor que se adapte a los criterios que hemos indicado. Si ninguno de los proveedores disponibles encaja en los criterios indicados, el método devolverá null. En caso de que no exista ningún proveedor de localización disponible se lanzará una LocationException. Es posible llamar a getInstance pasando null como parámetro: de esta forma se especifican los parámetros menos restrictivos para la selección.

Utilizaremos la instancia obtenida de LocationProvider para obtener la posición mediante el método getLocation. En el podemos especificar un timeout, o tiempo que queremos que espere el sistema para obtener una posición. Una vez obtenida dispondremos de un objeto Location que contendrá, entre otras cosas, las coordenadas de nuestra ubicación.

Un ejemplo sencillo

Juntemos todo en un ejemplo muy sencillo, sólo útil para hacerse una idea del código descrito:

// Establecemos los criterios. Queremos sólo un GPS interno o Bluetooth
Criteria criteria = new Criteria();
criteria.setCostAllowed(false);
// Como el valor por defecto de los siguientes parámetros
// es NO_REQUIREMENT, las siguientes líneas no son necesarias
criteria.setHorizontalAccuracy(NO_REQUIREMENT);
criteria.setVerticalAccuracy(NO_REQUIREMENT);
criteria.setPreferredPowerConsumption(NO_REQUIREMENT);


// Aquí falta capturar la posible excepción y verificar que el
// valor devuelto no es null
LocationProvider lp = LocationProvider.getInstance(criteria);
// Con -1 indicamos el timeout por defecto
Location location = lp.getLocation(-1);

Coordinates coordinates = location.getQualifiedCoordinates();
System.out.println(”Latitud: ” + coordinates.getLatitude());
System.out.println(”Longitud: ” + coordinates.getLongitude());

Referencias:

Documentación on-line de la Location API

Consejos para programadores J2ME (y BlackBerry) - 3

ClassCastException o instanceof

Habitualmente se considera la instrucción instanceof como una forma muy poco óptima de comprobar si una clase pertenece un objeto. Sin embargo, en el portal de BlackBerry aconsejan utilizar instanceof en vez de esperar a capturar una ClassCastException (lo cual es lógico, ya que la excepción debería ser siempre más costosa), debido a ciertas optimizaciones que se realizan cuando se hace el cast justo después de la comprobación:

if(x instanceof String) {
(String)x.whatever();
} else {
// something else
}

Estas optimizaciones deberían evitarnos tener que buscar métodos alternativos, como llamadas a métodos virtuales. Son optimizaciones comunes en las JVM de J2SE, pero que no sé si se da en otras implementaciones de J2ME.

Programación orientada a objetos

Sin entrar en debates académicos sobre las ventajas e inconvenientes de la programación orientada a objetos, hay que ser consciente de que algunas de las técnicas de este paradigma son especialmente costosas para la máquina. Aunque programemos en Java, que es un lenguaje de clases y objetos, es conveniente ponerse a pensar de una forma más estructurada, buscando ahorrar recursos a costa de incurrir en algunas cosas que nos enseñaron a evitar cuando aprendimos POO. En concreto:

- Conviene minimizar el número de clases. Ya no se trata de tener una clase para cada entidad que hayamos detectado en el análisis. Sólo queremos las clases indispensables para dar un poco de sentido al código, aunque los métodos que contengan no guarden mucha relación entre sí. Este es el punto más controvertido, ya que podemos limitarnos a meter todo el código en una clase. Sin embargo no se trata de renunciar por completo al mecanismo de clases, tan solo de ser consciente de que estas tienen un coste importante.

- Como norma general, nada de getters y setters. La encapsulación está bien para los PCs, no para los disposivitos móviles. Unas variables miembro con ámbito protected o public funcionarán mejor. El principal motivo para usar getters y setter es proporcionar una interfaz pública estable que nos permita cambiar la implementación interna en el futuro. Salvo que queramos diseñar una librería pública o trabajemos en un proyecto realmente muy grande, no los necesitamos.

- Nada de crear interfaces. Están muy bien para llevar a cabo una librería extensible, pero como en el punto anterior, es muy probable que no las necesitemos. Y en cuanto a su uso, cuantos menos interfaces implemente nuestra aplicación, mejor. El ejemplo típico es la interfaz CommandListener: en vez de implementarla en cada pantalla, es más efectivo implementarla en una sola clase controladora.

- Nada de clases abstractas. Ver punto anterior.

Lógicamente ninguna de estas recomendaciones se puede tomar como dogma, pero son útiles para recordar que nuestra mentalidad de programador debe cambiar cuando nos enfrentemos a dispositivos móviles.

Entradas relacionadas:
Consejos para programadores J2ME (y BlackBerry) - 1
Consejos para programadores J2ME (y BlackBerry) - 2

Entradas siguientes »