Android: integrando Google Maps con el GPS

Luego del post anterior donde vimos como usar google maps en nuestra aplicación vamos a integrar el GPS haciendo que nuestro mapa nos muestre “nuestra” posición.

Escuchando al GPS

AndroidManifiest.xml

Primero debemos agregar los permisos para obtener la ubicación “fina”, es decir, mediante el GPS, para esto agregamos el permiso ACCESS_FINE_LOCATION debajo del permiso que ya tenemos.

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

MyGoogleMapsActivity

En la línea 16 estamos referenciando el objeto del mapa, en la 17 estamos referenciando al servicio de sistema de localización (LocationManager) y en la línea 18 estamos diciéndole al locationManager que escuche las actualizaciones del de localización producidas por el GPS con una instancia del listener MyLocationListener.

   1: package com.neluz.MyGoogleMaps;
   2:  
   3: import com.google.android.maps.MapActivity;
   4: import com.google.android.maps.MapView;
   5:  
   6: import android.content.Context;
   7: import android.location.LocationManager;
   8: import android.os.Bundle;
   9:  
  10: public class MyGoogleMapsActivity extends MapActivity {
  11:     @Override
  12:     public void onCreate(Bundle savedInstanceState) {
  13:         super.onCreate(savedInstanceState);
  14:         setContentView(R.layout.main);
  15:         
  16:         MapView mapView = (MapView) findViewById(R.id.mapview);
  17:         LocationManager locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
  18:         locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, 
  19:                 new MyLocationListener(mapView.getController(), locationManager, getBaseContext()));
  20:  
  21:         mapView.setBuiltInZoomControls(true);
  22:     }
  23:  
  24:     @Override
  25:     protected boolean isRouteDisplayed() {
  26:         return false;
  27:     }
  28: }

en la línea 21 estamos habilitando el control de zoom en el mapa, esto no es necesario para el uso de GPS, pero va mejorando nuestra aplicación.

MyLocationListener

Creamos la clase MyLocationListener implementando LocationListener y en el método onLocationChanged es donde vamos a posicionar el mapa en la nueva ubicación, para esto creamos el GeoPoint a partir de las coordenadas de la nueva localización (líneas 34 a 36) y le pedimos al controller del mapa que lo posicione en las nuevas coordenadas (línea 38). Luego hacemos zoom en esta ubicación (línea 39).

   1: package com.neluz.MyGoogleMaps;
   2:  
   3: import android.content.Context;
   4: import android.location.Location;
   5: import android.location.LocationListener;
   6: import android.location.LocationManager;
   7: import android.os.Bundle;
   8: import android.widget.Toast;
   9:  
  10: import com.google.android.maps.GeoPoint;
  11: import com.google.android.maps.MapController;
  12: import com.google.android.maps.MapView;
  13:  
  14: public class MyLocationListener implements LocationListener {
  15:     private Context context;
  16:     private LocationManager locationManager;
  17:     private MapController mapController;
  18:  
  19:     public MyLocationListener(MapController mapController, LocationManager locationManager, Context context)
  20:     {
  21:         this.mapController = mapController;
  22:         this.locationManager = locationManager;
  23:         this.context = context;
  24:     }
  25:     
  26:     @Override
  27:     public void onLocationChanged(Location loc) {
  28:         Toast.makeText(context, "onLocationChanged", Toast.LENGTH_SHORT).show();
  29:         
  30:         if (loc!=null) {
  31:             Toast.makeText(context, 
  32:                     "Location changed : Lat: " + loc.getLatitude() + " Lng: " + loc.getLongitude(), Toast.LENGTH_SHORT).show();
  33:             
  34:                 GeoPoint p = new GeoPoint(
  35:                         (int) (loc.getLatitude() * 1E6), 
  36:                         (int) (loc.getLongitude() * 1E6));
  37:  
  38:                 mapController.animateTo(p);
  39:                 mapController.setZoom(12);
  40:         }
  41:         else
  42:         {
  43:             Toast.makeText(context, "location changed to null value", Toast.LENGTH_SHORT).show();
  44:         }
  45:         
  46:         // por el emulador??
  47:         locationManager.removeUpdates(this);
  48:         locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, 
  49:                 new MyLocationListener(mapController, locationManager, context));
  50:     }
  51:  
  52:     @Override
  53:     public void onProviderDisabled(String arg0) {
  54:         Toast.makeText(context, "Provider Disabled", 
  55:                 Toast.LENGTH_SHORT).show();
  56:     }
  57:  
  58:     @Override
  59:     public void onProviderEnabled(String arg0) {
  60:         Toast.makeText(context, "Provider Enabled", 
  61:                 Toast.LENGTH_SHORT).show();
  62:  
  63:  
  64:     }
  65:  
  66:     @Override
  67:     public void onStatusChanged(String provider, int status, Bundle arg2) {
  68:         Toast.makeText(context, "Status on " + provider + " is " +  getStatusMessage(status), 
  69:                 Toast.LENGTH_SHORT).show();
  70:     }
  71:  
  72:     private String getStatusMessage(int status) {
  73:         // TODO Auto-generated method stub
  74:         if (status == 1)
  75:             return "contecting";
  76:         else if (status == 2)
  77:             return "conected";
  78:         return "unknown";
  79:     }
  80:  
  81: }

