TuLista 2.10

Hola de nuevo.

Ya han pasado bastantes días desde la última versión 2.9 pero aquí está la nueva versión 2.10!!

Novedades:  Se añade un panel de configuración con dos opciones:

– Se podrá deshabilitar el menú de edición de precio y cantidades cuando un producto es marcado en el panel principal. Además se deshabilita por defecto. Varios usuarios me han indicado que es un poco molesto.

– Se permite cambiar la imagen de fondo del listado de productos. Esta opción te permite seleccionar una imagen de la galería y ponerla de fondo. Estoy seguro que a muchos os gustará llevar vuestra propia imagen personalizada 😉

Espero que os guste la mejora.

Si tienes alguna sugerencia será bienvenida! 😀


Android app on Google Play

https://play.google.com/store/apps/details?id=es.blackpent.tulista

Usando MockCursor de Android

Estos dias me he decidido a implementar algunos tests en Android y una de las implementaciones que creo que ha merecido un post ha sido la implementación de un test que ha requerido la personalización de MockCursor, así que aquí está:

Contextualizo un poco:  La idea del test es probar un método que hace uso de un cursor el cual recibe por parámetro.  El método a probar utiliza este cursor para recorrer cada fila resultado y acceder a sus columnas.

El método a probar sería algo como el siguiente (prestad solo atención al uso del cursor)

private BigDecimal calcularTotal(Cursor cursor){
    BigDecimal total = new BigDecimal(0,MathContext.DECIMAL32);
    int unidades = 0;
    float precio = 0;
    if(cursor.moveToFirst()){
        if(DatabaseHelper.TRUE == cursor.getInt(cursor.getColumnIndex(ProductoDbAdapter.KEY_COMPRADO))){
            unidades = cursor.getInt(cursor.getColumnIndex(ProductoDbAdapter.KEY_UNIDADES));
            unidades = unidades <=1? 1:unidades;
            precio = cursor.getFloat(cursor.getColumnIndex(ProductoDbAdapter.KEY_PRECIO));
            if(precio > 0){
                total = total.add(new BigDecimal(unidades*precio));
            }
        }
        while(cursor.moveToNext()){
            if(DatabaseHelper.TRUE == cursor.getInt(cursor.getColumnIndex(ProductoDbAdapter.KEY_COMPRADO))){
                unidades = cursor.getInt(cursor.getColumnIndex(ProductoDbAdapter.KEY_UNIDADES));
                unidades = unidades <=1? 1:unidades;
                precio = cursor.getFloat(cursor.getColumnIndex(ProductoDbAdapter.KEY_PRECIO));
                if(precio > 0){
                    total = total.add(new BigDecimal(unidades*precio));
                }
            }
        }
    }
    return total;
}//Fin calcularTotal

Para testear este método de forma unitaria, es decir, aislada del resto, necesitamos que sea independiente del Cursor, y ahí es donde entra MockCursor para echarnos un cable. He de decir que MockCursor no es un JMock,  solo es una clase que implementa la interfaz  Cursor devolviendo UnsupportedOperationException para cada llamada de sus métodos., por lo tanto para usarlo hay que sobreescribir los métodos que vamos a usar.

Otro punto a destacar es que el método a probar es privado y no podremos probarlo tal cual. Mas adelante veremos como.

Bien, para probar nuestro método debemos personalizar el MockCursor, es decir, extenderlo y personalizar los métodos que queremos usar. Hay varias formas de hacerlo, una clase anónima, una clase anidada, etc. El lector que decida que solución es la que mas le convence.  Yo usaré una clase anidada al test. La idea de la implementación es simular el recorrido por los resultados obtenidos por una supuesta consulta y devolver los valores cuando se acceda a las columnas de cada resultado. El método a probar solo accede a tres columnas, así que no es demasiado complicado.

