Páginas

26 dic. 2012

Crear aplicaciones Multiidioma para Android

En los dos últimos artículos intenté mostrar todo el proceso de creación de una aplicación y, sobretodo, cómo dar los primeros pasos desarrollando aplicaciones de mapas utilizando Google Maps Android API v2. En ésta ocasión y a raiz de que creo que la aplicación que desarrollé a modo de ejemplo podría tener algo de tirón dentro de toda la comunidad de musulmanes a nivel internacional y no sólo entre los musulmanes hispanohablantes como pensé en un principio he decidido hacer multi-idioma dicha aplicación para que cualquier musulman del planeta la pueda utilizar.

Lo primero que voy a hacer va a ser cambiarla de nombre para que tenga un nombre más internacional. Me he preguntado cuál es el idioma que más musulmanes hablan y buscando un poco he encontrado ésta pregunta y una buena respuesta dice:
El mayor número de musulmanes del mundo hablan Filipino, Indú, Arabe y Francés, ya que en cuanto a números la mayor cantidad de Musulmanes están en el sudeste de Asia, en la India, en la llamada "Media Luna" de Oriente Medio y en los países excolonias belgo/francesas de Africa.
Atendiendo a esa respuesta y haciendo varias búsquedas en Google barajo varios posibles nombres,  Mekah y Makkah, siendo esta segunda la palabra más internacional de todas para referirse a La Meca. Creo que la voy a cambiar de nombre a Makkah Compass.