las líneas 47 a 49 no estoy seguro que sean necesarias, pero el emulador de posiciones no funciona sino reiniciamos el listener.

Simulando posiciones

Algo que probablemente queramos hacer es simular distintas posiciones de nuestro Android como si estuviésemos en esos lugares (o moviéndonos). Para esto me encontré con una limitación que les cuento a continuación y dos alternativas para solucionarlo.

Una limitación y dos solución

Una gran “limitación” en el “Emulator Console” ya sea el integrado con Eclipse como el que se ejecuta desde DDMS es que requieren que la configuración regional esté en formato inglés de estados unidos. Así que o bien cambian la configuración regional de sus PCs o bien usan la terminal telnet, yo me quedé con esta segunda opción (aunque probé y ambas funcionan).

Si ustedes desarrollan todo como está en este post y al enviarle coordenadas al Android, el gps cambia de estado pero nunca se ejecuta el evento onLocationChanged es por este problema.

Simulando posiciones con telnet

Ejecutamos el comando telnet localhost 5554, donde 5554 es el puerto del AVD (Android Virtual Devices) donde están emulando el Android. Luego, con el comando geo fix simulamos datos del GPS.

image
iniciamos telnet

image

image
Iniciamos el emulador

image

ejecutamos el comando:

geo fix -58.433533 -34.615127

image

Nuestra aplicación debe navegar hasta esta ubicación, en la ciudad de Buenos Aires.

Luego podemos ir a:

  • geo fix –69.18 –16.01 (Isla del Sol en Bolivia)
  • geo fix –3.59 37.18 (Granada en España)
  • y todo un planeta para navegar

Android: Aplicación con GoogleMaps

Ahora si, luego del post introductorio y el ejemplo de una caja de texto, un botón y un popup vamos a desarrollar algo mas interesante…
Vamos a trabajar en una aplicación sobre GoogleMaps y veremos que va saliendo…

Proyecto


image

Propiedades del proyecto 
Primero debemos crear un nuevo proyecto, similar a los proyectos que venimos creando, con el detalle que en Target elegimos “Google APIs”. para las demás propiedades, en mi caso elegí:
Proyect name: MyGoogleMaps
Application name: My Google Maps
Package: com.neluz.MyGoogleMaps
Activity: MyGoogleMapsActivity

image

El proyecto debería quedarnos algo así.

AndroidManifiest.xml

