sábado, 21 de abril de 2012

Introducción a Google Apps Script.


Desarrollo sobre la plataforma Google Apps:


Hola a todos, les voy a hablar un rato sobre Google Apps Script, esta va a ser una simple introducción o mas bien demostración, sobre la enorme gama de posibilidades que posee esta enorme herramienta. Ya que en un principio nos resulta difícil darnos cuenta de la magnitud que posee, la mejor manera de ver esto; es probándolo, que es lo que me ocurrió a mi. Una vez que realizaste el primer script, te diste cuenta lo sencillo que resulto, el tiempo y comodidad que te dio a cambio, la verdad que es un poco difícil dejar de utilizarlo.


Que es?

Google Apps Script es un lenguaje de scripting basado en  la nube que ofrece maneras fáciles de automatizar las tareas a través de productos de Google y servicios de terceros. No es nueva, ya que hace del 2009 que se libero y  esta herramienta ya esta muy madura .

Ventaja:

Basado en JavaScript, sintaxis muy similar y fácil de aprender.

Bajo el capó, Google Apps Script utiliza el Google Web Toolkit (GWT) para crear y mostrar los elementos de la interfaz de usuario. Google Web Toolkit es fácil de aprender, y completamente abstrae la complejidad de AJAX / HTML.

Basado en la nube, tenemos las herramientas de desarrollo como el debbuguer en el propio navegador web, todo de manera online.
Puede ser utilizado para crear herramientas sencillas para el consumo interno de una organización, realizar tareas simples de administración del sistema, así como tareas mas complejas, pero de forma muy sencilla.

Con Google Apps de Script se puede por ejemplo:
- Automatizar los procesos repetitivos y flujos de trabajo.
- Crea scripts para automatizar las aprobaciones de gastos, gestión de la compra de entradas de pedidos, etc.
- Enlace productos de Google con los servicios de terceros. Un script puede enviar correos electrónicos personalizados y una invitación a una lista de calendario a partir de una base de datos MySQL.
- Crear funciones personalizadas de hoja de cálculo. Aplicar el formato personalizado o realizar análisis complejos de datos en hojas de cálculo de Google Docs.
- Construir ricas interfaces gráficas de usuario y los menús. Por ejemplo: una compañía podría dar energía a una aplicación interna para la compra de suministros de oficina, donde los usuarios pueden comprar a través de una interfaz de menú personalizado.
- Integración con todos los productos de Google como por ejemplo: Integrar con los albunes de Picasa Web, publicar de manera automática los eventos de Google Calendar en nuestro sitio web. Podemos incrustar nuestro App Script en nuestro Google Site, mediante Apps Script Gadget.


Gran ventaja de como esta licenciado:


- Licenciado bajo CCA 3.0 y Apache 2.0.
- Por ese motivo "casi todo", es de uso libre y gratuito. Esta exepción se debe a que en algunos casos, una página puede incluir contenido, como una imagen, que no está cubierto por la licencia. Así que Con excepción de lo señalado , el contenido de esta página está licenciado bajo la Creative Commons Attribution 3.0 License , y ejemplos de código están registrados bajo la licencia Apache 2.0 .

Para que esto no se paresa a una simple propaganda, creo que la mejor manera de tratar de demostrar lo que digo, es dando un par de ejemplo, sobre algunas de las cosas mas sencillas que podemos automatizar.


Primer Ejemplo:


Primero lo que debemos hacer por supuesto es tener una cuenta de Gmail, para acceder a Google Docs por supuesto jeje. Dentro de Google Docs una de las utilidades que tenemos, ademas de los archivos de texto y hojas de calculo, son los formularios, estos resulta de bastante utilidad a la hora de organizar reuniones (como estas) o recaudar por ejemplo información de un grupo de personas. Simplemente debemos crear un Formulario:



Donde podemos poner la cantidad de campos que necesitemos, pudiendo también especificar si alguno es obligatorio u opcional, agregar algún texto para explicar el objetivo del formulario. Compartirlo a nuestros contactos, o también enviar el formulario a las redes sociales o web que queramos. Y al rellenar el formulario, los datos se irán acumulando en una hoja de calculo asociada al mismo.
Ahora bien, se preguntaran donde ingresarían o para que los scripts, pues supongamos que no deseamos estar revisando cada tanto si alguien relleno o no el formulario, pues con el siguiente script obtendremos la solución de una manera muy sencilla. Y además ni siquiera necesitaremos desarrollar el script por completo. Para eso necesitaremos ingresar a la hoja de calculo asociada al formulario, vamos a Herramientas - Galería de secuencia de comandos (galería de scripts ;)).



