ProgressCircular 1.0.0 en Maven

 

Finalmente uno de mis objetivos de este año (arrastrado del anterior) ha sido cumplido. Publicar una librería de código abierto en Maven.

La importancia para mí, era primero, aprender el proceso de publicación en este repositorio y segundo contribuir con librerías para el desarrollo en Android. Por ahora esta pequeña librería solo aporta un widget para mostrar el progreso de una manera diferente, usando circulos.

La idea se originó de un diseño solicitado para un cliente en mi trabajo. Pero la pantalla donde estaba sufrió cambios y al final se deshechó la idea. Para ese momento yo ya había implementado el widget y me pareció un desperdicio dejarlo en el olvido y me propuse publicar el widget.

Para empezar me parece adecuado que sea algo sencillo. Así puedo irlo mejorando o inclusive atender mejor cualquier solicitud de cambio o reporte de “pulgas”.

Entre los pendientes para esta librería estan “promover” el proyecto a release, así ya queda listo para producción. Basicamente es subir una versión firmada del mismo (ahorita esta como debug). También subir al PlayStore o en este sitio, una aplicación de ejemplo de como se ve y como utilizar el widget. También tengo pendiente algunas mejoras a nivel de “pintado” del widget.

Si alguno está interesado en utilizarlo, pueden dirigirse al repositorio y ver el código fuente. Aquí se explica como agregar la libería (utilizando Maven mediante Gradle claro está) a sus proyectos. El repositorio lo pueden encontrar aqui: https://github.com/fr4gus/ProgressCircular

Problemas Guava + Proguard

Para los que vieron los post sobre usar Google App Engine Endpoints para un App en Android (sí, yo sé que debo la parte final) a la hora de generar un APK firmado, puede que se topen con una excepción con Proguard. Esto pasa por culpa de una de las librerías, en este caso Guava.

Los warnings que tira Proguard son respecto a dos clases: sun.misc.Unsafe y com.google.common.collect.MinMaxPriorityQueue

Esto se arregla agregando las siguientes directivas en su archivo de proguard:

-dontwarn sun.misc.Unsafe
-dontwarn com.google.common.collect.MinMaxPriorityQueue

Parte 1: Android + AppEngine + Cloud Datastore

Introducción

La aplicaciones móviles van evolucionando y ahora más que nunca deben ofrecer valor agregado con la utilización de la nube o inclusive delegar a servicios externos tareas más complejas. Actualmente existen diversas opciones para proveer a las aplicaciones de poder computacional en la nube y en este tutorial se utilizarán las herramientas que Google facilita: Google App Engine y Cloud Datastore.

Google App Engine

Antes del boom de los servicios en “la nube”  los desarrolladores o las empresas requerían mantener su propia infraestructura. Ahora existen muchas compañias que ofrecen su propia infraestrucutra y proveen de diferentes frameworks para poder instalar aplicaciones sin preocuparse del como.

Google ofrece su infraestrucura a través de App Engine que es una plataforma como servicio (PaaS del inglés Platform as a Serviced) que permite construir aplicaciones y correrlas en dicha infraestrucutra. GAE se encuentra disponible en los sabores: Java, Python, PHP y Go. El servicio es gratuito hasta cierta “cuota”. Las cuotas se cuentan por “solicitudes por día”, y en el momento que su aplicación sobrepase dicha cuota, deberá pagar. Para más informacion sobre GAE puede consultar la documentación oficial (1).

Cloud Data

Google ofrece 3 servicios para almacenamiendo de datos. La primera es Cloud SQL que es prácticamente un MySQL pero hospedado en la infraestructura de Google. La segunda Google Storage que es más bien para almacenamiento de archivos (tipo Drive) y finalmente Cloud Datastore para almacenar datos no relacionales (NoSQL).

El propósito de este tutorial es condensar la información de diferentes fuentes y aglomerar los pasos necesarios para poder proveer de backed a una aplicación Android utilizando Google App Engine (GAE).

Requerimientos:

  • Conocimientos básicos de desarrollo de aplicaciones Android
  • Conocimientos básicos de desarrollo de aplicaciones JavaEE (Servlets, JSP).

Parte 1. Aplicación Android

Si ya cuenta con una aplicación Android y desea integrarla con GAE puede pasar a la parte 2 (si no tiene una aplicación web en GAE). Se creará una aplicación sencilla para ayudar con el ejemplo.

En este caso se hará una aplicación para guardar “notas” que no es más que un simple texto que luego podemos visualizar en la Web.