Como ya vimos, aquí configuramos parte de la aplicación, en este caso queremos decirle que vamos a usar la librería del mapa (línea 8) y que vamos a acceder a internet (línea 20), esto último va a ser necesario para conectarnos al servidor de Google Maps:
   1: <?xml version="1.0" encoding="utf-8"?>
   2: <manifest xmlns:android="http://schemas.android.com/apk/res/android"
   3:       package="com.neluz.MyGoogleMaps"
   4:       android:versionCode="1"
   5:       android:versionName="1.0">
   6:     <application android:icon="@drawable/icon" android:label="@string/app_name">
   7:     
   8:         <uses-library android:name="com.google.android.maps" />
   9:         
  10:         <activity android:name=".MyGoogleMapsActivity"
  11:                   android:label="@string/app_name">
  12:             <intent-filter>
  13:                 <action android:name="android.intent.action.MAIN" />
  14:                 <category android:name="android.intent.category.LAUNCHER" />
  15:             </intent-filter>
  16:         </activity>
  17:  
  18:     </application>
  19:  
  20:     <uses-permission android:name="android.permission.INTERNET" />
  21:     
  22: </manifest> 

Layout

Como ya vimos, el layout de nuestra aplicación la definimos en res/layout/main.xml. En esta oportunidad vamos a poner un mapa como único contenedor:
   1: <?xml version="1.0" encoding="utf-8"?>
   2: <com.google.android.maps.MapView
   3:     xmlns:android="http://schemas.android.com/apk/res/android"
   4:     android:id="@+id/mapview"
   5:     android:layout_width="fill_parent"
   6:     android:layout_height="fill_parent"
   7:     android:clickable="true"
   8:     android:apiKey="Your Maps API Key goes here"
   9: />
En api key donde dice “Your Maps API Key goes here” debemos indicar la llave que necesitaremos para conectarnos a Google Maps, si tenemos un problema con ella la aplicación iniciará pero quedará en blanco el mapa. Sobre este tema vamos a volver mas adelante en este post, primero quiero mostrarles como se ve la aplicación sin esta llave.

MyGoogleMapsActivity

Como también vimos en los post anteriores, la activity es la que contiene la lógica de nuestra UI, por lo tanto aquí vamos a extender la clase com.google.android.maps.MapActivity:
   1: package com.neluz.MyGoogleMaps;
   2:  
   3: import com.google.android.maps.MapActivity;
   4: import android.os.Bundle;
   5:  
   6: public class MyGoogleMapsActivity extends MapActivity {
   7:     /** Called when the activity is first created. */
   8:     @Override
   9:     public void onCreate(Bundle savedInstanceState) {
  10:         super.onCreate(savedInstanceState);
  11:         setContentView(R.layout.main);
  12:     }
  13:  
  14:     @Override
  15:     protected boolean isRouteDisplayed() {
  16:         return false;
  17:     }
  18: }
al extender esta MapActivity en lugar de Activity tenemos que implementar el método: isRouteDisplayed() que por ahora dejamos la implementación por defecto: return false;

Ejecutando sin la llave

Si en este momento ejecutamos la aplicación, deberíamos obtener algo como la imagen de la derecha, es decir, el mapa en blanco. image

Obtener la llave

Primero vamos a ejecutar el comando keytool que podemos encontrar en el JDK de java de la siguiente manera para obtener el fingerprint:
Windows: C:\Documents and Settings\<user>\.android>keytool -list -alias androiddebugkey -keystore debug.keystore -storepass android -keypass android

Linux: ~/.android$ keytool -list -alias androiddebugkey -keystore debug.keystore -storepass android -keypass android
luego nos dirigimos a Sign Up for the Android Maps API para generar la llave (api key), leemos y aceptamos los términos y condiciones, ingresamos el fingerprint obtenido y presionamos Generarte API Key. Es muy importante destacar que en este caso estamos trabajando con una key que nos va a servir para desarrollo, cuando queramos hacer un release de nuestra aplicación deberemos obtener una key para tal fin. Esta key que obtuvimos la debemos colocar en el archivo res/layout/main.xml donde dice: android:apiKey en lugar de “Your Maps API Key goes here”.

image Con esto, nuestra aplicación debería ya conectarse al servidor de google y bajar los mapas, luciendo como la imagen de la derecha.

Referencias

Google Map View

Android: una caja de texto, un botón y un popup

Siguiendo con esta serie de post sobre Android les voy a compartir el siguiente: una aplicación que tiene un textbox donde se ingresa un texto, un botón “Mostrar” que muestra el texto ingresado en un popup que dura unos segundos.