Y para el siguiente ejemplo vamos a buscar Contact Us, (comentario, como verán, ya existe una gran cantidad de scripts, y además nosotros podremos compartir los que queramos). Una vez elegido el script le damos a install, le damos los permisos necesarios, y ahora tenemos que ingresar al editor de scripts ( o secuencia de comandos) donde podremos ver y editar el scripts que instalamos, o podremos escribir uno propio.
En este caso, simplemente con editar, la dirección de correo donde queremos que lleguen las actualizaciones de cada vez que alguien rellena el formulario, y personalizar las siguientes frases.



Ahora guardamos el script, y debemos activarlos, para eso vamos a la opción, activadores:



Y le damos click a Here to add one now, y modificamos la ultima opción para que nos notifique cada vez que alguien llena y envía el formulario. Ahora lo que debemos hacer el guardar la autorización y darles los permisos que nos pide.



Ahora podemos hacer unas pruebas ;) enviamos el formulario.



Supongamos que alguien lo rellena y lo envía. Y como pueden observar, acaba de llegar la notificación, de que un formulario se ah rellenado.




Segundo Ejemplo:

Este segundo ejemplo puede llegar a ir de la mano del primero, ya que en el primero deberíamos haber conseguido una gran lista de mail en la hoja de calculo, pues con el siguiente ejemplo vamos a poder enviar mail desde la misma lista ;)
Para ello lo que vamos a hacer, es ir a nuestro Google Docs, y esta vez crear una hoja de calculo, y en la primera linea vamos a ir agregando los campos que creamos necesarios, por ejemplo: nombre, apellido, mail, dirección, etc, etc.



Y agregamos los datos (aclaro que tranquilamente se puede utilizar la hoja de calculo con los datos que tiene la que ya creamos con el formulario ;) ) Ahora tenemos dos opciones, que son las que les pasare a mostrar, una y la mas sencilla es utilizar algún script ya creado por otra persona. Para ello vamos a herramientas, galería de scripts, y en este caso vamos a buscar FormEmailer, le damos install, y debemos autorizarlo, como hicimos con el otro script.

Ahora como podemos observar, tenemos una nueva opción en el menú llamada FormEmailer, ahora lo abrimos y le damos install, una vez realizado esto, nos creara otra hoja de calculo y como se puede ver, en nuestra hoja, se habrá añadido una nueva columna. 

Ahora, yendo nuevamente al menú FormEmailer, y dándole click a setting nos saldrá nueva ventana, donde podemos configurar el sujeto del mail, el mensaje, y varios datos mas. Si vamos a nuevamente a su menú, elegimos process manually, le podemos decir a la persona que queremos enviarle el mail con solo pasarle la fila, y además en nuestra hoja de calculo, quedara asentado a quienes ya se les mando el mail.

Otra opción seria por ejemplo decir el numero de fila seguido de un *, eso hará que se les envié un mail desde la fila que seleccionamos, hasta el final de la lista.


Pasemos a lo interesante : veamos código.

Ejemplo bien simple para enviar un mail:

function sendEmail(){
MailApp.sendEmail('rosario-gtug@googlegroups.com', '¡Hola!', 'Este mensaje se ha enviado desde Google Apps Script; ¿no es maravilloso?');
}

Como verán, con la API de Google, utilizando MAilApp.sendEmail(); simplemente hay que pasarle como parámetros el mail a quien va dirigido, el asunto del mismo, y el texto que se corresponde al mensaje. Con esta simple función, ya estaríamos mandado un mail con solo llamar a la función ;) Ahora vamos a verlo desde una hoja de calculo.

Sending emails from a Spreadsheet


Para no extendernos demasiado vamos a dar un ejemplo corto, donde simplemente enviaremos unos mail a partir de una hoja de calculo, y analizaremos el código utilizado, y veremos lo sencillo de entender que nos resultara la sintaxis.

Código:

var EMAIL_SENT = "EMAIL_SENT";

function sendEmails2() {
var sheet = SpreadsheetApp.getActiveSheet();
var startRow = 2; // Primera linea de información a procesar
var numRows = 2; // Segunda linea de información a procesar
// El rando de celdas será A2:B3
var dataRange = sheet.getRange(startRow, 1, numRows, 3)
// Se vera los valores de cada linea del rango
var data = dataRange.getValues();
for (var i = 0; i < data.length; ++i) {
var row = data[i];
var emailAddress = row[0]; // Primer columna
var message = row[1]; // Segunda columna
var emailSent = row[2]; // Tercer Columna
if (emailSent != EMAIL_SENT) { // Acá prevenimos el no reenvío de mails
var subject = "Sending emails from a Spreadsheet";
MailApp.sendEmail(emailAddress, subject, message);
sheet.getRange(startRow + i, 3).setValue(EMAIL_SENT);
// Nos aseguramos de que la celda fue actualizada, sino interrumpimos el envió
SpreadsheetApp.flush();
}
}
}