La actividad para guardar notas tendrá el siguiente layout:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                xmlns:tools="http://schemas.android.com/tools"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:paddingLeft="@dimen/activity_horizontal_margin"
                android:paddingRight="@dimen/activity_horizontal_margin"
                android:paddingTop="@dimen/activity_vertical_margin"
                android:paddingBottom="@dimen/activity_vertical_margin"
                tools:context=".NotesActivity">

    <TextView
        android:id="@+id/add_note_description"
        android:text="@string/add_note_description"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

    <EditText
        android:id="@+id/note_content"
        android:layout_below="@+id/add_note_description"
        android:layout_width="match_parent"
        android:layout_above="@+id/action_save"
        android:layout_height="match_parent"/>

    <Button
        android:id="@+id/action_save"
        android:text="@string/add_note"
        android:layout_alignParentBottom="true"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
</RelativeLayout>

Y se verá de la siguiente manera:

Save Notes Layout

El código de la actividad se verá de momento de la siguiente manera:

import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;

public class NotesActivity extends ActionBarActivity implements View.OnClickListener {
    EditText mNoteContent;
    Button mSaveNote;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_notes);

        mNoteContent = (EditText) findViewById(R.id.note_content);
        mSaveNote = (Button) findViewById(R.id.action_save);
    }

    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.action_save) {
            String noteContent = mNoteContent.getText().toString();

            // TODO call save on backend

            // TODO when save is finished clear the textview and show a toast message
        }
    }
}

Parte 2. Servicios en GAE

En Android Studio se debe agregar un nuevo módulo, que contendrá la implementación del backend de la aplicación. Simplemente vaya a File -> New Module y seleccione el módulo “App Engine Java Endpoints Module”. Si desea agregar Google Cloud Messaging, seleccione la opción anterior.

new_backend_module

Esto va a crear un nuevo módulo en el proyecto. Otro de los efectos es que el build.gradle del módulo de la aplicación de Android va a ser modificado con la siguiente línea:

 dependencies {
    ...
    compile project(path: ':backend', configuration: 'android-endpoints')
}

Esto significa que el módulo de Android (app en el ejemplo) va a poder utlizar al módulo de GAE (backend en el ejemplo) como si tuviera las librerías de cliente. Si estuvieran los módulos en proyectos separados o utilizando otra herramienta distinta de Android Studio o más bien sin gradle, se deben compilar las librerias clientes. Esto genera unos archivos zip, uno por cada endpoint registrado y el codigo fuente hay que agregarlo a nuestro proyecto de Android. ¿Tedioso verdad? Por eso es mejor tenerlo así en Android Studio.

A la fecha que se escribió este tutorial, había un pequeño problema con la plantilla de módulos de GAE en Android Studio. Yo tuve un problema luego de crear el módulo y es por un problema con el plugin de appengine. Si se fija en el build.gradle del módulo, vera un comentario con una nota sobre esto. Para resolverlo (bajar el sdk y las respectivas dependencias) simplemente deben correr en una terminal el siguiente comando en la raíz del proyecto: ./gradlew [modulename]:appengineRun. En este caso sería ./gradlew backend:appengineRun. Si no tienen el sdk de App Engine o las librarías, con este comando lo podrán obtenerlo.

El módulo GAE viene con un codigo tipo plantilla, para ejemplificar el uso del módulo. Vamos a crear nuestro ejemplo y explicar las anotaciones en conjunto para ahorra tiempo.

Agregar y listar

Vamos a hacer dos endpoints. Un endpoint para poder salvar notas y listarlas. Otro endpoint para registrar usuarios. Para simplificarlo un poco, vamos a hacerlo anónimo, y de auto registro utilizando un identificador único por dispositivo.

Para no hacer muy largo el ejemplo vamos a utilizar en nuestro App solo el de insertar, pero podemos usar el de listar para corroborar que los datos están siendo guardados utilizando el dashboard admin de la instancia en GAE.

Primero hay que crear las clases del modelo que van a ser los DTOs (Data Transfer Objects) y que representan la información que vamos a almacenar. En nuestro caso ocupamos dos. User y Note. User va a tener un id único en el sistema y el device_id. Note va a tener un string con el contenido de la nota, un id único, la fecha de creación y el id interno del usuario.

Para hacer las cosas fáciles Android Studio cuenta con un comando para crear endpoints. Nada más seleccionamos la clase a la cual queremos hacer el endpoint y le damos “Generate Endpoints“:

generate_endpoint

Esto generará una clase llamada <nombre-clase>Endpoint. Por ejemplo si lo hacemos para User, será UserEndpoint. Y se verá similar a esto:

/** An endpoint class we are exposing */
@Api(name = "userEndpoint", version = "v1", namespace = @ApiNamespace(ownerDomain = "savenotes.backend.fr4gus.com", ownerName = "savenotes.backend.fr4gus.com", packagePath=""))
public class UserEndpoint {