Hay una manera fácil y recomendada para hacer aplicaciones multi-idioma para Android y consiste en crear una carpeta llamada "values-sufijo" que contenga el strings.xml que corresponda dentro, por ejemplo values-en tendrá en su interior el fichero con los textos en inglés. (podéis ver la documentación oficial aquí: http://developer.android.com/guide/topics/resources/localization.html )

He encontrado el listado de los ISO Country codes aquí:


Voy a empezar con:

AE UNITED ARAB EMIRATES
FR FRANCE
ID INDONESIA
IQ IRAQ officially assigned
IR IRAN, ISLAMIC REPUBLIC OF
PH PHILIPPINES
PK PAKISTAN
PS PALESTINIAN TERRITORY, OCCUPIED
IN INDIA

Éstos son los distintos ficheros strings.xml que acabo de generar:

values-ae/strings.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="intro_arabe">وأعرب عن وحدانية الله في الصلاة عقائديا المجتمع، يجب على جميع المسلمين مراقبة، والصلاة في نفس الاتجاه: نحو الكعبة، أول بيت الله.</string>
    <string name="intro"></string>
    <string name="lanzar_mapa">الى اين هو الكعبة؟</string>
    <string name="lanzar_configuracion_gps">GPS إعدادات</string>
    <string name="app_name">مكة البوصلة</string>

</resources>
values-es/strings.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="intro_arabe">وأعرب عن وحدانية الله في الصلاة عقائديا المجتمع، يجب على جميع المسلمين مراقبة، والصلاة في نفس الاتجاه: نحو الكعبة، أول بيت الله.</string>
    <string name="intro">La unicidad de Dios se expresa ritualmente en la oración comunitaria, que todos los musulmanes deben observar, rezando en la misma dirección: hacia la Kaaba, la primera casa de Dios.</string>
    <string name="lanzar_mapa">¿Hacia dónde está la Kabaa?</string>
    <string name="lanzar_configuracion_gps">Configuración GPS</string>
    <string name="app_name">Makkah Compass</string>

</resources>
values-fr/strings.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="intro_arabe">وأعرب عن وحدانية الله في الصلاة عقائديا المجتمع، يجب على جميع المسلمين مراقبة، والصلاة في نفس الاتجاه: نحو الكعبة، أول بيت الله.</string>
    <string name="intro">L\'unicité de Dieu est exprimé rituellement dans la prière communautaire, tous les musulmans doivent observer, en priant dans la même direction: vers la Kaaba, la première maison de Dieu.</string>
    <string name="lanzar_mapa">Où se trouve la Kaaba?</string>
    <string name="lanzar_configuracion_gps">Paramètres GPS</string>
    <string name="app_name">La Mecque boussole</string>

</resources>
values-id/strings.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="intro_arabe">وأعرب عن وحدانية الله في الصلاة عقائديا المجتمع، يجب على جميع المسلمين مراقبة، والصلاة في نفس الاتجاه: نحو الكعبة، أول بيت الله.</string>
    <string name="intro">Keesaan Tuhan diungkapkan dalam doa ritual komunitas, semua muslim harus mengamati, berdoa dalam arah yang sama: menuju Ka'bah, rumah Allah yang pertama.</string>
    <string name="lanzar_mapa">Kemanakah adalah Ka'bah?</string>
    <string name="lanzar_configuracion_gps">GPS Pengaturan</string>
    <string name="app_name">Mekkah kompas</string>

</resources>
values-in/strings.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="intro_arabe">وأعرب عن وحدانية الله في الصلاة عقائديا المجتمع، يجب على جميع المسلمين مراقبة، والصلاة في نفس الاتجاه: نحو الكعبة، أول بيت الله.</string>
    <string name="intro">परमेश्वर की एकता धार्मिक समुदाय प्रार्थना में व्यक्त किया है, सभी मुसलमानों का निरीक्षण, एक ही दिशा में प्रार्थना: Kaaba, भगवान के पहले घर की ओर.</string>
    <string name="lanzar_mapa">मक्का कहाँ है?</string>
    <string name="lanzar_configuracion_gps">जीपीएस सेटिंग्स</string>
    <string name="app_name">मक्का कम्पास</string>

</resources>
values-iq/strings.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="intro_arabe">وأعرب عن وحدانية الله في الصلاة عقائديا المجتمع، يجب على جميع المسلمين مراقبة، والصلاة في نفس الاتجاه: نحو الكعبة، أول بيت الله.</string>
    <string name="intro"></string>
    <string name="lanzar_mapa">الى اين هو الكعبة؟</string>
    <string name="lanzar_configuracion_gps">GPS إعدادات</string>
    <string name="app_name">مكة البوصلة</string>

</resources>
values-ir/strings.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="intro_arabe">وأعرب عن وحدانية الله في الصلاة عقائديا المجتمع، يجب على جميع المسلمين مراقبة، والصلاة في نفس الاتجاه: نحو الكعبة، أول بيت الله.</string>
    <string name="intro">یکتایی و یگانگی خداوند رسوم در نماز جامعه بیان می شود، همه مسلمانان باید رعایت، دعا در همان جهت به سمت کعبه، اولین خانه خدا.</string>
    <string name="lanzar_mapa">کجا کعبه است؟</string>
    <string name="lanzar_configuracion_gps">تنظیمات GPS</string>
    <string name="app_name">مکه قطب نما</string>

</resources>
values-ph/strings.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="intro_arabe">وأعرب عن وحدانية الله في الصلاة عقائديا المجتمع، يجب على جميع المسلمين مراقبة، والصلاة في نفس الاتجاه: نحو الكعبة، أول بيت الله.</string>
    <string name="intro">Ang kaisahan ng Diyos ay ipinahayag ritually sa komunidad panalangin, ang lahat ng Muslim ay dapat obserbahan, nagdarasal sa parehong direksyon: patungo sa Kaaba, ang unang bahay ng Diyos.</string>
    <string name="lanzar_mapa">Pasaan ang Kaaba?</string>
    <string name="lanzar_configuracion_gps">GPS Setting</string>
    <string name="app_name">Mecca compass</string>

</resources>
values-pk/strings.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="intro_arabe">وأعرب عن وحدانية الله في الصلاة عقائديا المجتمع، يجب على جميع المسلمين مراقبة، والصلاة في نفس الاتجاه: نحو الكعبة، أول بيت الله.</string>
    <string name="intro">کمیونٹی کی نماز میں خدا کی وحدانیت کا اظہار کیا ہے، تمام مسلمانوں کا مشاہدہ، ایک ہی سمت میں نماز بھی ضروری ہے: کعبہ، خدا کے پہلے گھر کی طرف.</string>
    <string name="lanzar_mapa">کعبہ کہاں ہے؟</string>
    <string name="lanzar_configuracion_gps">GPS ترتیبات</string>
    <string name="app_name">مکہ کمپاس</string>

</resources>
values-ps/strings.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>

    <string name="intro_arabe">وأعرب عن وحدانية الله في الصلاة عقائديا المجتمع، يجب على جميع المسلمين مراقبة، والصلاة في نفس الاتجاه: نحو الكعبة، أول بيت الله.</string>
    <string name="intro"></string>
    <string name="lanzar_mapa">الى اين هو الكعبة؟</string>
    <string name="lanzar_configuracion_gps">GPS إعدادات</string>
    <string name="app_name">مكة البوصلة</string>

</resources>


Ha sido fácil, he copiado los cinco textos y los he ido traduciendo a los distintos idiomas oficiales de cada país, quizá en una próxima versión suba más traducciones a más idiomas pero creo que con estos será suficiente por ahora. Si alguien encuentra alguna traducción mal hecha que por favor me corrija.

إذا وجدت أي عدم تطابق الترجمة يرجى تصحيح لي.
यदि आप पाते हैं किसी भी बेमेल अनुवाद मुझे सही कृपया.
Jika Anda menemukan terjemahan mismatch tolong benar saya.
Kung nalaman mo ang anumang Maling pagtutugma ng pagsasalin paki-tama sa akin.

اگر آپ کو تلاش کسی بھی بیمیل ترجمہ براہ مہربانی آپ کے وزٹرز کا ریکارڈ رکھا درست کریں.


 La aplicación la podréis descargar en muy poco tiempo, actualizaré ésta misma entrada con el enlace a la descarga. 

24 dic. 2012

Aplicaciones de Google Maps v2 desde cero (II)

Antes de ayer le dediqué la mañana a aprender lo básico de programación con el API de Google Maps para Android y de paso a escribir un breve artículo exponiendo sin tapujos mis cábalas, lo que me iba pasando por la cabeza... Estuvo entretenido pero, como digo, me llevó toda la mañana incrustar un mapa en mi aplicación. Es cierto que tuve que leer bastante y también es cierto que tuve distracciones ya que estuve leyendo las noticias, mirando el facebook y haciendo alguna "tarea del hogar" pero no fue sencillo del todo. En pocas palabras, el anterior artículo se resume en:
  1. Crear el proyecto y el repositorio para la gestión y el control de cambios.
  2. Darle forma al código para que sea fácil de mantener, robusto y versátil.
  3. Conseguir la key del Google Maps Android API.
  4. Incluir referencias y librerías (tuve que actualizar eclipse y varios plugins y eso también me llevó un rato largo, google-play-services_lib.jar, android-support-v4.jar) 
  5. Insertar el mapa como fragment (lo que me llevó a pasar de utilizar Activity a usar FragmentActivity)
Todas estas cábalas las podéis leer con mucho mas detalle en la parte primera de ésta serie de artículos sobre Google Maps Android API.

Hoy quiero continuar con ese proyecto, el objetivo sigue siendo el mismo, construir una aplicación robusta, versátil, mantenible y ampliable enfocada a la comunidad musulmana cuya principal característica tiene que ser que nos diga siempre en qué dirección se encuentra exactamente la ciudad santa de La Meca. La planificación inicial era:

1º Requisitos
2º Análisis
3º Creación del proyecto y repositorio
4º Inserción de mapa
5º Localizar y apuntar a La Meca
6º Detalles de usabilidad
7º Crear y firmar la applicación y subirla a +Google Play

En la primera parte llegué hasta el punto 4 y hoy me gustaría terminar y publicar una versión BETA de ésta aplicación. así que empecemos.

Localización

En la actividad que maneja el mapa necesitamos un objeto tipo GoogleMap para manejarlo par alo cual hay que hacer lo siguiente:
GoogleMap mapa = ((SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map)).getMap();
Tendremos que importar además com.google.android.gms.maps.GoogleMap y com.google.android.gms.maps.SupportMapFragment, cosa que no debería darnos problemas si tenemos bien enlazadas las librerías del API y con ésto obtenemos una instancia del mapa que hemos declarado en la vista así:
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/map"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        class="com.google.android.gms.maps.SupportMapFragment"/>
Se pueden hacer muchas cosas con ese mapa y recomiendo leer éste segundo artículo de sgoliver sobre mapas para ver como jugar con los estilos del mapa y con la cámara, habrá cosas de ese artículo que utilizaré más adelante... Pero lo que ahora quiero es centrar el mapa en mi posición.

Haciendo mapa.setMyLocationEnabled(true); nos aparecerá el famoso círculo azul que indica nuestra posición y el radio aproximado de precisión pero el mapa seguirá centrado en el punto 0,0 y con el zoom por defecto...

Después de darle muchas vueltas a todo el tema de la localización y haber evolucionado mucho el código de mi MapActivity creo que es mejor que lo exponga y lo desgrane aquí que ir explicando los pasos que he ido siguiendo porque he dado muchas vueltas sin resultados. Es cierto que el código es bastante sucio pero esto es porque he intentado con multitud de comentarios que el código sea auto-explicativo:
package com.jgc.lameca;

import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.LocationSource;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.MarkerOptions;
import com.google.android.gms.maps.model.PolylineOptions;

import android.graphics.Color;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;

public class MapActivity extends BaseActivity implements LocationListener, LocationSource, SensorEventListener {
 // El mapa
 private GoogleMap mMap;
 // Para escuchar los cambios de localización del usuario, 
 // si se mueve o si la localización es más precisa
 private OnLocationChangedListener mListener;
 // Para gestionar nosotros la localización y no el propio mapa y poder 
 // manejar eventos como onLocationChanged que de otra manera no podríamos gestionar
 private LocationManager locationManager;

 private boolean haveLocation = false;

 // Sensores para orientar el mapa de acuerdo a la brújula
 private SensorManager mSensorManager;
 private float[] mGravity;
 private float[] mGeomagnetic;

 // para controlar el intervalo de ejecución del evento de los sensores
 private long lastTimeCompassUpdated = 0;

 /** Called when the activity is first created. */
 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.map_layout);       

  // Instanciamos locationManager
  locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
  // Establecemos la precisión
  Criteria locationCriteria = new Criteria();
  locationCriteria.setAccuracy(Criteria.ACCURACY_FINE);
  // Y establecemos que se escuche al mejor proveedor de localización 
  // (o redes móviles o GPS o WiFi, dependerá del estado del dispositivo
  // y así nos despreocupamos nosotros)
  locationManager.requestLocationUpdates(locationManager.getBestProvider(locationCriteria, true), 1L, 2F, (LocationListener) this);

  initializeMap();       
 }

 private void initializeMap() {
  // Confirmamos que no se ha inicializado el mapa todavía
  if (mMap == null) 
  {
   // obtenemos el mapa de la vista
   mMap = ((SupportMapFragment) getSupportFragmentManager().findFragmentById(R.id.map)).getMap();

   // Registramos o establecemos ésta clase, MapActivity, como LocationSource 
   // del mapa para utilizar nuestro locationManager y el Listener ;)
   mMap.setLocationSource(this);

   // Inicializamos el mapa...
   if (mMap != null) 
   {
    setUpMap();     
   }
  }
 }

 private void setUpMap() 
 {
  // Nos localiza...
  mMap.setMyLocationEnabled(true);
  // Quitamos el botón de "mi posición"
  mMap.getUiSettings().setMyLocationButtonEnabled(false);
  // Pinta una marca en La Meca
  addLaMecaMarkOnMap();
  centerMapOnLaMeca();
 }

 private void addLaMecaMarkOnMap(){
  mMap.addMarker(new MarkerOptions()
  .position(new LatLng(Constants.laMecaLatitude, Constants.laMecaLongitude))
  .title("La Meca")
  .icon(BitmapDescriptorFactory.fromResource(R.drawable.icono_meca)));
 }

 public void centerMapOnLaMeca(){
  CameraPosition newCameraPosition = new CameraPosition.Builder()
  .target(new LatLng(Constants.laMecaLatitude, Constants.laMecaLatitude))      
  .zoom(1)                   
  .bearing(0)        
  .tilt(0)                  
  .build();  

  mMap.animateCamera(CameraUpdateFactory.newCameraPosition(newCameraPosition));
 }

 private void initialiceSensors() {
  mSensorManager = (SensorManager)getSystemService(SENSOR_SERVICE);
  // Vamos a usar el acelerómetro y el sensor magnético
  Sensor gsensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
  Sensor msensor = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);

  mSensorManager.registerListener(this, gsensor, SensorManager.SENSOR_DELAY_NORMAL); 
  mSensorManager.registerListener(this, msensor, SensorManager.SENSOR_DELAY_NORMAL);
 } 

 private void drawLineBetweenOurLocationAndLaMeca(){
  // Debe ejecutarse cuando sepamos nuestra localización
  // es decir, onLocationChanged
  Location myLocation = mMap.getMyLocation();
  LatLng myLatLng = new LatLng(myLocation.getLatitude(), myLocation.getLongitude());
  LatLng laMecaLatLng = new LatLng(Constants.laMecaLatitude, Constants.laMecaLongitude);

  // creamos un polyline (linea poligonal) con dos puntos
  PolylineOptions rectOptions = new PolylineOptions()
  .add(myLatLng) // nuestra posición
  .add(laMecaLatLng); // la posición de la meca

  // pintamos esa línea de color verde
  rectOptions.color(Color.GREEN);

  // Y la añadimos al mapa
  mMap.addPolyline(rectOptions); 
 }

 @Override
 public void onPause()
 {
  if(locationManager != null)
  {
   locationManager.removeUpdates((LocationListener) this);
  }

  super.onPause();
 }

 @Override
 public void onResume()
 {
  super.onResume();

  initializeMap();

  if(locationManager != null)
  {
   mMap.setMyLocationEnabled(true);
  }
 }


 public void activate(OnLocationChangedListener listener) 
 {
  mListener = listener;
 }

 public void deactivate() 
 {
  mListener = null;
 }

 public void onLocationChanged(Location location) 
 {
  if (!haveLocation){
   initialiceSensors();
   haveLocation = true;
  }
  mMap.clear();
  if( mListener != null )
  {
   addLaMecaMarkOnMap();

   mListener.onLocationChanged( location );
   CameraPosition newCameraPosition = new CameraPosition.Builder()
   .target(new LatLng(location.getLatitude(), location.getLongitude()))      // centra el mapa en nuestra posición
   .zoom(19)   // establece el zoom
   .bearing(0)        
   .tilt(0)                  
   .build();  

   //Move the camera to the user's location once it's available!
   mMap.animateCamera(CameraUpdateFactory.newCameraPosition(newCameraPosition)); 
   drawLineBetweenOurLocationAndLaMeca();

  }
 }

 public void onProviderDisabled(String provider) 
 {
  // cuando se deshabilita un probeedor de localización... prefiero no hacer nada
  // Toast.makeText(this, "provider disabled", Toast.LENGTH_SHORT).show();
 }

 public void onProviderEnabled(String provider) 
 {
  // cuando se habilita un probeedor de localización... prefiero no hacer nada
  // Toast.makeText(this, "provider enabled", Toast.LENGTH_SHORT).show();
 }

 public void onStatusChanged(String provider, int status, Bundle extras) 
 {
  // Cuando cambia el estado de la localización... prefiero no hacer nada
  // Toast.makeText(this, "status changed", Toast.LENGTH_SHORT).show();
 }

 public void onAccuracyChanged(Sensor sensor, int accuracy) {
  // cuando cambia la precisión...
 }


 public void onSensorChanged(SensorEvent event) {
  // quiero dejar un margen de 3 segundos al principio
  // para la animación hasta el punto donde se encuentra el usuario...
  if (lastTimeCompassUpdated == 0){
   lastTimeCompassUpdated = System.currentTimeMillis()+3000;
   return;
  }
  else if (System.currentTimeMillis()>lastTimeCompassUpdated ){
   if (mMap!=null){
    int matrix_size = 16;
    float[] R = new float[matrix_size];
    float[] outR = new float[matrix_size];

    if (event.accuracy == SensorManager.SENSOR_STATUS_UNRELIABLE)
     return;

    switch (event.sensor.getType()) {
    case Sensor.TYPE_MAGNETIC_FIELD:
     mGeomagnetic = event.values.clone();
     break;
    case Sensor.TYPE_ACCELEROMETER:
     mGravity = event.values.clone();
     break;
    }

    if (mGeomagnetic != null && mGravity != null) {
     if (SensorManager.getRotationMatrix(R, null, mGravity, mGeomagnetic)) {
      SensorManager.getOrientation(R, outR);
     }

     CameraPosition oldPosition = mMap.getCameraPosition();

     // Quiero que sólo se actualice si hay una variación de mas de un grado
     //if (Math.abs(Math.abs(oldPosition.bearing) - Math.abs(Math.toDegrees(outR[0])))>1){

     CameraPosition newCameraPosition = new CameraPosition.Builder()
     .target(oldPosition.target)      // Sets the center of the map to Mountain View
     .zoom(oldPosition.zoom)                   // Sets the zoom
     .bearing((float) Math.toDegrees(outR[0]))        
     .tilt(oldPosition.tilt)                 
     .build();                   // Creates a CameraPosition from the builder

     mMap.moveCamera(CameraUpdateFactory.newCameraPosition(newCameraPosition));
     
    }
   }
  }
 }
}
Si te fijas el código es muy lineal, hay una secuencia de acontecimientos. Se pinta el icono sobre la meca y se centra la cámara en ese punto y cuando el dispositivo da una localización (onLocationChanged) si es la primera localización se inicializan los sensores y se lanza una animación de la cámara hasta nuestra posición.

