Sabemos lo importante que es que nuestra aplicación Android sea valorada por nuestros usuarios en Google Play para conseguir más descargas, aparecer antes en los resultados de búsqueda, etc.
Es una costumbre habitual que una aplicación sea valorada solamente si hemos tenido una mala experiencia con ella (nos darán una estrella) y son los menos los que la valoran positivamente si les ha gustado (así somos
).
Una forma de “ayudar” al usuario a que valore nuestra aplicación en Google Play, es mostrarle un mensaje recordándole de que puede valorar la app (otra cosa es que lo haga positiva o negativamente).
En este ejemplo mostraremos un popup a través de un AlertDialog para que pueda ir directamente a nuestra aplicación en Google Play.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setMessage("Valora la aplicación en Google Play!") .setCancelable(false) .setPositiveButton("Vale, ir a Google Play", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { dialog.cancel(); startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=ID_DE_LA_APP") ) ); } }) .setNegativeButton("Ahora no", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { dialog.cancel(); } }); alert = builder.create(); alert.show(); |
Solo tendremos que cambiar “ID_DE_LA_APP” de la línea 7 por el identificador de nuestra aplicación, por ejemplo com.example.myapp.
Etiquetas: AlertDialog, Android, aplicación, dialog, DialogInterface, google play, java, startActivity
Cuando iniciamos una aplicación en nuestro dispositivo Android, es habitual que se muestre durante unos pocos segundos una pantalla inicial (también llamada “splash“) donde se muestra el logotipo de la aplicación, de la empresa, etc. También es habitual utilizar esta pantalla para la precarga de datos y así tenerlos disponibles una vez la aplicación haya iniciado.
En este post vamos a ver como mostrar una pantalla splash durante unos segundos al iniciar nuestra aplicación.
Para ello, creamos una nueva clase llamada “SplashActivity” con el siguiente contenido:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | public class SplashActivity extends Activity { // Duración en milisegundos que se mostrará el splash private final int DURACION_SPLASH = 3000; // 3 segundos @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Tenemos una plantilla llamada splash.xml donde mostraremos la información que queramos (logotipo, etc.) setContentView(R.layout.splash); new Handler().postDelayed(new Runnable(){ public void run(){ // Cuando pasen los 3 segundos, pasamos a la actividad principal de la aplicación Intent intent = new Intent(SplashActivity.this, MainActivity.class); startActivity(intent); finish(); }; }, DURACION_SPLASH); } } |
Por supuesto debemos tener una aplicación principal llamada (en este caso) MainActivity que es la que se abrirá automáticamente cuando la pantalla splash desaparezca cuando pasen los 3 segundos que hemos configurado.
No podemos olvidar incluir esta nueva actividad en el archivo AndroidManifest.xml como actividad principal (para que nada más iniciar la aplicación, sea la actividad SplashActivity la que se abra). Es habitual mostrar la pantalla de splash a pantalla completa, para ello le daremos el estilo correspondiente:
1 2 3 4 5 6 7 8 9 | <activity android:name=".SplashActivity" android:label="@string/app_name" android:theme="@style/Theme.Light.NoTitleBar.Fullscreen"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> |
Etiquetas: activity, Android, handler, intent, manifest, postDelayed, runnable, splash, startActivity
En Java, cuando definimos una nueva clase, debemos conocer el tipo de dato con el que trabajaremos. Si queremos realizar una operación específica dentro de esta nueva clase, sea cual sea el tipo de datos que va a recibir, podemos hacer uso de los tipos genéricos. Este tipo genérico asumirá el tipo de dato que realmente le pasaremos a la clase.
Mejor con un ejemplo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | class ClaseGenerica<T> { T obj; public ClaseGenerica(T o) { obj = o; } public void classType() { System.out.println("El tipo de T es " + obj.getClass().getName()); } } public class MainClass { public static void main(String args[]) { // Creamos una instancia de ClaseGenerica para Integer. ClaseGenerica<Integer> intObj = new ClaseGenerica<Integer>(88); intObj.classType(); // Creamos una instancia de ClaseGenerica para String. ClaseGenerica<String> strObj = new ClaseGenerica<String>("Test"); strObj.classType(); } } |
Unos apuntes:
El resultado será el siguiente:
1 2 | El tipo de T es java.lang.Integer El tipo de T es java.lang.String |
Hay que tener en cuenta que los generics de java solo funcionan con objetos. El código siguiente nos mostrará un error:
1 | ClaseGenerica<int> myOb = new ClaseGenerica<int>(53); // Error, can't use primitive type |
Existen una serie de convenciones para nombrar a los genéricos:
Las clases y tipos genéricos nos serán de gran utilidad para programar ciertas funcionalidades en nuestras aplicaciones para Android.
Etiquetas: clase genérica, generics, java, tipos genéricos
Cuando estamos desarrollando una aplicación es habitual (y muy recomendable) tener el código en un sistema de control de versiones como podría ser Subversión o Git. De esta forma podremos colaborar con otros programadores en el desarrollo de la aplicación. También podremos seguir programando desde diferentes ordenadores y continuar por donde lo hemos dejado con solamente descargarnos la última versión que hayamos subido.
En el caso de Android (Java), me he encontrado con un error bastante peculiar a la hora de importar un proyecto en Eclipse. El error en cuestión es el siguiente:
The method onClick(View) of type LoginFragment must override a superclassmethod |
Este es un extracto de la clase donde me aparecía el error:
public class LoginFragment extends Fragment implements OnClickListener @Override public void onClick(View v) { [...] } |
El error indica que al añadir la anotación @Override necesito sobrescribir un método de la clase padre (OnClickListener).
El problema es que el método onClick es un método de una interface y con el compilador de Java 1.5 esto no es posible ya que solo se pueden sobrescribir métodos de una superclase utilizando la anotación @Override.
En Java 1.6 si que está permitido, y lo hacemos incluyendo la anotación @Override.
En conclusión, estamos utilizando diferentes versiones de Java en cada entorno. En el entorno que nos da problemas debemos cambiar el compilador de Java. Para ello vamos a Window > Preferences y en la columna izquierda seleccionamos Java > Compiler. Solo debemos cambiar el “Compiler compliance level” a la versión 1.6 (o superior).
Etiquetas: Android, eclipse, git, interface, java, onclick, override, subversion, superclass
En el momento de desarrollar una aplicación para Android nos encontramos con el “problema” de la gran cantidad de dispositivos disponibles en el mercado, cada uno con un tamaño de pantalla diferente. Como programadores, queremos que nuestra aplicación sea compatible con todos los diferentes tamaños de pantalla.
Para solucionar este problema, en Android tenemos disponibles varias unidades de medida que nos ayudarán a que nuestra aplicación se vea correctamente sea cual sea el tamaño de la pantalla. Para ello, debemos utilizar la unidad de medida que mejor se ajuste a nuestra aplicación y requerimientos. La utilizaremos para especificar el tamaño de los elementos de nuestra aplicación.
Aquí va un listado de las diferentes unidades de medida que tenemos disponibles en Android:
- dp (Density-independent Pixels)
Es una unidad abstracta que se basa en la densidad física de la pantalla. Esta unidad es equivalente a un píxel en una pantalla con una densidad de 160 dpi. Cuando se está ejecutando en una pantalla de mayor densidad, se aumentan el número de píxels utilizados para dibujar 1dp según los dpi’s de la pantalla. Por otro lado, si la pantalla es de menor densidad, el número de píxeles utilizados para 1dp se reducirán. Utilizar las unidades dp en lugar de píxeles es la solución más simple para tratar los diferentes tamaños de pantalla de los dispositivos.
- sp (Scale-independent Pixels)
Esta unidad es como la anterior, pero se escala según el tamaño de fuente configurada. Se recomienda utilizar esta unidad si se especifican tamaños de fuente, por lo que se ajusta tanto para la densidad de pantalla y como a las preferencia del usuario.
- pt (Points)
Es un 1/72 de una pulgada, según el tamaño físico de la pantalla
- px (Pixels)
Corresponde a un píxel real en la pantalla. Esta unidad de medida no se recomienda porque la representación real puede variar según el dispositivo en el que se ejecute, ya que cada uno de ellos puede tener un número diferente de píxeles por pulgada y pueden tener más o menos píxeles totales disponibles en la pantalla.
- mm (Milímetros)
Son milímetros reales según el tamaño físico de la pantalla.
- in (Pulgadas)
Son pulgadas reales según el tamaño físico de la pantalla.
Etiquetas: Android, dp, dpi, in, medida, mm, pt, px, sp, unidades
Si estamos desarrollando una aplicación Android que se comunica con recursos externos en Internet, necesitaremos comprobar si el dispositivo en el que está instalada la aplicación tiene conexión a Internet en ese momento para poder realizar las consultas.
Con esta función podemos comprobar si existe conexión a Internet desde la aplicación Android. Podemos utilizarla para mostrar una advertencia al usuario en caso de que no haya conexión a Internet.
public boolean isOnline() { ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo netInfo = cm.getActiveNetworkInfo(); if (netInfo != null && netInfo.isConnected()) { return true; } return false; } |
Debemos añadir este permiso al fichero manifest:
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> |
Etiquetas: ACCESS_NETWORK_STATE, Android, aplicación, CONNECTIVITY_SERVICE, internet
AndEngine es un framework que nos facilita poder desarrollar juegos para plataformas Android utilizando el poder de OpenGL. Si quieres leer más sobre este framework, te recomiendo que vayas a los foros de la web de AndEngine.
En este post vamos a instalar el framework para poder utilizarlo en nuestro proyecto y futuro videojuego para Android.
Para ello, primero tenemos que hacer un clone del repositorio de AndEngine alojado en Github:
git clone https://github.com/nicolasgramlich/AndEngine.git |
Según el tipo de juego que quieras hacer, deberás instalar las extensiones que necesites. No hace falta que instales todas, solo las que tu juego vaya a necesitar. Puedes encontrar todas las extensiones disponibles en el Github del propio desarrollador (https://github.com/nicolasgramlich)
Para este ejemplo, vamos a instalar también la extensión AndEnginePhysicsBox2DExtension por lo que debemos hacer otro clone tal y como hemos hecho en el paso anterior.
git clone https://github.com/nicolasgramlich/AndEnginePhysicsBox2DExtension |
Ahora importamos en Eclipse el framework AndEngine desde “File > New Project > Android Project from existing code” y buscamos la carpeta AndEngine, que se encontrará en el directorio donde hayamos hecho el clone.
Debemos importar también los plugins que necesitemos, en este caso importamos también AndEnginePhysicsBox2DExtension de la misma forma.
Debemos asegurarnos de que tanto AndEngine como cualquier otra extensión que hayamos instalado están marcados como librerías. Para ello pulsamos con el botón derecho sobre el proyecto y seleccionamos “propiedades”. En la sección “Android”, nos fijamos que en la zona inferior está marcado el check “Is library”. Si no es así, lo debemos seleccionar para poder utilizarlos en nuestro aplicación.
Ahora vamos a crear nuestro proyecto de la manera habitual (File > New Project > Android Application Project), teniendo en cuenta que AndEngine solo funciona con la versión de Android 2.2 o superior, por lo que nos debemos asegurar de que seleccionamos una versión compatible.
Una vez creado el proyecto, pulsamos con el botón derecho en el nombre y seleccionamos la opción “propiedades”. Vamos a la sección “Android” y en la parte inferior pulsamos el botón “Add” para añadir tanto la librería AndEngine como las extensiones que necesitemos, que en nuestro caso es AndEnginePhysicsBox2DExtension.
Y listo, en este momento ya podemos hacer uso del framework AndEngine y de las extensiones que hayamos instalado.
Si quieres ver el potencial de AndEngine en acción, puedes descargarte las demos que tienen disponibles (para probar todas deberás instalar las demás extensiones).
Para instalar las demos de AndEngine, deberás clonar su repositorio de Github:
git clone https://github.com/nicolasgramlich/AndEngineExamples.git |
Después solo tienes que importarlo en Eclipse, ya sabes, “File > New Project > Android Project from existing code” y seleccionar la carpeta AndEngineExamples que nos acabamos de descargar.
Etiquetas: AndEngine, AndEnginePhysicsBox2DExtension, Android, framework, git, juegos, OpenGL, videojuegos
Con motivo del concurso #BigBangApps convocado por la web Ideas4All en que proponían desarrollar una aplicación utilizando su API, me puse a programar una aplicación para Android de la web. La API de Ideas4All devuelve los datos en XML (hubiese preferido en JSON pero de momento no lo permite) por lo que el “gran secreto” de la app es leer correctamente este tipo de archivos.
Hay varias formas de leer archivos XML, siendo una de ellas mediante SAX (Simple API for XML). En este post voy a explicar los pasos para leer/parsear correctamente un archivo XML disponible en Internet.
El fichero de ejemplo que queremos leer es el listado de categorías:
1 2 3 4 5 6 7 8 9 10 11 12 13 | <?xml version="1.0" encoding="UTF-8"?> <categories type="array"> <category> <id type="integer">6</id> <name>Vida/Salud</name> </category> <category> <id type="integer">3</id> <name>Sostenibilidad</name> </category> ... ... </categories> |
Lo primero que necesitamos es una clase que utilizaremos para guardar los datos de cada categoría. El objetivo es devolver una lista de objetos del tipo categoría, donde se encontraran todas las categorías leídas del XML.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | public class ParsedCategoryDataSet { private String id = null; private String name = null; public String getId() { return id; } public void setId(String extractedString) { this.id = extractedString; } public String getName() { return name; } public void setName(String extractedString) { this.name = extractedString; } public String toString(){ return "name = " + this.name; } } |
Como podemos observar, solamente se trata de una clase con setters y getters para almacenar los datos de cada categoría.
Ahora pasamos a crear la clase con la que parsearemos el archivo XML mediante SAX en Android.
El modelo SAX lee secuencialmente el fichero XML y ejecuta varios métodos (que podemos controlar) por cada elemento leído. Estos métodos son:
- startDocument(): comienza a leer el XML.
- endDocument(): ha terminado de leer XML.
- startElement(): ha encontrado el comienzo de una etiqueta XML.
- endElement(): ha encontrado el cierre de una etiqueta XML.
- characters(): texto que ha encontrado entre el comienzo y el cierre de una etiqueta
Estos métodos se encuentran en la clase org.xml.sax.helpers.DefaultHandler, de la que heredaremos nuestro propio parseador de categorías:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 | import java.util.Vector; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; public class CategoryHandler extends DefaultHandler{ public CategoryHandler() { super(); this.myParsedCategoryDataSet = new Vector<ParsedCategoryDataSet>(); } // Variables de control para saber cuando estamos en el interior de cada etiqueta @SuppressWarnings("unused") private boolean in_category = false; private boolean in_id = false; private boolean in_name = false; // En esta variable guardamos el texto encontrado entre las etiquetas StringBuilder builder; // Aquí guardamos cada objeto categoria private ParsedCategoryDataSet DataSet; // Vector donde se guardaran todas las categorías encontradas private Vector<ParsedCategoryDataSet> myParsedCategoryDataSet; public Vector<ParsedCategoryDataSet> getParsedCategoryDataSets() { return this.myParsedCategoryDataSet; } public Vector<ParsedCategoryDataSet> getParsedData() { return this.myParsedCategoryDataSet; } @Override public void startDocument() throws SAXException { // Comenzamos a leer el fichero xml, creamos el vector donde se guardarán las categorías this.myParsedCategoryDataSet = new Vector<ParsedCategoryDataSet>(); } @Override public void endDocument() throws SAXException { // Ha terminado de leer el fichero, en este paso no hacemos nada } @Override public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException { if (localName.equals("category")) { // Ha encontrado la etiqueta principal de cada elemento "category" // Creamos un nuevo objeto categoría donde iremos guardando los datos this.in_category = true; DataSet = new ParsedCategoryDataSet(); }else if (localName.equals("id")) { // Estamos dentro de la etiqueta "id", creamos el StringBuilder que utilizaremos // en el método characters para guardar el contenido this.in_id = true; builder = new StringBuilder(); }else if (localName.equals("name")) { // Estamos dentro de la etiqueta "name", creamos el StringBuilder que utilizaremos // en el método characters para guardar el contenido this.in_name = true; builder = new StringBuilder(); } } @Override public void endElement(String namespaceURI, String localName, String qName) throws SAXException { if (localName.equals("category")) { // Hemos llegado al final de la etiqueta principal de cada elemento "category" // Añadimos al vector el elemento leído this.in_category = false; myParsedCategoryDataSet.add(DataSet); }else if (localName.equals("id")) { // Ha encontrado la etiqueta de cierre de "id" this.in_id = false; }else if (localName.equals("name")) { // Ha encontrado la etiqueta de cierre de "name" this.in_name = false; } } @Override public void characters(char ch[], int start, int length) { // Si estamos dentro de la etiqueta "id" if(this.in_id){ if (builder!=null) { for (int i=start; i<start+length; i++) { // Añadimos al StringBuilder (definido al encontrar el comienzo de la etiqueta "id") // lo que haya entre las etiquetas de inicio y fin builder.append(ch[i]); } } // Lo asignamos al "id" del objeto categoría (DataSet) DataSet.setId(builder.toString()); } // Si estamos dentro de la etiqueta "id" if(this.in_name){ if (builder!=null) { for (int i=start; i<start+length; i++) { // Añadimos al StringBuilder (definido al encontrar el comienzo de la etiqueta "name") // lo que haya entre las etiquetas de inicio y fin builder.append(ch[i]); } } // Lo asignamos al "name" del objeto categoría (DataSet) DataSet.setName(builder.toString()); } } } |
El código está comentado y creo que se explica bastante bien. Me consta que algunos programadores leen el contenido que se encuentra entre las etiquetas de inicio y cierre, cuando llegan a esta última etiqueta en lugar de en el método characters. Yo me he encontrado con muchos problemas haciéndolo así ya que se corre el riesgo de no conseguir leer todos los caracteres que realmente hay. La propia documentación del método lo explica:
The Parser will call this method to report each chunk of character data. SAX parsers may return all contiguous character data in a single chunk, or they may split it into several chunks; however, all of the characters in any single event must come from the same external entity so that the Locator provides useful information.
Ya tenemos casi todo lo necesario para leer el fichero XML de la API. Solamente nos queda implementar el método que parseará el archivo utilizando nuestra clase.
Esto solo es el extracto del método que lee el XML, encontraréis el código completo de la activity aquí, donde también se hace uso de la combinación de ProgressDialog y Thread para mostrar un mensaje de “cargando” mientras se leen los datos (este tema da para otro post
).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | ... ... private Vector<ParsedCategoryDataSet> categories; private String categories_url = "http://url_de_la_api/categories.xml"; ... ... public void loadCategories() { try { // Url del archivo XML URL url = new URL(categories_url); SAXParserFactory spf = SAXParserFactory.newInstance(); SAXParser sp = spf.newSAXParser(); XMLReader xr = sp.getXMLReader(); // Utilizamos nuestro propio parseador (CategoryHandler) CategoryHandler myExampleHandler = new CategoryHandler(); xr.setContentHandler(myExampleHandler); InputSource is = new InputSource(url.openStream()); // Le indicamos la codificación para evitar errores is.setEncoding("UTF-8"); xr.parse(is); // Asignamos al vector categories los datos parseados categories = myExampleHandler.getParsedData(); } catch (Exception e) { // Ha ocurrido algún error Log.e("Ideas4All", "Error", e); } } |
Y eso es todo, no olvidéis indicar en el AndroidManifest.xml solicitar permisos de acceso a Internet.
<uses-permission android:name="android.permission.INTERNET"></uses-permission> |
Podéis encontrar la aplicación completa en github:
Código fuente de la aplicación de Ideas4All para Android
Etiquetas: api, github, ideas4all, leer, parsear, progressdialog, sax, thread, xml
Una de las cosas más habituales cuando desarrollamos una aplicación para Android, es poder “enviar” datos (variables, objetos, etc.) desde una Activity a otra. En términos generales, una Activity es una “pantalla” de nuestra aplicación.
Para realizar esta tarea, Android pone a nuestra disposición los llamados “Intent“. En general, utilizaremos un Intent cuando queramos movernos de una actividad a otra, permitiéndonos a su vez pasar datos desde la Activity en la que estamos hacia la nueva.
1 2 3 4 5 6 | // Pasaremos de la actividad actual a OtraActivity Intent intent = new Intent(this, OtraActivity.class); intent.putExtra("variable_integer", objeto.getId()); intent.putExtra("variable_string", objeto.getNombre()); intent.putExtra("objeto_float", objeto.getPrecio()); startActivity(intent); |
- En la segunda línea creamos un Intent, al que se le pasa como parámetros la actividad actual (this) y la actividad a la que queremos pasar (en este caso OtraActivity.class).
- En las tres líneas siguientes definimos tres variables de tres tipos diferentes. En el primer caso, estaremos creando una variable llamada “variable_integer” con el contenido de objeto.getId(). Esta variable se “envía” a OtraActivity y podremos recuperar su contenido allí para utilizarlo.
- En la última línea, iniciamos la nueva actividad pasando como parámetro el Intent que hemos creado.
El código que viene a continuación es el que se ejecutará en la actividad OtraActivity. Se muestran dos formas de recuperar las variables, se puede utilizar la que más convenga en cada ocasión.
1 2 3 4 5 6 7 8 9 10 11 12 | // Estamos en OtraActivity int recuperamos_variable_integer = getIntent().getIntExtra("variable_integer"); String recuperamos_variable_string = getIntent().getStringExtra("variable_string"); float recuperamos_variable_float = getIntent().getFloatExtra("objeto_float"); // O también de esta otra forma // Estamos en OtraActivity Bundle datos = this.getIntent().getExtras(); int recuperamos_variable_integer = datos.getInt("variable_integer"); String recuperamos_variable_string = datos.getString("variable_string"); float recuperamos_variable_float = datos.getFloat("objeto_float"); |
Es importante que sepamos de que tipo son las variables que enviamos en el Intent, ya que deberemos asignar esos valores al tipo de variable correcto.
Etiquetas: activity, Android, bundle, datos, getExtras, getIntent, intent, variables
Como anunciaba en el post anterior (Me han suspendido la aplicación de Cercanías Renfe para Android) he liberado el código fuente de la aplicación Cercanías Renfe para dispositivos Android.
Así mismo, también se puede descargar el fichero .apk de la aplicación e instalarla directamente es vuestros móviles, tablets, etc.
El único requisito para utilizar el código fuente y crear tu propia aplicación, es que sea liberada como código abierto. Nada más.
El código no está excesivamente documentado, quizás haga un tutorial en mi blog explicando paso a paso como funciona la aplicación.
Una vez más, gracias a todos por los ánimos y el apoyo recibido. ¡GRACIAS!
Pd.: Si decides crear tu propia aplicación o reutilizar el código, deja un comentario!
Etiquetas: Android, apk, código abierto, github, open source, renfe, tutorial