public class MockCursorAdapted extends MockCursor{
        int actualIndex = 0; //Indice para el recorrido por los resultados
        Map<Integer,String> entry;
        List<Map<Integer,String>> entryList;
        Map <String,Integer> columnIndexes;
        //Value initialization
        {
            //Column indexes: Se configuran las posiciones de las columnas en función de sus nombres.
            columnIndexes = new HashMap<String,Integer>();
            columnIndexes.put(ProductoDbAdapter.KEY_UNIDADES, 0);
            columnIndexes.put(ProductoDbAdapter.KEY_PRECIO, 1);
            columnIndexes.put(ProductoDbAdapter.KEY_COMPRADO, 2);
            //Creación de los resultados. Se crea una lista de mapas. Cada entrada en la lista es
            //un resultado. Y cada resultado, es un mapa con los valores de cada columna.  
            entryList = new ArrayList<Map<Integer,String>>();

            Map<Integer,String> m = new HashMap<Integer,String>();
            m.put(getColumnIndex(ProductoDbAdapter.KEY_UNIDADES),"1");
            m.put(getColumnIndex(ProductoDbAdapter.KEY_PRECIO),"22.40");
            m.put(getColumnIndex(ProductoDbAdapter.KEY_COMPRADO),"1");
            entryList.add(m);

            m = new HashMap<Integer,String>();
            m.put(getColumnIndex(ProductoDbAdapter.KEY_UNIDADES),"2");
            m.put(getColumnIndex(ProductoDbAdapter.KEY_PRECIO),"5.50");
            m.put(getColumnIndex(ProductoDbAdapter.KEY_COMPRADO),"1");
            entryList.add(m);

            m = new HashMap<Integer,String>();
            m.put(getColumnIndex(ProductoDbAdapter.KEY_UNIDADES),"1");
            m.put(getColumnIndex(ProductoDbAdapter.KEY_PRECIO),"100.0");
            m.put(getColumnIndex(ProductoDbAdapter.KEY_COMPRADO),"1");
            entryList.add(m);
        }

        @Override
        public String getString(int columnIndex) {
            return getValue(columnIndex);
        }

        @Override
        public float getFloat(int columnIndex) {
            return Float.parseFloat(getValue(columnIndex));
        }

        @Override
        public int getInt(int columnIndex) {
            return Integer.parseInt(getValue(columnIndex));
        }

        /**
         * Obtención del valor de la columna indicada para la fila actual
         * @param columnIndex
         * @return
         */
        private String getValue(int columnIndex){
            entry = entryList.get(actualIndex);
            String value = entry.get(columnIndex);
            if(value == null){
                Log.e(TAG, "Sin valor para columna "+columnIndex+" y actualIndex "+actualIndex+" entrada "+entry.toString());
            }
            return value;
        }

        @Override
        public int getColumnIndex(String columnName) {
            return columnIndexes.get(columnName);
        }

        @Override
        public boolean moveToFirst() {
            return true;
        }

        @Override
        public boolean moveToNext() {
            // TODO Auto-generated method stub
            return ++actualIndex < entryList.size();
        }
    }//FIN MockCursorAdapted

Bien, ya tenemos el Mock del cursor personalizado. Creando la clase que ejecutará el test ya lo tendriamos resuelto… bueno… falta el acceso al método privado…

Bien, la forma de ejecutar el método privado es mediante reflexión, digamos que le pedimos a la clase que nos de el método que tiene dicho nombre. De forma muy resumida y sin controlar excepciones:

 Cursor cursorAdapted = new MockCursorAdapted();
 ActividadConMetodoCalcularTotal mActivity = new ActividadConMetodoCalcularTotal();
 Method m = this.mActivity.getClass().getDeclaredMethod("calcularTotal", new Class[]{Cursor.class});
 m.setAccessible(true); //Le decimos que nos deje ejecutarlo desde fuera 😛
 BigDecimal resultado = (BigDecimal) m.invoke(mActivity , new Object[]{cursorAdapted});

Listo!

Un apunte mas:

OJO! MockCursor está disponible a partir de la API 8, por lo que debeis ejecutar los test en un emulador con la versión correspondiente (creo que a partir de la 2.3.3). Si no lo haceis, dará un error en ejecución de NoClassDefFoundError sobre la clase personalizada (en este caso $MockCursorAdapted). Mirando mas detenidamente las trazas se puede ver al principio un warning “unable to resolve superclass” refiriendose a MockCursor… este error me ha tenido ocupado un buen rato.