Durante mis pruebas y errores me he encontrado con que el giro (la animación o el movimiento) de la cámara de acuerdo a los sensores interrumpía las animaciones del mapa, es decir, el movimiento de la cámara de un punto a otro, si éste movimiento se hacía por código, es decir, si hacia un movimiento desde La Meca hasta mi localización y empezaban a funcionar los sensores (onSensorChanged) el ajuste de la orientación de la cámara en ese método paraba la animación así que decidí meter un pequeño retardo de tres segundos en el evento la primera vez e inicializar los sensores una vez tuviera una localización. Así he conseguido que, primero el mapa nos muestre La Meca y una vez sepamos donde estamos movernos "elegantemente" hasta nuestra posición y, tres segundos después, orientar nuestro mapa de acuerdo a la brújula para saber en qué dirección se encuentra exáctamente La Meca. 

Hay que tener en cuenta también que si no hay ningún proveedor de localización éste código fallará y se cerrará la aplicación, por tanto lo que debemos hacer es obligar a que haya un proveedor de localización activo antes de poder lanzar ésta actividad. Para ello, en EntryPointActivity, tengo el siguiente código:
import android.location.LocationManager;
import android.os.Bundle;
import android.widget.Button;

public class EntryPointActivity extends BaseActivity {
 /** Called when the activity is first created. */
 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);
 }
 
 @Override
 protected void onResume() {
  setMapButtonVisibility();
  super.onResume();
 }

 private void setMapButtonVisibility() {
  LocationManager locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);

  boolean network_enabled=locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER);
  boolean gps_enabled=locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
  Button mapButton = ((Button)this.findViewById(R.id.ep_map_button)); 
  if (!(network_enabled || gps_enabled)){
   
   mapButton.setEnabled(false);
  }
  else {
   mapButton.setEnabled(true);
  }
 }
}
Lo último que nos queda por hacer antes de poder firmar y subir a Google Play es pulir detalles de interfaz de usuario y de usabilidad... vamos con ello:

Interfaz de Usuario