Analisis:

Como verán, una e imagino que ya se abran dado cuenta, a esta herramienta, lo que realmente la hace poderosa es la capacidad que tiene la comunidad para compartir código, y además darnos la posibilidad de mejorarlo o adaptarlo hacia nuestras necesidad, con las facilidades que nos brindan desde la pagina oficial con una excelente documentación de toda la API :

Además de darnos una gran cantidad de ejemplos:

Y de poseer un sistema muy simple para poder publicar nuestros script para ayudar a que siga creciendo la comunidad y facilitar mas todavía las tareas de automatización.
Esto es todo por ahora sobre esta herramienta, al que asistió a la presentación del Lanzamiento Gtug Argentina, podrá recordar gran parte de estas palabras, así que en los próximos que hable sobre el tema, vamos a profundizar mas en su funcionamiento. Espero que les allá gustado, hasta la próxima.

Saludos a todos, Gabriel E. Pozo

lunes, 16 de abril de 2012

Persistencia de datos con SQLite sobre Android: parte 3.


Desarrollo sobre la plataforma Android:


Hola a todos nuevamente, en este caso vamos a seguir un poco mas sobre el tema de persistencia de datos, utilizando el motor de base de datos SQLite, pero a esto le sumaremos Content Provider ya que en el anterior post sobre SQLite no pude hablar sobre ello ;).

Ahora porque y para que vamos a utilizar Content Provider???

En el modelo de seguridad de Android, los archivos escritos por una solo aplicación, no se pueden leer o escribir por cualquier otra aplicación. Cada programa tiene su propio ID de usuario Linux y directorio de datos (data/data/packagename) y además su propio espacio de memoria protegida.
Por este motivo los programas pueden comunicarse entre si solo de dos maneras:

  • Inter-Process Communication (IPC) o comunicación entre procesos : Uno de los procesos declara una API arbitraria utilizando el Android Interface Definition Language (AIDL) y la interfaz IBinder. Los parámetros calculan las referencias de forma segura y eficiente entre los procesos cuando la API se llama. Esta técnica avanzada se utiliza para las llamadas a procedimientos remotos de hilos que tienen service en background. (Como explica este tema es sobre programación avanzada y va a escapar los temas que vamos a trabajar por ahora, así que lo voy a dejar para mas adelante ;) ).
  • ContentProvider: Los procesos se registran en el sistema como proveedores de cierto tipo de datos. Cuando esa información es solicitada, las llama Android atraves de una API fija para consultar o modificar el contenido de la manera que mejor les parezca. Y esta es la técnica que vamos a ver y utilizar en este post ;)

Cualquier pieza de información gestionado por una ContentProvider se dirige a través de un URI que tiene este aspecto:

content://authority/path /id

Donde:

  • content:// es el prefijo estándar que se requiere.
  • Authority: es el nombre del proveedor, es recomendado utilizar el nombre completo del paquete para prevenir cualquier conflicto de nombres.
  • Path: es un directorio virtual donde se identifica al proveedor de tipo de datos que se solicita.
  • ID: es la llave primaria de un registro especifico que se solicita. Al solicitar todos los registros de un tipo particular, omite este y el ultimo es recortado.

Android ya viene con varios proveedores incluidos estos:

  • content://browser
  • content://contacts
  • content://media
  • content://setting

Para realizar una demostración del uso de Content Provider vamos a modificar el ejemplo utilizado anteriores post. Por ejemplo para ese ejemplo, estos podrían ser URIs validos:

content://com.prueba.gabriel.sqlite3/PruebasSQLite3/5 - un evento simple con un _ID = 5
content://com.prueba.gabriel.sqlite3/PruebasSQLite3 – todos los eventos

Ahora vamos a ver un poco de código, lo primero que vamos a hacer, es agregar un par de constantes al archivo Constantes.java, para que quede de esta manera:

package com.prueba.gabriel.sqlite3;


import android.net.Uri;

import android.provider.BaseColumns;

public interface Constantes extends BaseColumns {
public static final String TABLE_NAME = "PruebasSQLite3";
public static final String AUTHORITY = "com.prueba.gabriel.sqlite3";
public static final Uri CONTENT_URI = Uri.parse("content://"
+ AUTHORITY + "/" + TABLE_NAME);

public static final String TIME = "tiempo";
public static final String TITLE = "titulo";
}