    // Make sure to add this endpoint to your web.xml file if this is a web application.

    private static final Logger LOG = Logger.getLogger(UserEndpoint.class.getName());

    /**
     * This method gets the <code>User</code> object associated with the specified <code>id</code>.
     * @param id The id of the object to be returned.
     * @return The <code>User</code> associated with <code>id</code>.
     */
    @ApiMethod(name = "getUser")
    public User getUser(@Named("id") Long id) {
        // Implement this function

        LOG.info("Calling getUser method");
        return null;
    }

    /**
     * This inserts a new <code>User</code> object.
     * @param user The object to be added.
     * @return The object to be added.
     */
    @ApiMethod(name = "insertUser")
    public User insertUser(User user) {
        // Implement this function

        LOG.info("Calling insertUser method");
        return user;
    }
}

Es importante verificar que el endpoint se encuentre registrado en el archivo web.xml que se encuentra por lo general aqui:

verify_web_xml

Se debe verificar que el endpoint se registró. Si no es así, agregarlo manualmente para hacerlo visible.

verify_endpoint

Para probar rápidamente el endpoint, vamos primero a crear un almacenamiento en memoria con un HashMap. En getUser vamos a hacer un get del map y en insertUser vamos a hacer put. Además vamos a agregar un nuevo método en el endpoint, para poder listar los uuarios registrados. La clase UserEndpoint tendrá las siguiente modificaciones.

1. Agregar el map para almacenamiento en memoria. Propiedades de la clase mLocalUsers y mLastId. La variable mLastId nos servira como id autoincremental de los usuarios.

    static Map<Long, User> mLocalUsers = new HashMap<Long, User>();
    static long mLastId = 0;

2. Modificar gerUser para obtener el usuario del map.

        User user = mLocalUsers.get(id);
        LOG.info("Returning user with id " + user.getDeviceId());

3. Modificar insertUser para guardar el user en el map.

        synchronized (mLocalUsers) {
            mLastId++;
            user.setId(mLastId);
            mLocalUsers.put(mLastId, user);

        }

4. Agregar el método listUser para listar los usuarios del map.

    @ApiMethod(name = "listUsers", path = "user/list")
    public List<User> listUser(){
        ArrayList<User> users = new ArrayList<User>();
        users.addAll( mLocalUsers.values());
        return users;
    }

Como notarán, listUser tiene un parámetro extra en la anotación. “path” esta redifiniendo la ruta por defecto para acceder este endpoint. Esto se debe a que sin él, getUser y listUser van a tener la misma firma a nivel de servicio. Van a ser endpoints con la misma ruta “user/” que por medio de GET van a traer objetos tipo User, por lo que va a crear un conflicto. Es por ello que es necesario modificar la ruta del list.

Si recuerdan la nota sobre el issue con el plugin de GAE y Android Studio, ejecutamos el commando ./gradlew backend:appengineRun. Este comando hace levantar la instancia local de GAE. Si acceden a http://localhost:8080/ les aparecera una página como la siguiente:

 gae_landing

Dentro de las opciones en el menú superior, hay uno que nos interesa que es el Google Cloud Endpoints API Explorer. Con este explorador pueden hacer llamados a los endpoints y probarlos directamente. Por ejemplo podemos meter un usuario:

add_user

Que corresponde al siguiente request y response.

add_user_request_response

Como ven, desarrollar para Google App Engine es relativamente sencillo. Recuerden que hay disponibles otros lenguajes si Java no es de su preferencia, pero como este tutorial está orientado para Android Devs, parece adecuado hacer la implementación en Java también.

Para la segunda parte se hará la integración con el app de Android y posteriormente como extra, como utilizar Google Objectify para acceder al Cloud Datastore de una manera sencilla.

Nine Patch Remotos

Una de las herramientas más útiles dentro del arsenal del framework de Android son las imagenes nine-patch. Estas permiten a una imagen adaptarse a su contenedor para una misma densidad. El ejemplo por excelencia son los botones. Si se utiliza una imagen para el boton, por lo general es una imagen con un tamaño predeterminado. Esta imagen tal vez se vea muy bien cuando el dispositivo se encuentra en posicion vertical, pero cuando se gira el dispositivo, el boton mantiene su tamaño y no se “estira”.

Button Landscape

Boton en orientacion horizontal

Button Portrait

Boton en orientacion vertical

 