Espero que os haya gustado la aportación, suerte con los test!

TuLista 2.9 Ahora con productos creados por defecto

Esta versión insertará unos cuantos productos por defecto en el histórico cuando se instala, así los nuevos usuarios no tendrán que comenzar desde cero.

Si tienes pocos productos en tu lista e histórico puedes desinstalarla e instalarla de nuevo. Ojo, perderás todos los datos que ya hayas añadido.

Además se cambia la ordenación de la pantalla principal.  Antes se ordenaba por categoría y luego por tachados, ahora se ordena primero por tachados y luego por categorías. Así quedan los productos no tachados primero y luego los tachados.  Así no es tanto lio hacer el seguimiento de los productos que ya has añadido a tu cesta.

Espero que os guste la mejora.

El malware crece mas de un 3000% en sistemas Android

Como era de esperar, un sistema abierto y con tan poco control como Android era un lugar donde pueden campar a sus anchas los creadores de virus, troyanos, malware en general. Cierto es que es el usuario final es el que acepta instalar las aplicaciones maliciosas y aceptan darle los permisos que pide sin cuestinárselo, pero hay que ponerse en lugar de los usuarios finales y la mayoría nos confiamos facilmente.

A mediados de febrero de 2012, Juniper Networks publicaba un informe donde decía que el crecimiento de aplicaciones maliciosas para android habia crecido de una forma terrible. Una gran parte de ellas son publicadas en portales alternativos de descarga, y otra gran parte, no tanto se publican el market oficial.

Por dar algunas cifras:

De junio a diciembre de 2011 el número de aplicaciones infectadas de las analizadas por Juniper crecio en mas de un 3300% … Para muestra una grafica

Esta claro que es algo serio…

¿Qué hacer?

Revisar los permisos que pide: ¿Qué sentido tiene que una aplicación que es un juego quiera acceder al envio de SMS? ¿Y a tu agenda de contactos?

Revisar los comentarios de los usuarios.

Instalar aplicaciones de fuentes en las que confies, aunque, con tanto crecimiento hay pocos fabricantes conocidos. Yo no soy conocido :P.

En resumen, desconfia de portales alternativos, revisa bien los permisos que solicita y si te hace sospechar busca información y si no lo ves claro no instales la aplicación.

Un saludo!

Error “NoClassDefFoundError” con ADT 17

Acabo de actualizar el plugin de eclipse y me he econtrado que no se cargaban las librerias de admob y daba un error.

ComponentInfo{es.blackpent.tulista/es.blackpent.tulista.ListaCompraActivity}: android.view.InflateException: Binary XML file line #17: Error inflating class com.google.ads.AdView
04-02 19:10:51.122: E/AndroidRuntime(212)

Al parecer, la nueva versión del plugin cambia el tratamiento de las librerias añadidas al proyecto.

Este cambio ha provocado o producido dos efectos colaterales:

Las librerias que antes habíamos añadido a la carpeta lib ya no son localizadas ya que ahora la carpeta ha de llamarse libs. Tendrás que crear dicha carpeta y mover las librerias.

Las librerias añadidas en el buildpath de eclipse no son publicadas junto con la aplicación dando fallos al instalarla o ejecutarla en el emulador, (aunque durante el build compile bien).  En este caso, tienes dos opciones, añadirlas a libs o  indicando en las opciones de configuración de Java BuildPath que se exporten. Yo aconsejo la segunda.

Mas info aquí: http://android.foxykeep.com/dev/how-to-fix-the-classdefnotfounderror-with-adt-17

TuLista 2.6

Esta actualización me ha llevado mas tiempo, pero ya está aquí recién salida del horno!

En esta actualización añade:

  • Categorización de productos, que se muestran agrupados en el listado.
  • Editor de productos avanzados (hay que asignar la categoría 😉 )
  • Se mejora el aspecto de la aplicación.

Espero que os guste!

TuLista:  https://market.android.com/details?id=es.blackpent.tulista

http://qrcode.kaywa.com/img.php?s=5&d=https%3A%2F%2Fmarket.android.com%2Fdetails%3Fid%3Des.blackpent.tulista