Los archivos main.xml e item.xml no van a necesitar modificaciones, asi que ahora vamos a modificar el archivo PruebasSQLite3Activity.java, para que quede de la siguiente manera:

package com.prueba.gabriel.sqlite3;

import static android.provider.BaseColumns._ID;
import static com.prueba.gabriel.sqlite3.Constantes.CONTENT_URI;
import static com.prueba.gabriel.sqlite3.Constantes.TIME;
import static com.prueba.gabriel.sqlite3.Constantes.TITLE;

import com.prueba.gabriel.sqlite3.R;

import android.app.ListActivity;
import android.content.ContentValues;
import android.database.Cursor;
import android.os.Bundle;
import android.widget.SimpleCursorAdapter;

public class PruebasSQLite3Activity extends ListActivity {
private static String[] FROM = { _ID, TIME, TITLE, };
private static int[] TO = { R.id.rowid, R.id.time, R.id.title, };
private static String ORDER_BY = TIME + " DESC";

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
addEvent("Content Provider desde aprendiendodeandroidymas.blogspot!!!");
Cursor cursor = getEvents();
showEvents(cursor);
}

private void addEvent(String string) {
ContentValues values = new ContentValues();
values.put(TIME, System.currentTimeMillis());
values.put(TITLE, string);
getContentResolver().insert(CONTENT_URI, values);
}

private Cursor getEvents() {
return managedQuery(CONTENT_URI, FROM, null, null, ORDER_BY);
}

private void showEvents(Cursor cursor) {
SimpleCursorAdapter adapter = new SimpleCursorAdapter(this,
R.layout.item, cursor, FROM, TO);
setListAdapter(adapter);
}
}


El método onCreate() se pone simple porque no hay ningún objeto de base de datos al que debamos realizarle un seguimiento. Por eso no vamos a necesitar tampoco un bloque try/catch/finally también vamos a poder eliminar las referencias hacia PruebaSQLiteDataHelper.
También como podrán observar para agregar una linea debemos modificar dos lineas de código en el método addEvent().

La llamada a getWritableDatabase() se ha ido, y la llamada a insertOrThrow () se sustituye por getContentResolver().insert (). En lugar de un manejador de base de datos, vamos a utilizar una URI de proveedor de contenido.

Tambien podemos ver como el médoto getEvent() se simplifica mucho cuando utilizamos un ContentProvider. Aquí vamos a utilizar el método Activity.managedQuery( ), donde pasamos la URI del contenido, de la lista de columnas que nos interesa y el orden en que deben ser ordenados.
Al eliminar todas las referencias a la base de datos, hemos desacoplado el cliente del proveedor de base de datos. El cliente es bastante mas sencillo, pero ahora debemos implementar una pieza que no teníamos antes.

Ahora vamos a lo bueno ;) vamos a implementar el Content Provider:

Un Content Provider es un objeto de alto nivel como una actividad, que debe ser declarado en el sistema. Así, el primer paso a realizar es añadirlo a su archivo AndroidManifest.xml antes de la etiqueta <activity> (como un hijo de <application>) quedándonos de esta manera:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.prueba.gabriel.sqlite3"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk android:minSdkVersion="3" android:targetSdkVersion="10" />
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<provider android:name=".PruebaSQLite3ProveedorEventos"
android:authorities="com.prueba.gabriel.sqlite3"/>
<activity android:name=".PruebasSQLite3Activity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />ç
</intent-filter>
</activity>
</application>

</manifest>

  • android:name es el nombre de la clase que vamos a utilizar como proveedor de contenido.
  • Y android:authorities es la cadena string que se utiliza como URI de donde estara el contenido.

Lo siguiente sera crear la clase PruebaSQLite3ProveedorEventos que debe extenderse de Content Provider. En este caso vamos a crear el esquema básico, que se vera de la siguiente manera:

package com.prueba.gabriel.sqlite3;

import static android.provider.BaseColumns._ID;
import static com.prueba.gabriel.sqlite3.Constantes.AUTHORITY;
import static com.prueba.gabriel.sqlite3.Constantes.CONTENT_URI;
import static com.prueba.gabriel.sqlite3.Constantes.TABLE_NAME;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.text.TextUtils;