Cuando se sabe cual imagen se va a utilizar, esta imagen es alojada junto con el resto de la aplicaicon en el APK. Se puede utilizar una herramienta del mismo SDK para crear las marcar necesarias para indicarle al sistema operativo que partes de la imagen deben estirarse e inclusive cual va a ser la zona disponible para albergar contenido.

Boton con nine patch

Todo muy bien hasta aquí. Pero ¿Qué pasaría si se ocupa obtener un boton de manera remota? Es decir, al momento de hacer la aplicación, aún no es posible saber como va a ser el botón, por lo que no sería posible usar la herramienta anterior.

“Compile” vs “Source”

Cuando se crea un nine-patch con la herramienta, esto genera un archivo png, con la terminación 9.png. Este archivo es similar al original, pero tiene una transparencia de 1px con ciertas marcas. Cuando se compila el proyecto, esto genera por debajo el “chunk” metadata necesario para poder manipular la imagen en el UI.

La clase Bitmap tiene un metodo llamado getNinePatchChunk que permite obtener ese metadata. La clase NinePatch puede verificar si el metadata es válido y de ser así, se puede hacer un NinePatchDrawable.

 
InputStream stream = .. //whatever Bitmap bitmap = BitmapFactory.decodeStream(stream);
byte[] chunk = bitmap.getNinePatchChunk();
boolean result = NinePatch.isNinePatchChunk(chunk);
NinePatchDrawable patchy = new NinePatchDrawable(bitmap, chunk, new Rect(), null); 

Para poder hacer esto funcional, en un escenario con la imagen remota, se ocuparía que el servidor pueda enviar una versión compilada de la imagen. Esto presenta el problema de que no existe una herramienta oficial para “compilar” un nine-patch. Si bien existen herramientas de terceros que lo permiten, pueden haber situaciones donde esto no sea posible.

Este fue el caso que se presentó recientemente, donde se solicitó soportar nine-patch para unos botones, cuyas imagenes venian de manera remota. Para ello se creo un drawable nuevo que pudiera emular el nine patch. Eso si, debe recibir los componentes por separado. Por ahora no es posible recibir una imagen con las marcas de la herramienta del nine-patch y solo soporta 3 parches, aunque en un futuro se planea agregar soporte para los 9 en total.

La parte mas importante de este componente es el método draw. Que simplemente pinta los parches de los bordes (izquierda y derecha) y replica el parche del centro tantas veces como sea necesario para llenar el espacio.

	@Override
	public void draw(Canvas canvas) {
		int left = 0;
		int mTopRightWidth = 0;

		if (mTopLeftBG != null) {
			Rect dst = new Rect(0, 0,
					(int) (mTopLeftBG.getWidth() * mDensityScale),
					(int) (mTopLeftBG.getHeight() * mDensityScale));

			canvas.drawBitmap(mTopLeftBG, null, dst, mPaint);
			left = (int) (mTopLeftBG.getWidth() * mDensityScale);
		}

		if (mTopRightBG != null) {
			Rect dst = new Rect(getBounds().right
					- ((int) (mTopRightBG.getWidth() * mDensityScale)), 0,
					getBounds().right,
					((int) (mTopRightBG.getHeight() * mDensityScale)));

			canvas.drawBitmap(mTopRightBG, null, dst, mPaint);
			mTopRightWidth = (int) (mTopRightBG.getWidth() * mDensityScale);
		}

		if (mTopCenterBG != null) {
			int bitmapScaledWidth = (int) (mTopCenterBG.getWidth() * mDensityScale);
			for (int i = left; i < getBounds().right - mTopRightWidth; i += bitmapScaledWidth) {
				Rect dst = new Rect(i, 0, i + bitmapScaledWidth,
						(int) (mTopCenterBG.getHeight() * mDensityScale));
				canvas.drawBitmap(mTopCenterBG, null, dst, mPaint);
			}
		}
	}

Para poder soportar diferentes densidades, se requiere que la imagen sea para la densidad máxima (en este momento xxhdpi), por ejemplo una imagen que tenga 144px de alto. A partir de ahi a la hora de recibir la imagen se escala hacia abajo deacuerdo a la densidad del dispositivo.

Próximamente voy a publicar un proyectos en github de ejmplo y el código fuente del PatchDrawable.

Código de FireNFC liberado

Hace unos meses, publiqué una aplicación, que era una prueba de concepto sobre el uso de NFC en juegos interactivos.

Mi idea era luego publicar el código, ojalá, presentable, pero debido a que estoy en otros proyectos y actividades, prefiero compartir el código de una vez, por si alguien lo encuentra útil para desarrollar una nueva idea.

El repositorio se encuentra en Bitbucket: https://bitbucket.org/fr4gus/firenfc/overview

Pueden bajar el Juego aquí.

Get it on Google Play