Fundamentos de Android: Fechas en la base de datos y ordenamiento
Spanish (Español) translation by steven (you can also view the original English article)
Android usa tecnología SQLite para su base de datos local. Esto funciona bastante bien. Sin embargo, existen peculiaridades ocasionales en comparación con una base de datos relacional con todas las funciones. Una de esas peculiaridades es que SQLite no admite ningún tipo de tipo de fecha. Afortunadamente, admite funciones de fecha y es capaz de almacenar fechas en numerosos formatos. Este tutorial te proporcionará un método para trabajar con fechas en el contexto de agregar fechas a la base de datos de la aplicación "TutList" y (finalmente) mostrar una lista de tutoriales, ordenados por fecha.
Este tutorial se basa en tutoriales anteriores, incluido el Curso intensivo de SQLite para desarrolladores de Android y la serie continua sobre nuestra actividad TutList con el tutorial más reciente, Fundamentos de Android: Trabajar con proveedores de contenido. Si tienes problemas para mantenerte al día, no dudes en publicar preguntas en la sección de comentarios; muchas personas leen y responden, incluidos nosotros. Además, no te olvides de la referencia del SDK de Android. El código de muestra final que acompaña a este tutorial está disponible para descargar como código abierto desde el alojamiento de código de Google.
Paso 0: Comenzando
Este tutorial asume que comenzarás donde terminaste el tutorial anterior de la serie, Compatibilidad con Android: Indicadores de lista en Honeycomb. Puedes descargar ese código y trabajar desde allí o puedes descargar el código para este tutorial y seguirlo. De cualquier manera, prepárate descargando uno u otro proyecto e importándolo a Eclipse.
Paso 1: Actualización de la base de datos
Para almacenar y obtener fechas a través del proveedor de contenido, necesitaremos actualizar la base de datos de la aplicación para almacenar las fechas asociadas con cada registro de contenido. Un cambio en la base de datos significa que deberás actualizar la versión de la base de datos, agregar el nuevo nombre de columna como una constante, proporcionar un nuevo esquema inicial y codificar una actualización a la base de datos existente. Todos estos cambios afectan a la clase TutListDatabase.
Comienza por actualizar la versión de la base de datos a 3.
1 |
private static final int DB_VERSION = 3; |
A continuación, agrega la nueva columna para la fecha, definiendo ese nombre de columna en Java.
1 |
public static final String COL_DATE = "tut_date"; |
Actualiza el esquema para incorporar la nueva columna.
1 |
private static final String CREATE_TABLE_TUTORIALS = "CREATE TABLE " |
2 |
+ TABLE_TUTORIALS + " (" + ID |
3 |
+ " INTEGER PRIMARY KEY AUTOINCREMENT, " + COL_TITLE |
4 |
+ " TEXT NOT NULL, " + COL_URL + " text UNIQUE NOT NULL, " |
5 |
+ COL_DATE + " INTEGER NOT NULL DEFAULT (strftime('%s','now'))" |
6 |
+ ");"; |
Hemos optado por utilizar un tipo INTEGER para almacenar la fecha. Podríamos haber elegido REAL, para almacenar fechas julianas, o TEXT para almacenar fechas como cadenas. Usamos una expresión SQLite, "strftime('%s', 'now'))" que inserta la hora actual, en forma de un número entero, en la base de datos. El valor está en segundos y se define como tiempo Unix, segundos desde el comienzo UTC de 1970.
Finalmente, actualiza los esquemas más antiguos dentro del método onUpgrade().
1 |
@Override
|
2 |
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { |
3 |
if (oldVersion == 2 && newVersion == 3) { |
4 |
this value is mid february 2011 |
5 |
db.execSQL("alter table "+ TABLE_TUTORIALS + " add column " + COL_DATE + " INTEGER NOT NULL DEFAULT '1297728000' "); |
6 |
|
7 |
} else { |
8 |
Log.w(DEBUG_TAG, "Upgrading database. Existing contents will be lost. [" |
9 |
+ oldVersion + "]->[" + newVersion + "]"); |
10 |
db.execSQL("DROP TABLE IF EXISTS " + TABLE_TUTORIALS); |
11 |
onCreate(db); |
12 |
}
|
13 |
}
|
Observa aquí que el esquema alterado varía del esquema original. SQLite no permite que se utilicen expresiones o valores de fecha actuales como valores predeterminados al modificar una tabla. Usar un valor constante (elegimos a mediados de febrero de este año) es la única opción que no es crear una nueva tabla y copiar datos, una operación costosa cuando hay una gran cantidad de datos. Consulta la documentación de SQLite en ALTER TABLE para obtener más información sobre las restricciones con la operación ADD COLUMN. Ten en cuenta estas restricciones al diseñar el esquema de la base de datos de tu aplicación y planificar cambios o actualizaciones en un momento posterior.
Esto completa los cambios de base de datos necesarios para respaldar los datos de la fecha del artículo. Esto permitirá actualizar aplicaciones más antiguas sin perder ningún dato ya descargado. Aunque esto puede no parecer útil en esta aplicación, hacerlo correctamente y acostumbrarse a hacerlo evitará problemas más adelante. Además, tus usuarios estarán más felices.
Paso 2: Actualizar el proveedor de contenido
Al actualizar la base de datos de la aplicación, querrás revisar tu proveedor de contenido para ver qué cambios deben realizarse para que los dos estén en paridad. En este caso, no es necesario realizar cambios. Simplemente agregamos una nueva columna para la fecha, que no necesita reflejarse en ningún tipo de URI u otras modificaciones de consulta en el proveedor de contenido.
Paso fácil, ¿eh? No siempre es tan fácil, como verás la próxima vez que realicemos este ejercicio.
Paso 3: Actualizando el analizador
Dentro de la clase TutListDownloaderService, usamos un analizador de extracción para obtener datos de la fuente XML e insertarlos en la base de datos. Necesitamos obtener la fecha del feed, transformarla al formato apropiado para la base de datos y luego agregarla como parte de cada registro de la base de datos.
Dentro del método xmlParse(), agrega la siguiente verificación en la cadena de verificaciones para el evento START_TAG:
1 |
if (tutorials.getName().equals("pubDate")) { |
2 |
tutorials.next(); |
3 |
DateFormat parser = new SimpleDateFormat("E, dd MMM yyyy"); |
4 |
try { |
5 |
Date date = parser.parse(tutorials.getText()); |
6 |
tutorialData.put(TutListDatabase.COL_DATE, |
7 |
date.getTime() / 1000); |
8 |
} catch (ParseException e) { |
9 |
Log.e(DEBUG_TAG, "Error parsing date: " |
10 |
+ tutorials.getText()); |
11 |
}
|
12 |
}
|
A continuación, se muestra un ejemplo de cómo se ve la fecha en XML:
1 |
<pubDate>Fri, 20 May 2011 11:30:23 +0000</pubDate> |
Como solo estamos interesados en la fecha, la analizamos usando la clase SimpleDateFormat. Una vez que se ha llamado al método parse(), tenemos un objeto Date estándar. Dado que un objeto Date contiene valores en milisegundos en lugar de segundos, luego lo dividimos por 1000 antes de almacenar el resultado en la base de datos.
Paso 4: Modificar el diseño de ListView
La fecha debe mostrarse en ListView junto con el título de cada artículo. Actualmente, el recurso de diseño list_item.xml contiene un único TextView. Ahora, modifica este diseño para usar un LinearLayout (orientado verticalmente) para colocar el título sobre la fecha. Aquí hay una lista del archivo list_item.xml actualizado.
1 |
<?xml version="1.0" encoding="utf-8"?>
|
2 |
<LinearLayout
|
3 |
xmlns:android="http://schemas.android.com/apk/res/android" |
4 |
android:layout_width="match_parent" |
5 |
android:layout_height="wrap_content" |
6 |
android:orientation="vertical"> |
7 |
<TextView
|
8 |
android:id="@+id/title" |
9 |
android:layout_width="match_parent" |
10 |
android:layout_height="wrap_content" |
11 |
android:textSize="24dp" |
12 |
android:padding="6dp" /> |
13 |
<TextView
|
14 |
android:id="@+id/date" |
15 |
android:layout_width="match_parent" |
16 |
android:layout_height="wrap_content" |
17 |
android:textSize="18dp" |
18 |
android:padding="4dp" |
19 |
android:gravity="right" /> |
20 |
</LinearLayout>
|
No olvides realizar este cambio en el list_item.xml alternativo que se encuentra también en el directorio /layout-v11.
Paso 5: Actualización del fragmento de ListView
Dentro de la clase TutListFragment, se deben realizar varios cambios para agregar compatibilidad de ordenamiento al control ListView. La proyección del Cursor debe actualizarse para incluir la columna de fecha. Los valores de enlace para el adaptador deben actualizarse para agregar también los datos de fecha, y debe configurarse el nuevo objeto TextView que mostrará la fecha. Finalmente, se debe agregar una clase ViewBinder personalizada; de lo contrario, las fechas mostradas no se verán bien.
Comienza por actualizar la proyección del Cursor y agrega la cláusula de clasificación, dentro del método onCreateLoader(), así:
1 |
@Override
|
2 |
public Loader<Cursor> onCreateLoader(int id, Bundle args) { |
3 |
String[] projection = { TutListDatabase.ID, TutListDatabase.COL_TITLE, |
4 |
TutListDatabase.COL_DATE }; |
5 |
|
6 |
Uri content = TutListProvider.CONTENT_URI; |
7 |
CursorLoader cursorLoader = new CursorLoader(getActivity(), |
8 |
content , projection, null, null, TutListDatabase.COL_DATE+" desc"); |
9 |
return cursorLoader; |
10 |
}
|
Un orden de fecha descendente significa que los elementos más nuevos aparecerán en la parte superior.
A continuación, modifica las variables de enlace (que ahora se encuentran en el nivel de clase) que utiliza la clase SimpleCursorAdapter, así:
1 |
private static final String[] UI_BINDING_FROM = { TutListDatabase.COL_TITLE, |
2 |
TutListDatabase.COL_DATE }; |
3 |
private static final int[] UI_BINDING_TO = { R.id.title, R.id.date }; |
En este punto, el código debería funcionar. Sin embargo, el resultado se verá así:


La información de la fecha sin procesar, aunque ordenada correctamente, no es muy útil para la mayoría de los usuarios. La solución es transformar el valor sin procesar de nuevo a una fecha mostrada utilizando la configuración regional del usuario. Para hacer esto, podemos implementar un objeto ViewBinder.
Paso 6: Implementar un ViewBinder
Como estamos usando un objeto SimpleCursorAdapter, implementaremos una clase SimpleCursorAdapter.ViewBinder personalizada. La clase ViewBinder permite el mapeo personalizado de una columna a su objeto View como se identifica a través de las matrices de binder suministradas. Esto significa que podemos cambiar lo que sucede durante el enlace de la columna de fecha al objeto TextView sin interferir con ninguno de los otros enlaces. Aquí hay una implementación de muestra de un ViewBinder personalizado que realiza esto:
1 |
private class TutorialViewBinder implements SimpleCursorAdapter.ViewBinder { |
2 |
|
3 |
@Override
|
4 |
public boolean setViewValue(View view, Cursor cursor, int index) { |
5 |
if (index == cursor.getColumnIndex(TutListDatabase.COL_DATE)) { |
6 |
// get a locale based string for the date
|
7 |
DateFormat formatter = android.text.format.DateFormat |
8 |
.getDateFormat(getActivity().getApplicationContext()); |
9 |
long date = cursor.getLong(index); |
10 |
Date dateObj = new Date(date * 1000); |
11 |
((TextView) view).setText(formatter.format(dateObj)); |
12 |
return true; |
13 |
} else { |
14 |
return false; |
15 |
}
|
16 |
}
|
17 |
}
|
Los parámetros entrantes al método único que necesitamos implementar, setViewValue, son el objeto View de la matriz UI_BINDING_TO que proporcionamos, el objeto Cursor con el que está trabajando el adaptador y el índice de los datos que se encuentran dentro del Cursor.
Usamos el índice y el objeto Cursor para determinar si el valor que se enlaza es la fecha. De lo contrario, se devuelve falso para indicar que se debe realizar el enlace predeterminado. Si es así, procedemos a reformatear la fecha. El uso de la clase android.text.format.DateFormat nos permite recuperar un objeto DateFormat específico de la configuración regional. Dado que el valor almacenado en la base de datos está en segundos, debemos convertirlo en milisegundos. Entonces podemos llamar a setText() en la vista para escribir la cadena resultante.
Finalmente, esta clase ViewBinder debe asignarse al adaptador. Justo después de inicializar el adaptador, en el método onCreate(), llama al método setViewBinder():
1 |
adapter.setViewBinder(new TutorialViewBinder()); |
Ahora, cuando ejecutas la aplicación, la fecha aparece en el formato específico de la configuración regional del usuario (o el formato de fecha que estableciste en la Configuración).


Conclusión
En este tutorial, has aprendido a trabajar con fechas en Android al leerlas de fuentes XML y leerlas y escribirlas en bases de datos SQLite. Además, aprendiste cómo realizar cambios personalizados en los valores individuales que se muestran dentro de un ListView. En el contexto de TutList, has modificado la aplicación para almacenar las fechas analizadas de la fuente XML de tutoriales y usar esas fechas para mostrar los tutoriales más recientes al usuario en orden de publicación.
Como siempre, esperamos tus comentarios.
Sobre los autores
Los desarrolladores móviles Lauren Darcey y Shane Conder han sido coautores de varios libros sobre el desarrollo de Android: un libro de programación en profundidad titulado Desarrollo de aplicaciones inalámbricas de Android y Sams - Aprende tu mismo: Desarrollo de aplicaciones de Android en 24 horas. Cuando no están escribiendo, dedican su tiempo a desarrollar software móvil en su empresa y a brindar servicios de consultoría. Se les puede contactar por correo electrónico a androidwirelessdev+mt@gmail.com, a través de su blog en androidbook.blogspot.com y en Twitter @androidwireless.