Que no se crea nadie que me he complicado demasiado, he buscado un mensaje que invita a la oración, lo he traducido al árabe y he incluido los dos textos, he buscado alguna imagen bonita de la Kaaba y la he centrado y colocado como background del layout principal, también he centrado los botones y buscado una imagen pequeña de la Kaaba para utilizarla como icono de la aplicación. Así ha quedado el xml (main.xml) de EntryPoint:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:background="@drawable/centered"
    android:orientation="vertical" >

    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_margin="5dp"
        android:text="@string/intro_arabe" />

    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_margin="5dp"
        android:text="@string/intro" />

    <RelativeLayout
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_gravity="bottom|center_horizontal"
        android:layout_marginBottom="1dip"
        android:orientation="vertical" >

        <Button
            android:id="@+id/ep_gps_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:layout_centerHorizontal="true"
            android:layout_marginBottom="25dp"
            android:onClick="onClickFeature"
            android:text="@string/lanzar_configuracion_gps" />

        <Button
            android:id="@+id/ep_map_button"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_above="@+id/ep_gps_button"
            android:layout_centerHorizontal="true"
            android:layout_marginBottom="14dp"
            android:onClick="onClickFeature"
            android:text="@string/lanzar_mapa" />

     </RelativeLayout>

</LinearLayout>
Respecto al centrado de la imagen, he creado un fichero en /res/drawable llamado centered.xml así:
<?xml version="1.0" encoding="utf-8"?>
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
    android:gravity="center"
    android:src="@drawable/Kaaba_mirror_edit_jj" />

Esto hace que la imagen elegida quede centrada y no se deforme, ha quedado así a la primera y me ha gustado aunque quizá en móviles con la pantalla un poco más pequeña no quede del todo bien, ya me plantearé ese ajuste más adelante, de momento doy por terminado "el maquillaje" y voy a firmar la aplicación y a subirla a Google Play.

Para firmar la aplicación: Botón derecho sobre el proyecto -> Android Tools -> Export Signed Application Package... Pones tus credenciales, creas el apk, bas a https://play.google.com/apps/publish  y subes tu aplicación...

(siento haber resumido ésto tanto pero da para varias entradas y ya se ha escrito mucho sobre firmar aplicaciones y gestionar tus aplicaciones en Google Play, en cuanto esté activa pondré un enlace para que veáis el resultado)


Y como hoy es Noche Buena voy a ducharme y a vestirme para ir a cenar con mi familia, espero que éste artículo sirva como introducción a la programación de aplicaciones con Google Maps. En el próximo artículo me gustaría hacer una nueva versión de ésta aplicación donde aparezcan laas mezquitas más cercanas, tendré que buscar si existe esa información en algún sitio y hacer un poco de I+D, y eso será dentro de unos días...

¡Hasta la próxima entrada!

Juan García Carmona
d.jgc.it@gmail.com



21 dic. 2012

Aplicación de Google Maps Android API v2 desde cero

Hola a tod@s. En ésta entrada quiero compartir con los lectores el proceso de creación de una aplicación desde cero. Tengo como objetivo aprender a utilizar los mapas de +Google Maps en +Android y se me ha ocurrido que un buen comienzo puede ser la creación de una aplicación que nos diga en qué dirección se encuentra un punto concreto. Para éste ejemplo voy a hacer algo que espero sea útil a la comunidad muslumana hispano hablante puesto el punto concreto que he decidido utilizar en éste ejemplo es La Meca.

Según Wikipedia La Meca (en árabe مكة المكرمة Makkah al-Mukarrama o simplemente Makkah) es la principal ciudad de la región del Hiyaz, en la actual Arabia Saudita, y una de las más importantes de la península de Arabia. Está situada al oeste de la península y cuenta con 1.294.167 habitantes (censo de 2004), localizada en un estrecho valle, a 277 m sobre el nivel del mar; se ubica a 80 km del mar Rojo.

Como todos sabéis cada musulmán debe rezar cinco veces al día en dirección a La Meca y como supongo que no siempre será fácil saber en qué dirección mirar y menos si se viaja constantemente alrededor del mundo. 

Vamos a hacer una pequeña planificación inicial, a ver cuánto me acabo desviando, pero la planificación inicial podría ser la siguiente:

1º Requisitos
2º Análisis 
3º Creación del proyecto y repositorio
4º Inserción de mapa
5º Localizar y apuntar a La Meca
6º Detalles de usabilidad
7º Crear y firmar la applicación y subirla a +Google Play 

Siete pasos, podría valer, siete es un número que me gusta...

Requisitos

Queremos una aplicación mínima que nos diga, desde donde nos encontramos, hacia dónde o en qué dirección se encuentra La Meca.

Queremos además dejar la aplicación abierta a futuras mejoras o ampliaciones de funcionalidad

Análisis

El primer requisito, ya que es el objetivo, lo voy a atacar utilizando +Google Maps para Android, del que aún no sé nada y cuya investigación incluiré en éste artículo. También se podría utilizar una brújula hecha con Canvas pero lo que me importa o me gustaría conseguir es dar al usuario la dirección a la que debe mirar, bien pintando una linea recta sobre el mapa, bien ofreciéndole una brújula que apunte a La Meca.

Con respecto al segundo requisito seguiré las pautas del anterior artículo, Arquitectura de aplicaciones Android, ya que creo que siguiendo esas pautas me aseguro robustez, versatilidad y facilidad de mantenimiento. 

Creación del proyecto y repositorio

Repositorio

Tengo cuenta en GitHub y tengo pendiente mostrar cómo se gestiona un respositorio con GitHub pero para mi proyecto, por comodidad, voy a utilizar un repositorio local svn utilizando Tortoise SVN (como ésto carece de interés hoy aquí podemos decir que he seguido estos mismos pasos o muy parecidos para crear el respositorio, creo el repositorio, creo una carpeta para el proyecto, hago checkout sobre el repositorio  y ya tengo lista esa carpeta para trabajar sobre ella).

Ahora voy a crear un nuevo proyecto desde Eclipse que trabaje sobre la carpeta anterior, también lo podía haber hecho al revés y haber creado primero el proyecto y luego haber hecho checkout sobre el respositorio,, es lo mismo, mientras sepas lo que estás haciendo...

 Proyecto

Crear un proyecto de aplicación de Android no tiene ningún misterio si tienes el eclipse bien configurado. Abres eclipse, File -> New -> Android Project, elegimos un nombre para el proyecto, por ejemplo La_Meca, y seleccionamos una ubicación, nuestra carpeta de SVN, en mi caso D:\WORK\La Meca y pulsamos Next, después elegimos un SDK, en mi máquina tengo el 2.1, el 4.0 y el 4.3 y creo que voy a optar por el 2.1 ya que hay muchos dispositivos antiguos que aún utilizan esa versión de Android y como no voy a hacer grandes virguerías no creo que me haga falta nada de lo nuevo que hay en el 4.x... Selecciono entonces Google API 2.1 y pulso Next. La aplicación ya hemos dicho que se llama La Meca, al paquete le he puesto por nombre com.jgc.lameca he cambiado el nombre a la actividad que quería crear y la he llamado EntryPointActivity y he marcado como Mínimum SDK el 7 (que coincide con el API level seleccionado en la pantalla anterior).
Podría crear un proyecto de tests pero en esta ocasión no lo voy a hacer porque no estoy demasiado puesto en tests con java y para Android (es una de mis tareas pendientes que también intentaré compartir con los lectores por aquí). Y ya podemos pulsar Finish, eclipse nos avisa de que vamos a crear un proyecto en un directorio no vacío, ningún problema, es lo que queremos, dentro de D:\Work\La Meca se crearán una serie de carpetas y ficheros que son nuestro proyecto, era justo lo que queríamos y es momento de ponernos manos a la obra.

Inserción del mapa

Antes de ponernos expresamente con el mapa voy a aplicar una serie de cambios al proyecto, para empezar voy a crear una clase BaseActivity de la que heredarán muchas o todas mis activities como ya expliqué en el anterior artículo para abrir mi aplicación a la extensión, y una clase MapActivity que será la que contenga el mapa.