public class PruebaSQLite3ProveedorEventos extends ContentProvider {
private static final int EVENTS = 1;
private static final int EVENTS_ID = 2;

private static final String CONTENT_TYPE
= "vnd.android.cursor.dir/vnd.prueba.gabriel.sqlite3/PruebasSQLite3";

private static final String CONTENT_ITEM_TYPE
= "vnd.android.cursor.item/vnd.prueba.gabriel.sqlite3/PruebasSQLite3";

private PruebaSQLiteDataHelper pruebassqlite;
private UriMatcher uriMatcher;

@Override
public boolean onCreate() {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI(AUTHORITY, "PruebasSQLite3", EVENTS);
uriMatcher.addURI(AUTHORITY, "PruebasSQLite3/#", EVENTS_ID);
pruebassqlite = new PruebaSQLiteDataHelper(getContext());
return true;
}

@Override
public Cursor query(Uri uri, String[] projection,
String selection, String[] selectionArgs, String orderBy) {
if (uriMatcher.match(uri) == EVENTS_ID) {
long id = Long.parseLong(uri.getPathSegments().get(1));
selection = appendRowId(selection, id);
}

SQLiteDatabase db = pruebassqlite.getReadableDatabase();
Cursor cursor = db.query(TABLE_NAME, projection, selection,
selectionArgs, null, null, orderBy);

cursor.setNotificationUri(getContext().getContentResolver(),
uri);
return cursor;
}

@Override
public String getType(Uri uri) {
switch (uriMatcher.match(uri)) {
case EVENTS:
return CONTENT_TYPE;
case EVENTS_ID:
return CONTENT_ITEM_TYPE;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}
}

@Override
public Uri insert(Uri uri, ContentValues values) {
SQLiteDatabase db = pruebassqlite.getWritableDatabase();

if (uriMatcher.match(uri) != EVENTS) {
throw new IllegalArgumentException("Unknown URI " + uri);
}

long id = db.insertOrThrow(TABLE_NAME, null, values);

Uri newUri = ContentUris.withAppendedId(CONTENT_URI, id);
getContext().getContentResolver().notifyChange(newUri, null);
return newUri;
}

@Override
public int delete(Uri uri, String selection,
String[] selectionArgs) {
SQLiteDatabase db = pruebassqlite.getWritableDatabase();
int count;
switch (uriMatcher.match(uri)) {
case EVENTS:
count = db.delete(TABLE_NAME, selection, selectionArgs);
break;
case EVENTS_ID:
long id = Long.parseLong(uri.getPathSegments().get(1));
count = db.delete(TABLE_NAME, appendRowId(selection, id),
selectionArgs);
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}

getContext().getContentResolver().notifyChange(uri, null);
return count;
}

@Override
public int update(Uri uri, ContentValues values,
String selection, String[] selectionArgs) {
SQLiteDatabase db = pruebassqlite.getWritableDatabase();
int count;
switch (uriMatcher.match(uri)) {
case EVENTS:
count = db.update(TABLE_NAME, values, selection,
selectionArgs);
break;
case EVENTS_ID:
long id = Long.parseLong(uri.getPathSegments().get(1));
count = db.update(TABLE_NAME, values, appendRowId(
selection, id), selectionArgs);
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
}

getContext().getContentResolver().notifyChange(uri, null);
return count;
}

private String appendRowId(String selection, long id) {
return _ID + "=" + id
+ (!TextUtils.isEmpty(selection)
? " AND (" + selection + ')'
: "");
}
}


PruebaSQLite3ProveedorEventos maneja dos tipos de eventos:


EVENTS (MIME type CONTENT_TYPE): un directorio o lista de eventos.
EVENTS_ID (MIME type CONTENT_ITEM_TYPE): un único evento.

Por las dudas aclaro:
Multipurpose Internet Mail Extensions (MIME) es un estándar de Internet para describir el tipo de cualquier tipo de contenido.

En termino de URI, la diferencia es que en el primero no se especifica un identificador o id, pero si en el segundo. Para esto vamos a utilizar la clase Android UriMatcher para analizar la URI y ver que cliente en especifico se trata. Y volvemos a utilizar la clase PruebaSQLiteDataHelper que ya creamos en los anteriores post, para gestionar la base de datos real en el interior del Provider ;)



Bueno esto es todo por ahora, en el tema del manejo de base de datos SQLite, tengan en cuanta que con esto no solo se puede mostrar texto. Se pueden mostrar imágenes o cualquier otra cosa que nosotros necesitemos. También, creo que es una herramienta muy potente, donde lo único que les puede llegar a traer algun tipo de problemas es al crear la URI, pero con un poquito de practica y ver como funciona UriMatcher se soluciona de manera sencilla. Espero que les allá gustado, en próximos post vamos a ver temas diferentes a este. Y como siempre, estos son los enlaces de la docu oficial y los enlaces de los otros lugares de donde recaude información.

Documentación oficial:



Otros Enlaces:




Saludos a todos, Gabriel E. Pozo