¿Qué es un Loader?
Como introducción podemos
decir que un objeto Loader proporciona funcionalidad para realizar
una carga asíncrona de datos. Fueron introducidos en la versión 3.0
de Android y hasta entonces si querías realizar alguna lectura de
datos en segundo plano tenías que proporcionar tu el AsyncTask que
se encargará de ejecutarla, los mecanismos de sincronización... etc
Si además querías manejar
correctamente el ciclo de vida de los datos leídos lo único que
proporcionaba Android era la función
Activity#startManagingCursor(Cursor).
No solo quedaba restringido al uso de Cursor para manejar datos sino
que realizaba todas las "recargas" de datos en la hebra
principal.
En Android realizar una
operación costosa como leer de una base de datos o realizar una
comunicación web en la hebra principal es uno de los principales
sinónimos de mal desarrollo. La hebra principal es la hebra de la
IU, por lo que bloquear esta hebra implica bloquear la aplicación de
cara al usuario, no pudiendo recibir eventos de entrada ni mostrar
animaciones o datos como salida. Esto empobrecería enormemente la
experiencia de usuario, que incluso podría llegar a tener la
sensación de que la app ha dejado de funcionar. Además Android, a
partir de la versión 4.0, responde a llamadas bloqueantes sobre la
hebra principal con un error que cierra automáticamente la
aplicación.
Cómo usar un Loader
Los
componentes principales a tener en cuenta a la hora de usar Loaders
en nuestra aplicación son la clase LoaderManager y la interfaz
LoaderCallbacks.
Toda
Activity o Fragment tiene asociada una instancia única de
LoaderManager a la que podemos acceder usando la función
getLoaderManager().
Usando esta instancia podemos iniciar, reiniciar y destruir Loaders
sin preocuparnos por bloquear la hebra principal. Tampoco tenemos que
preocuparnos por manejar el ciclo de vida de los Loader ya que estos
quedan "ligados" al ciclo de vida de la Activity/Fragment
propietaria del LoaderManager. Es decir, si el propietario es
iniciado, el Loader es iniciado, si se para (al dejar de ser
visible), el Loader se para... etc
Por
su parte la interfaz LoaderCallbacks define los métodos a
implementar para instanciar los Loader y recoger los resultados de su
ejecución:
- Loader<D> onCreateLoader(int id, Bundle args): Instancia un nuevo Loader.
- id: Identificador único del Loader.
- args: Parámetros adicionales necesarios en la creación del Loader.
- onLoadFinished(Loader<D> loader, D data): Método llamado cuando un Loader ha terminado de leer datos.
- loader: El Loader que ha finalizado.
- data: Los datos recibidos de la lectura del Loader.
- onLoaderReset(Loader<D> loader): Método llamado cuando un Loader ha sido reiniciado y por lo tanto sus datos pasan a ser inválidos.
- loader: Loader reiniciado.
En
la práctica acabaremos usando como máximo tres funciones de
LoaderManager para manejar toda nuestra lectura de datos:
- Loader<D> initLoader(int id, Bundle args, LoaderCallBacks<D> callbacks): Inicia un nuevo Loader y comienza a leer datos. Si ya existe un Loader con ese identificador se reutiliza sin volver a ejecutar la lectura de datos.
- Loader<D> restartLoader(int id, Bundle args, LoaderCallBacks<D> callbacks): Reinicia un Loader y comienza a leer datos. Si no existe un Loader con ese identificador creará uno nuevo.
- destroyLoader(int id): Elimina un Loader. Si el Loader ya ha sido iniciado y esta leyendo datos, la lectura se cancela.
Como
lo mejor es verlo en un ejemplo, veamos como usaríamos los Loader en
el caso de tener una lista que quisiésemos llenar con los resultados
de una consulta, que es un caso práctico que seguro nos
encontraremos en muchas aplicaciones.
Nota:
He obviado partes del código para concentrarnos en la parte del
manejo del Loader.
public
class
MyListFragment extends
ListFragment implements
LoaderCallbacks<Cursor>{
@Override
public
void
onCreate(Bundle savedInstanceState){
//...
//
Iniciamos la carga de datos
//
Como solo tenemos un loader el valor del identificador no importa
getLoaderManager().initLoader(0,
null,
this);
//...
}
@Override
public
Loader<Cursor> onCreateLoader(int
id, Bundle args) {
//
Instanciamos el Loader
//
Este apartado se explica mejor a continuacion
return
new
CursorLoader(/*...*/);
}
@Override
public
void
onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
//
Hacemos uso de los datos recibidos
//
En este caso cambiamos el Cursor del ListAdapter de la lista
//
que se encargará de mostrar los datos al usuario
((CursorAdapter)
getListAdapter()).swapCursor(cursor);
}
@Override
public
void
onLoaderReset(Loader<Cursor> loader) {
//
Eliminamos el Cursor que contenia el ListAdapter
//
ya que los datos ya no son validos
((CursorAdapter)
getListAdapter()).swapCursor(null);
}
}
Y con
eso ya tendríamos una lista funcionando. Solo una línea en la
función onCreate()
e implementar tres métodos de una interfaz.
Como
se puede ver, en el método onCreateLoader()
hemos creado una instancia de CursorLoader. He obviado los parámetros
del constructor porque no son relevantes para el uso de Loaders. Si queréis aprender a usar CursorLoader antes os recomiendo que entendáis como funcionan las consultas con ContentProviders de
Android.
Si
habéis consultado la documentación sobre Loader habréis visto que
tanto Loader<D> como AsyncLoader<D> son clases
abstractas. La única implementación de Loader que proporciona
Android es la clase CursorLoader. Es fácil de usar y nos proporciona
carga en segundo plano y monitorización automática de cambios en
los datos. Sin embargo tiene una importante restricción: solo
funciona con consultas a ContentProviders. En el caso de que queramos
usar los Loader sin usar ContentProvider (o sin usar Cursor tampoco)
la única solución es implementar nosotros nuestro propio Loader.
No hay comentarios:
Publicar un comentario