BaseActivity:
package com.jgc.lameca;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import android.widget.Toast;

public abstract class BaseActivity extends Activity 
{
 protected void onCreate(Bundle savedInstanceState) 
 {
  super.onCreate(savedInstanceState);
  // Esto lo hará cada Activity con su vista:
  // setContentView(R.layout.activity_default);
 }
 

 protected void onDestroy ()
 {
  super.onDestroy ();
 }
 
 protected void onPause ()
 {
  super.onPause ();
 }

 protected void onRestart ()
 {
  super.onRestart ();
 }

 protected void onResume ()
 {
  super.onResume ();
 }

 protected void onStart ()
 {
  super.onStart ();
 }

 protected void onStop ()
 {
  super.onStop ();
 }
 
 // Click Methods HERE:

 
 public void onClickFeature (View v)
 {
  int id = v.getId ();
  switch (id) {
  case R.id.ep_map_button:
   lanzarMapa();
   
   break;
  case R.id.ep_gps_button :   
   lanzarLocationSettings();
  
  default: 
   break;
  }
 }

 private void lanzarMapa() {
  Intent i = new Intent(this, MapActivity.class);
  i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
  startActivity(i);
 }  

 private void lanzarLocationSettings() {
  Intent i = new Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS); 
  startActivity(i);
 } 

 public void lanzarEntryPoint(Context context) 
 {
  final Intent intent = new Intent(context, EntryPointActivity.class);
  intent.setFlags (Intent.FLAG_ACTIVITY_CLEAR_TOP);
  context.startActivity (intent);
 }

 public void setTitleFromActivityLabel (int textViewId)
 {
  TextView tv = (TextView) findViewById (textViewId);
  if (tv != null) tv.setText (getTitle ());
 } 


 public void toast (String msg)
 {
  Toast.makeText (getApplicationContext(), msg, Toast.LENGTH_SHORT).show ();
 }

 public void trace (String msg) 
 {
  Log.d("La Meca :: ", msg);
  toast (msg);
 }

} 

El código de BaseActivity es mas o menos auto-explicativo, se trata de una clase abstracta que extiende Activity y que además tiene una serie de métodos para lanzar distintos Intents, quizá tenga que refactorizarla dentro de un rato para añadir o quitar alguno pero he puesto métodos para lanzar el mapa, para lanzar las settings de ubicación (habilitar/deshabilitar GPS desde ajustes del sistema) e ir a EntryPointActivity.

Si ahora vamos al código de EntryPointActivity y hacemos que en vez de extender de Activity extienda BaseActivity entonces EntryPointActivity podrá hacer todo lo que acabamos de ver que puede hacer BaseActivity (simple, ¿no?) y así sucesivamente...

EntryPoitnActivity:
package com.jgc.lameca;

import android.os.Bundle;

public class EntryPointActivity extends BaseActivity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
    }
}

Al crear la EntryPointActivity se llama al create de BaseActivity y después se establece la vista (layout) main, que es la que se ha creado por defecto al crear el proyecto. Vamos a crear también MapActivity, que será muy similar a EntryPointActivity, antes de ponernos a darle un toque a las vistas...

MapActivity:
package com.jgc.lameca;
import android.os.Bundle;

public class MapActivity extends BaseActivity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.map_layout);
    }
}

Tan simple como eso, dos actividades, una que será el punto de entrada y otra que será el mapa. Si has seguido estos pasos hasta aquí tu proyecto tendrá errores igual que los tiene el mío ya que en BaseActivity estamos referenciando a botones que todavía no existen y en MapActivity a un layout que ni siquiera hemos creado:


Pues vamos a crear esos botones y ese layout para quitarnos esos errores. Sin preocuparnos de momento por el aspecto visual a main.xml (en res/layout/) le añadimos dos botones los cuales se llamarán ep_map_button y _ep_gps_button respectivamente. Voy a dejar el texto que había por defecto, hello y mi main.xml quedaría así.

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

    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/hello" />

    <Button
        android:id="@+id/ep_map_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Lanzar Mapa" />

    <Button
        android:id="@+id/ep_gps_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Lanzar Configuración GPS" />

</LinearLayout>

Bien, ya han desaparecido los errores de BaseActivity, vamos a crear map_layout.xml para que no haya errores en MapActivity y que compile el proyecto, una vez creado ese fichero en res/layout la estructura del proyecto queda así:


Aún no podemos probarlo porque hay que modificar AndroidManifest.xml para que incluya las actividades y los permisos necesarios. Muchas veces me ha sucedido que he creado una actividad, un activity, y que luego no la he incluído en en fichero Manifest y, durante un largo rato, me he vuelto loco porque no sabía por qué la aplicación "petaba" al abrir dicha actividad... Recuerda que AndroidManifest.xml es una parte fundamental de tu aplicación. Voy a dejarlo "decente" antes de empezar con los mapas propiamente hablando... (mientras escribo y leo alguna noticia estoy ojeando éste tutorial). Una primera versión de AndroidManifest.xml puede ser como ésta:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.jgc.lameca"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="7"
        android:targetSdkVersion="7" />

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:name=".EntryPointActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".MapActivity" />
    </application>

</manifest>

Bueno, pues vamos a probarlo, enchufo mi móvil, y en eclipse pulso Debug... Se abre, es feo y al pulsar en los botones no funciona, ¿por qué no funciona? pues porque no le he dicho al botón qué es lo que tiene que hacer. Hay varias maneras de hacerlo y como en BaseActivity ya tenía un método OnClickFeature lo que tengo que haacer es añadir la siguiente línea a cada botón:
android:onClick="onClickFeature"

Así cuando se pulse el botón se ejecutará el método onClickFeature, en el cual dependeindo de qué botón lo haya llamado, se ejecutará una acción u otra... Guardo y vuelvo a probar a ver qué pasa... Y como era de esperar ( o_0 ) todo funciona como esperaba, aparece EntryPoint y si pulso en configuración GPS se me abren las settings de localización, desde ahí si pulso atrás vuelvo a entryPoint y si pulso en Mapa se me abre una ventana vacía. No tiene más, es el momento de empezar a "crecer".

Mapas propiamente hablando

Hasta ahora he creado una estructura para la app pero no he tocada nada de mapas. Sinceramente, llevo bloqueado una hora o así leyendo documentación, empecé con el tutorial de mapview que puse antes pero ya hay una versión dos y una versión tres del api de google maps y estoy peleándome con la versión dos... No voy a liaros con pasos infructuosos pues de lo que se trata es de facilitar el aprendizaje a los posibles lectores y no liarlos ni cansarlos con mis cábalas y pruebas-errores...

Una de las cosas que he tenido que hacer ha sido actualizar el plugin de eclipse, y mientras se descarga e instala todo estoy leyendo las noticias y tomándome un pequeño descanso... y como se hace eterno me he puesto a leer un poco éste artículo sobre mapas en Android con Google Maps  Android API v2 (gran entrada en +sgoliver.net blog, gracias +Salvador Gómez )

Y bueno, después de muchas vueltas mi error (tengo que leer con más detenimiento a veces) era que para utilizar fragments en una actividad ésta debe extender FragmentActivity en vez de Activity a secas... Una vez modificado BaseActivity para que extienda FragmentActivity y habiendo añadido a nuestro proyecto las librerías y dependencias necesarias como tan bien se explica en el artículo de sgoliver.net ya se carga un mapa al lanzar MapActivity, ahora sólo nos queda que ese mapa nos localice y mostrar la dirección a La Meca, cosa con la que me pondré ésta tarde o ya el domingo... Así ha quedado AndroidManifest.xml, con todo lo necesario para utilizar Google Maps Android API v2.

AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.jgc.lameca"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="7"
        android:targetSdkVersion="7" />

    <permission
        android:name="com.jgc.lameca.permission.MAPS_RECEIVE"
        android:protectionLevel="signature" />

    <uses-permission android:name="com.jgc.lameca.permission.MAPS_RECEIVE" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

   <uses-feature
        android:glEsVersion="0x00020000"
        android:required="true" />

    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:allowBackup="true" >
        <activity
            android:name=".EntryPointActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".MapActivity" />

        <meta-data
            android:name="com.google.android.maps.v2.API_KEY"
            android:value="AQUÍ VA TU CLAVE" />
    </application>

</manifest>


Se me ha hecho un poco tarde, para terminar por hoy voy a dejar una última captura de pantalla de cómo ha quedado el proyecto y seguiré con él la próxima vez que tenga algo de tiempo libre.


[To be continued...]

28 nov. 2012

Arquitectura de aplicaciones Android

Estos días ando con varias peleas con la nueva versión de Gredos Virtual 3D pero tengo pendiente desde hace bastante tiempo publicar un artículo destripando un poco la arquitectura de ésta aplicación. En concreto de lo que quiero hablar aquí es de un diseño que me ha demostrado ser bastante eficiente y que creo que es reutilizable en casi cualquier futura aplicación Android tanto por su versatilidad como por su robustez. 

Doy por hecho que cualquiera que esté leyendo ésto está buscando una buena solución para la interfaz de usuario de su aplicación, ésta solución busca robustez y versatilidad y que el crecimiento en funcionalidades sea fácil de acometer sin suponer un grave impacto en la experiencia de usuario. Siguiendo las pautas que daré a continuación podremos conseguir que todas las actividades de nuestra aplicación sean homogéneas tanto en el flujo de control como en los aspectos visuales. No voy a pegar código pues creo que lo que se explica aquí cualquiera con algo de experiencia con Android lo puede codificar por si mismo, lo importante es pensar primero, realizar un buen diseño que es lo que aquí se intenta y codificar después, quizá si deje algún enlace a entradas de StackOverflow pero no código propio a no ser que me lo pida algún lector.

Vamos al grano. ¿Qué se le debe pedir a una aplicación móvil? En general las aplicaciones móviles suelen tener una Splash Screen, es decir, una pequeña pantalla con un logo o una barra de progreso indicando la carga de la aplicación, esto es totalmente opcional pero le da a nuestras aplicaciones un "toque" profesional. Lo siguiente suele ser un panel de control, una pantalla o actividad que será el centro neurálgico del resto de la aplicación desde donde accederemos a las distintas funcionalidades y en donde queda presente por medio de estilos la imagen y apariencia de la aplicación, imagen que queremos mantener a lo largo del resto de actividades. El resto de actividades de nuestra aplicación podrían, es más, deberían mantener la homogeneidad con la pantalla de inicio, el panel de control, e incluir todas ellas las mismas opciones. ¿Cómo podemos hacer ésto sin repetir código? 