Layout

En el archivo res/layout/main.xml definimos el layout de nuestra UI, en este caso vamos a definir el área de trabajo: LinearLayout, una caja de texto: EditText y un botón: Button.

   1: <?xml version="1.0" encoding="utf-8"?>
   2: <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
   3:     android:orientation="vertical"
   4:     android:layout_width="fill_parent"
   5:     android:layout_height="fill_parent">
   6:         
   7:     <EditText
   8:         android:id="@+id/txt_message"  
   9:         android:layout_width="fill_parent" 
  10:         android:layout_height="wrap_content" />
  11:     
  12:     <Button 
  13:         android:id="@+id/btn_showMessage"  
  14:         android:layout_width="fill_parent" 
  15:         android:layout_height="wrap_content"
  16:         android:text="@string/btn_showMessage" />
  17:  
  18: </LinearLayout>

Activity

Aquí vamos a definir el comportamiento de nuestra UI, en el evento onCreate de nuestra activity vamos a capturar las instancias del textbox y del botón por su Id en dos variables de instancia de la actividad, luego vamos a setear el listener para el OnClick del botón con un código que toma el texto del textbox y lo muestra como un popup por unos segundos mediante la clase Toast

   1: package com.neluz.MyUI;
   2:  
   3: import android.app.Activity;
   4: import android.os.Bundle;
   5: import android.view.View;
   6: import android.widget.Button;
   7: import android.widget.EditText;
   8: import android.widget.Toast;
   9:  
  10: public class MyActivity extends Activity {
  11:  
  12:     private EditText txtMessage;
  13:     private Button btnShowMessage;
  14:     
  15:     @Override
  16:     public void onCreate(Bundle savedInstanceState) {
  17:         super.onCreate(savedInstanceState);
  18:         setContentView(R.layout.main);
  19:         
  20:         txtMessage = (EditText) findViewById(R.id.txt_message);  
  21:         btnShowMessage = (Button) findViewById(R.id.btn_showMessage);
  22:  
  23:         btnShowMessage.setOnClickListener(new View.OnClickListener() {
  24:             public void onClick(View v) {
  25:                 String str = txtMessage.getText().toString() + "!";
  26:                 Toast.makeText(getBaseContext(), str, Toast.LENGTH_LONG).show();
  27:             }
  28:         });      
  29:     
  30:     }
  31: }

Si investigamos un poco, vamos a encontrar un archivo llamado R.java en la carpeta “gen” que contiene la relación entre los Id reales que van a tener nuestros controles con los nombres que nosotros indicamos que tengan. Esto nos relaciona el código de la activity con el layout.

AndroidManifest.xml

El manifiesto de nuestra aplicación debe lucir algo así como:

   1: <?xml version="1.0" encoding="utf-8"?>
   2: <manifest xmlns:android="http://schemas.android.com/apk/res/android"
   3:       package="com.neluz.MyUI"
   4:       android:versionCode="1"
   5:       android:versionName="1.0">
   6:     <application android:icon="@drawable/icon" android:label="@string/app_name">
   7:  
   8:  
   9:     <activity android:label="@string/app_name" android:name=".MyActivity">
  10:         <intent-filter>
  11:             <action android:name="android.intent.action.MAIN" />
  12:             <category android:name="android.intent.category.LAUNCHER" />
  13:         </intent-filter>
  14:     </activity>
  15: </application>
  16:  
  17:  
  18: <uses-sdk android:minSdkVersion="3"></uses-sdk>
  19: </manifest> 

las líneas mas relevantes son de la 9 a la 15 donde definimos nuestra actividad, la acción y su categoría.

strings.xml

en este archivo definimos las cadenas de texto que vamos a usar en otras partes de la aplicación como puede ser el manifiesto, el layout, etc.

   1: <?xml version="1.0" encoding="utf-8"?>
   2: <resources>
   3:     <string name="app_name">MyPopup</string>
   4:     <string name="btn_showMessage">Mostrar</string>
   5: </resources>

Resultado

image Una aplicación con una caja de texto y un botón que muestra el valor ingresado en la caja de texto al ser presionado.