La respuesta a éste primer interrogante, atendiendo al principio DRY (Don't Repeat Yourself) y a la S y la O de SOLID (SRP: Single Responsiblity Principle y OCP: Open Closed Principle) es utilizar herencia, de ésta forma no repetiremos código en cada una de nuestras actividades y abriremos nuestra aplicación a la extensión consiguiendo que cada actividad se centre en una única responsabilidad. El simple hecho de utilizar una clase BaseActivity que aglutine las funciones, las acciones y los flujos comunes como podrían ser ir a inicio, realizar una búsqueda o las comprobaciones pertinentes hacia el usuario, permite a las actividades que hereden de ésta clase base despreocuparse de estas responsabilidades y centrarse en lo que de verdad importe en cada una de ellas. Eso respecto a la lógica de la aplicación y el flujo entre actividades nos da potencia, robustez, homogeneidad y versatilidad, ¿qué más se puede pedir? 

Respecto a los aspectos visuales tenemos la herencia entre layouts, no existe, al menos hasta donde yo sé, una herencia como tal entre layouts de Android pero sí que existe la directiva <Include> con la que en cada layout de cada actividad que herede de BaseActivity podamos incluir por ejemplo una o más barras de tareas con las acciones comunes implementadas en BaseActivity así:

<include layout="@layout/tasksbar"/>
...
<include layout="@layout/footerbar"/>

Otro aspecto arquitectural importante suele ser mantener un contexto de aplicación, un nexo de unión entre las distintas actividades que contenga, por decirlo de alguna manera, estados, objetos o información de carácter global, que asegure dependencias y que sirva de nexo para todas las actividades de nuestra aplicación. Para ésto lo mejor, en mi opinión, es definir una clase propia que extienda a Application y declararla en el fichero AndroidManifest.xml de nuestra aplicación en la que podremos controlar, independientemente de qué suceda en cada actividad y de cuál se lance primero, qué sucede  cuando se crea, se pausa, se "resume" o relanza y se termina la aplicación por ejemplo instanciando distintos recursos como servicios o controladores, pausándolos y resumiéndolos cuando sea necesario y liberando la memoria utilizada por ellos al salir. 

Mediante estas tres pautas, una actividad base, un correcto uso de los layouts y un buen contexto de aplicación nuestras aplicaciones ganarán en profesionalidad, en robustez, en versatilidad y estaremos preparados para el cambio y para que nuestras aplicaciones crezcan sin demasiadas implicaciones y efectos colaterales pues podremos centrarnos bien en los cambios que haya que realizar en cierta actividad o bien en desarrollar una nueva actividad con una nueva funcionalidad y adaptar el flujo de nuestra aplicación en muy poco tiempo y sin demasiadas complicaciones. 

Enlaces:

Herencia en actividades (BaseActivity):
Android how to create my own Activity and extend it ?
How to minimize duplication code in Android without creating Objects
Options Menus and Base Activities

Sobre <Include>:

Contexto de Aplicación:

Patrones de diseño para Android:

Espero que ésta lectura y los enlaces hayan sido útiles y recibir comentarios tipo "quiero saber más sobre ésto", "En ésto te equivocas" o "Gracias, me ha sido muy útil éste artículo".

Un saludo.


Juan García Carmona

19 nov. 2012

Actualizado Gredos Virtual 3D

Para los que han seguido los desarrollos para Android que he estado haciendo estos meses y a los que ya les gustaban las aplicaciones de mapas en 3D que estaban en marcha tengo una buena noticia: 

Hoy he subido la actualización a la versión 2.0 Gredos Virtual 3D. 

Sólo voy a dejar unas capturas de pantalla y un enlace a la descarga aunque los que tengáis un móvil con Android podéis descargarla directamente desde Google Play en vuestros dispositivos. 

Gredos Virtual 3D 2.0 incluye muchas mejoras:
  • Se ha mejorado la interfaz de usuario
  • Se ha mejorado el modo paseo incluyendo una brújula y permitiendo andar de forma continua con una pulsación larga
  • Se ha añadido el modo niebla, el cual te posiciona en el mapa y gracias a los sensores de inclinación y a la brújula permiten ver lo que hay a nuestro alrededor aunque la niebla o las condiciones meteorológicas nos lo impidan. Lo he probado In-Situ y me ha sido enormemente útil.

Aquí las capturas de pantalla:




Y el enlace es:


Y creo que éste puede ser un buen momento para anunciar mi intención de crear una plataforma para la creación de mapas en 3D. No puedo dar más que los siguientes detalles:
  • Será una aplicación gratuíta, de momento para Android, pero quizá porte sólo o con ayuda ésta app a Windows Mobile y iPhone. Desde ésta aplicación se podrán descargar y utilizar los mapas en 3D que ya tengo hechos y los que poco a poco vaya haciendo y publicando.
  • Liberaré el código en GitHub para que cualquiera con conocimientos de programación e interés pueda aportar su granito de arena y mejorar la aplicación, de forma altruista y para todos.
  • Crearé una wiki explicando paso a paso cómo se puede crear un mapa y como están hechos "por dentro" y facilitaré al máximo el proceso para que cualquiera pueda hacerlo.

Ésta plataforma a la que ahora hago referencia requiere mucho trabajo y también mucho esfuerzo por mi parte. No sé hasta donde llegará ni cuando estará lista para dar los "primeros pasos" pero según acabe esta entrada comenzaré a dar forma a todo éste proyecto. Cualquiera interesado en participar activamente puede ponerse en contacto conmigo y le atenderé encantado.

Un saludo.

Juan García Carmona

15 nov. 2012

SOLID Y GRASP. Buenas prácticas hacia el éxito en el desarrollo de software.

Como muchos sabéis el otro día impartí una conferencia a través de Internet titulada "SOLID y GRASP, buenas prácticas hacia el éxito en el desarrollo de software". Dicha conferencia la organizó el grupo de investigación de Ingeniería Web y Testing Temprano (IWT2) fue dada a través de Internet utilizando Skype para el audio y el vídeo y compartiendo parte de mi pantalla con la sala de la Escuela Técnica Superior de Ingeniería Informática de la Universidad de Sevilla donde se encontraban los asistentes gracias a screenleap

Pues bien, antes del simposio entregué a los alumnos una documentación que no todos los asistentes tenían impresa y que era una guía muy detallada de lo que tenía pensado decir, incluyendo comentarios  diagramas UML y código fuente de multitud de ejemplos "malos" donde se cometían ciertos errores. También había en esa documentación una serie de huecos pensados inicialmente para que los asistentes, a modo de ejercicio, fueran resolviendo y arreglando cada ejemplo "malo", tanto con diagramas UML como con código fuente en C# o pseudocódigo. 

Como no todos tenían esa documentación decidí saltarme las prácticas e ir planteando yo cada una de las soluciones y fueron pocos o ninguno los que tuvieron tiempo de coger nota de todo lo que fui diciendo y al final de mi charla prometí que subiría un documento con todo lo que había añadido a la charla y no estaba en la guía inicial.

Bueno, acabo de terminar dicho documento y un poco mas abajo dejo el enlace para su descarga. Como me ha llevado muchísimas horas de trabajo prepararlo he decidido aplicarle una licencia Creative Commons, en concreto Creative Commons Attribution-NonCommercial-NoDerivs 3.0 Unported (CC BY-NC-ND 3.0). Ésta licencia permite copiar el documento y distribuirlo cuanto se quiera pero mencionando explícitamente de quien es la autoría y nunca y bajo ninguna circunstancia con ánimo de lucro o con fines comerciales. Mi intención con ésto es que se pueda utilizar éste documento, en el contexto que sea y a nivel internacional, para enseñar lo que en el documento se enseña pero reconociendo la labor de la elaboración de dicha documentación, diagramas UML y códigos fuente de ejemplos y soluciones.

A continuación dejo el enlace para la visualización y descarga del documento y quedo a la espera de comentarios, tanto de los asistentes al simposio como de futuros lectores.




Licencia de Creative Commons

Un saludo.
Juan García Carmona

5 nov. 2012

IWT2 DojoUS :: SOLID y GRASP

Éste viernes 9 de noviembre de 2012 voy a dar una pequeña charla en la Universidad de Sevilla. Hace tiempo me puse en contacto con Javier Gutierrez, profesor de la Universidad de Sevilla, entusiasta del TDD y miembro de IWT2, grupo de Ingeniería Web y Testing Temprano. A raiz de algunas conversaciones que tuvimos en cuanto a la formación de ingeniería de software y de TDD me ofreció hacer una ponencia sobre éste tema ya que es algo que en muchas ocasiones se menciona en libros y blogs sobre la materia pero en lo que pocas veces se profundiza.

Llevo unos días preparándolo, a partir de la documentación y los ejemplos que ya tenía de los distintos artículos que hay en éste blog, he preparado una guía para los asistentes en la que punto por punto iremos viendo una serie de ejemplos sobre los que trabajaremos y estoy preparando una serie de ejercicios prácticos de refactorización de código "foráneo" de programas que incumplen uno o varios principios que espero nos de tiempo a realizar ya que la documentación en sí es bastante extensa. 

De momento no puedo adelantar mucho más, podéis encontrar algo de información a éste respecto en los siguientes enlaces:


Tengo que decir que estoy nervioso pues éste no es un tema especialmente divertido y me gustaría poder entretener a los asistentes y conseguir a la vez que éstos principios arraiguen en ellos y sean aplicados, o al menos tenidos en cuenta, durante su ejercicio profesional.

No sé si publicaré más material del utilizado durante ésta charla, aquí os dejo la que va a ser la guía para el alumno y una serie de ejercicios propuestos. Supongo que éstos ejercicios, por la duración del evento y lo extenso de la materia, no podrán llegar a ser tratados tras la charla pero invito tanto a los asistentes al dojo como a cualquier lector a mandarme sus respuestas para que sean publicadas en su nombre aquí en mi blog.

Guía del curso SOLID y GRASP
Ejercicios sobre SOLID y GRASP

Un saludo.

Juan García Carmona
d.jgc.it@gmail.com

19 sept. 2012

Mis montañas en 3D

Hoy leo que María Teresa Ruiz Monzón, una informática de la Universidad del País Vasco (UPV/EHU), acaba de presentar una aplicación para que los montañeros puedan usar la geolocalización 3D en sus móviles. Por lo que he leído, porque no he conseguido saber cómo se llama la aplicación para probarla, tiene un grave fallo, en mi opinión, y es que necesita de conexión a Internet para ir descargando los datos necesarios para representar el área que tienes a tu alrededor.  Lo he leído en éste artículo en el que también se dice que tiene problemas de memoria. Por las tecnologías que menciona y por la captura de pantalla que he podido ver de la aplicación creo que el desarrollo ha sido muy similar a "mis montañas en 3D" sólo que yo tomé la decisión de, en vez de posibilitar la reproducción de la zona de España en la que estés, por esos mismos problemas de memoria, reproducir con más calidad zonas montañosas concretas como el Circo de Gredos en Ávila, el Valle de Ordesa en Huesca, o distintas zonas de la Sierra de Guadarrama en Madrid y Segovia como la zona de Peñalara o la ruta de La Cuerda Larga que va desde el Puerto de Navacerrada hasta el Puerto de La Morcuera. 

A raíz de ésta noticia he decidido volver a ponerme manos a la obra con "mis montañas en 3D" porque tengo pendiente, casi desde que saqué la primera versión de Gredos 3D que fue la primera que hice y una de las más vistosas, varias mejoras que, curiosamente, si que ha desarrollado María Teresa. Una de ellas es el modo niebla, que convertiría "mis montañas 3D" más que en un juego en una ayuda en montaña ya que, gracias a la brújula y a los niveles del dispositivo, en caso de niebla nos mostraría el terreno que tenemos delante aunque no lo viéramos. Otra de las mejoras y con la que me pondré primero es, en modo juego, es decir, moviéndote libremente por "el escenario", dar al usuario información sobre su posición, su altitud y la dirección en la que mira. La tercera de las mejoras sería poder hacer zoom, sé que OpenGL ES lo permite y lo apliqué en uno de los tutoriales que seguí para aprender pero tendría que recordarlo. En resumen son tres puntos, mis tres próximos hitos:
  1. Dar información de tu posición en modo juego, coordenadas, altura y dirección.
  2. Posicionarte en el mapa si estás en ése área, modo niebla, y utilizar sensores para mostrar lo que se debería ver.
  3. Poder hacer zoom.
  4. (Añadido a posteriori) Mejorar la pantalla de inicio.

No voy a sacar más mapas en 3D hasta que "el motor 3D", que es el mismo en todas las aplicaciones, no tenga, al menos, dos de estas tres nuevas características aunque si que tengo pensado mejorar algunas cosas en todas ellas como las texturas de alguna de ellas. Voy a hacer ahora un resumen del "estado del arte" de "mis montañas en 3D". 

Todas mis aplicaciones Android las publicaré con mi nombre completo y son accesibles desde aquí.


La primera que subí fue Gredos 3D, que fue uno de los primeros intentos de aprender a programar Android y que acabó derivando en una interminable serie de horas "jugando" a programar cosas en 3D. Muy entretenido y para mi gusto con un resultado final espectacular, aunque claro, soy el autor y siempre veo fallos, no me gusta, ni en ésta ni en las otras, la pantalla inicial, creo que es una chapuza. En Gredos 3D estamos en una representación en 3D del Circo de Gredos, Ávila, y sus alrededores. Podemos pasear rápidamente hasta el pico Almanzor o la Galana, andar por la Laguna Grande de Gredos y acercarnos a Los Galayos.  Si no recuerdo mal, no tengo los datos delante, eran 15 kilómetros cuadrados.



La siguiente aplicación que subí fue una variación de Gredos 3D a la que llamé Gredos Virtual 3D en la que en vez de una textura realizada "artesanalmente" juntando ortofotos de muchísima calidad realicé, a partir del mapa de alturas, una textura que no era más que un mapa con curvas de nivel y sombras creadas "automáticamente" y en el que dibujé a mano las lagunas, la carretera de la Plataforma de Gredos y los refugios de El Reguero Llano y el Refugio Elola.


[ACTUALIZADO 04/10/2012]

Hay bastante gente que ya lo sabe porque según las estadísticas de la Consola del desarrollador de Google Play hubo149 instalaciones totales de usuarios y hay 27 instalaciones activas de dispositivos actualmente. PAra todos menos esas 27 personas: volví a crear la textura de Gredos 3D con unas curvas de nivel más estandarizadas, es decir, con una separación de 20m entre las curvas. Me he guardado la imagen base, el mapa con curvas de nivel, para ir añadiendo sobre él caminos, rutas, cascadas de hielo, etcétera y ya he encontrado la manera de ponerle una leyenda en 3D que pueda leer el observador desde cualquier ángulo pero aún no me voy a poner con eso. Quiero que veáis el "estado del arte" del modo niebla y creo qu el siguiente vídeo habla por sí solo.



Mi intención es acabarlo hoy, ya tengo abierto el eclipse y el proyecto y las ideas claras sobre cómo quiero hacer la interfaz de usuario mientras se obtiene la posición GPS. Prometo hacer lo mismo para la siguiente actualización de Ordesa Virtual 3D en cuanto acabe con Gredos Virtual 3D.

[/ACTUALIZADO 04/10/2012]


Ordesa Virtual 3D es una representación de 10 kilómetros cuadrados alrededor del abrupto Valle de Ordesa. Para quien conozca ésta zona puedo decir que aparece parte del Cañón de Añisclo, El Monte Perdido y el Cilindro, El Balcón de Pineta y parte del Circo de Gavarnié. Al igual que sucede con Gredos Virtual 3D el resultado de la textura no acaba de convencerme pues no es estándar, tengo que modificarlo para que quede como en la representación que, para mi gusto, mejor quedó, la de Peñalara 3D Map. Dejo unas capturas del estado actual de Ordesa Virtual 3D.


En éstas imágenes se aprecia la sombra y se ve que están marcadas las alturas de algunas curvas de nivel pero el "código de colores" aunque si que es bastante estándar no acaba de gustarme, por eso ésta textura también la voy a modificar para que se parezca a la de Peñalara 3D Map.


Ordesa 3D es el mismo escenario que el anterior pero con una textura basada en Ortofotos copiadas y pegadas en un lienzo de 2048x2048 px. Fue un trabajo duro, cansado para la vista, pero creo que me quedó bastante bien, el único problema fue que dicha textura en formato png ocupaba una barbaridad (más de 10 MB) así que la comprimí y la guardé en otro formato que llevaba menos ruido y datos que para lo que se necesita en la aplicación son inútiles. Ésta optimización vino por un comentario que me hizo un usuario de la aplicación ya que siempre dejo mi correo "profesional" a disposición de mis potenciales usuarios (d.jgc.it@gmail.com). Él me dijo que en su dispositivo la aplicación no funcionaba debido a un desbordamiento de la memoria. Ésto me llevo a desarrollar Ordesa 3D Little (mismo escenario pero mucha menos resolución en cuanto a alturas y en cuanto a texturas pero parecida definición en tiempo de ejecución) y a encontrar la manera en la que después he ido reduciendo el paso de las distintas texturas de "mis montañas 3D". Ordesa 3D y 3D Little se ven así:


Creo que cualquiera que haya estado por allí podrá decir que es tremendamente realista, en esas imágenes vemos distintas perspectivas del Valle de Ordesa (1, 5 y 6) el Glaciar de Monter Perdido (2 y 3) y el Balcón de Pineta desde el Monte Perdido hacia donde debería estar el Refugio de Tucarroya (4). Espero tener pronto el modo niebla y el panel con los datos de la posición y la dirección de la cámara en modo juego.


Ya he hablado antes de ésta aplicación y he dicho que era la que tenía la mejor textura (virtual o tipo mapa) de todas. Es un mapa virtual en 3D con curvas de nivel cada 20 metros alrededor del Pico Peñalara. Me gusta mucho como me quedó la textura y la manera en la que la hice y será como haga a partir de ahora todas las aplicaciones que pretendan ser un mapa topográfico en 3D, es decir, en un solo color, verdoso, con sombras y curvas de nivel cada 20 metros. En esta App aparecen marcadas varias zonas, en dos tonos más oscuros de verde, primero el parque de Peñalara y segundo las zonas de especial protección medioambiental del mismo; en tono azulado aparece la parte de la estación de esquí de Valdesquí que queda dentro del mapa. ¿Que os parecen éstas imágenes?


¿Os gusta? Mi opinión ya la sabéis, y es que todas las texturas de mapas virtuales en 3D deberían ser como ésta, mejora que aplicaré poco a poco tanto a Gredos Virtual 3D como a Ordesa Virtual 3D.


Ésta es la aplicación que abarca más superficie de todas las que he desarrollado hasta el momento. Son 10 kilómetros en vertical, de Norte a Sur, por 20 kilómetros en horizontal, de Este a Oeste. En ella aparece representada gran parte de la Sierra de Guadarrama y en el mapa he marcado la ruta de La Cuerda Larga, ruta que, de cumbre en cumbre, recorre un trayecto directo entre el Puerto de Navacerrada y el Puerto de La Morcuera por encime de lo que los madrileños conocen como "La sierra de Madrid". Al igual que en Peñalara 3D Map en ésta aplicación he marcado en verde más intenso otro parque natural, el Parque Regional de La Pedriza del Manzanares, y también aparece marcado en azul la Estación de Esquí de Valdesquí, esta vez en su totalidad. Quien haya probado Peñalara 3D Map dirá que la textura de ésta última aplicación es mejorable pero yo le diría que el "peso" total de la aplicación y la cantidad de recursos consumidos en tiempo de ejecución hacen inviable una mejora. 


En esas imágenes vemos el comienzo de la ruta desde el Puerto de Navacerrada (1) y distintas partes de la ruta de La Cuerda Larga (2, 3 y 4), La Pedriza desde distintos puntos de la ruta (5, 6 y 8) y una imagen del Pico de La Maliciosa (8).

Voy a acabar la entrada de hoy con un resumen de mis siguientes pasos con respecto a "mis montañas 3D". 

1.- Mejorar las texturas de Gredos Virtual 3D y Ordesa Virtual 3D
2.- Mostrar información de la posición en modo juego
3.- Habilitar "modo niebla" (cuando estás dentro del mapa)
4.- Mejorar la pantalla de inicio de cada aplicación, todas son iguales con alguna variación.

PD: ¿Quieres o tienes especial interés en tener disponible alguna zona concreta en 3D con éste formato? Pídemelo sin problema, en mi lista de mapas pendientes tengo varios valles del Pirineo y de Picos de Europa y aunque no tengo pensado seguir haciendo mapas hasta que no acabe las mejoras en "el motor 3D" si quieres que saque alguno con el estado actual (modo juego sin datos de posición) sólo tienes que pedírmelo y lo haré encantado.

Un saludo.

Juan García Carmona