¿Cómo CursorLoader actualiza automáticamente la vista incluso si la aplicación está inactiva?
He estado trabajando en una pequeña aplicación de lista de tareas pendientes. Usé CursorLoader para actualizar ToDolistview desde un proveedor de contenido. Tengo una función escrita onNewItemAdded()
, que se llama cuando el usuario ingresa un nuevo elemento en la vista de texto y hace clic en enter. Refiérase abajo:
public void onNewItemAdded(String newItem) {
ContentResolver cr = getContentResolver();
ContentValues values = new ContentValues();
values.put(ToDoContentProvider.KEY_TASK, newItem);
cr.insert(ToDoContentProvider.CONTENT_URI, values);
// getLoaderManager().restartLoader(0, null, this); // commented for the sake of testing
}
@Override
protected void onResume() {
super.onResume();
//getLoaderManager().restartLoader(0, null, this); // commented for the sake of testing
}
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
CursorLoader loader = new CursorLoader(this,
ToDoContentProvider.CONTENT_URI, null, null, null, null);
Log.e("GOPAL", "In the onCreateLoader");
return loader;
}
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
int keyTaskIndex = cursor.getColumnIndexOrThrow(ToDoContentProvider.KEY_TASK);
Log.e("GOPAL", "In the onLoadFinished");
todoItems.clear();
if (cursor.moveToNext() == false) Log.e("GOPAL", "Empty Cursor");
else {
while (cursor.moveToNext()) {
ToDoItem newItem = new ToDoItem(cursor.getString(keyTaskIndex));
todoItems.add(newItem);
}
aa.notifyDataSetChanged(); // aa is arrayadapter used for the listview
}
}
He leído, CursorLoader actualiza automáticamente la vista, cada vez que hay un cambio de datos en la base de datos del proveedor de contenido. Eso significa que supongo, getLoaderManager().restartLoader(0, null, this)
tiene que ser llamado implícitamente cada vez que hay un cambio en los datos, ¿verdad?
Pero eso no está sucediendo. Cada vez que agrego un nuevo elemento (el elemento se agrega a la base de datos desde onNewItemAdded
, pero restartLoader no se llama explícitamente), pausar esta actividad y reanudarla de nuevo. No veo ninguna llamada implícita a restartLoader (a pesar de que se cambia la base de datos) y el listview tampoco se actualiza con nuevo elemento añadido. ¿Por qué es eso? ¿Cómo actualiza automáticamente una CursorLoader
la vista incluso si la aplicación no está activa???
Gracias :)
EDITAR: También he usado getContext().getContentResolver().notifyChange(insertedId, null)
en la inserción de mi proveedor de contenido.
3 answers
Encontré la respuesta a mi pregunta. En general, CursorLoader no detecta automáticamente los cambios de datos y los carga para verlos. Necesitamos rastrear URI para cambios. Esto se puede hacer siguiendo los siguientes pasos:
Registrar un Observador en el solucionador de contenido a través del cursor usando: (Hecho en el método de consulta de ContentProvider)
cursor.setNotificationUri(getContext().getContentResolver(), uri);
-
Ahora cuando hay algún cambio en los datos subyacentes de URI usando
insert()
/delete()
/update(),
notificamos alContentResolver
sobre el cambio usando:getContext().getContentResolver().notifyChange(insertedId, null);
Esto es recibido por el observador, nos registramos en el paso-1 y esto llama a
ContentResolver.query()
, que inturn llama al métodoContentProvider
'squery()
para devolver un cursor fresco aLoaderManager
.LoaderManager
llama aonLoadFinished()
pasando este cursor, junto con elCursorLoader
donde actualizamos la vista (usandoAdapter.swapCursor()
) con datos frescos.
Para AsyncTaskLoaders personalizados:
A veces necesitamos nuestro cargador personalizado en lugar de CursorLoader. Aquí podemos usar otro objeto que no sea cursor para apuntar a los datos cargados (como lista, etc). En esto no tendremos previlige para notificar a ContentResolver a través del cursor. La aplicación también puede no tener un proveedor de contenido, para rastrear los cambios de URI. En este escenario usamos BroadcastReceiver o explicit ContentObserver para lograr la actualización automática de la vista. Esto es como sigue:
- Necesitamos definir nuestro cargador personalizado que extiende
AsyncTaskLoader
e implementa todos sus métodos abstractos. A diferencia deCursorLoader
, nuestro Cargador personalizado puede o no use un Proveedor de contenido y su constructor no puede llamar aContentResolver.query()
, cuando este cargador se instatiza. Así que usamos un receptor de transmisión para servir el propósito. - Necesitamos instanciar una
BroadCastReceiver
oContentObserver
en el métodoOnStartLoading()
de la clase abstractaAsyncTaskLoader
. - Este receptor de transmisión debe definirse para recibir transmisiones de cambio de datos desde el proveedor de contenido o cualquier evento del sistema(Como la nueva aplicación instalada) y tiene que llamar al método
onContentChanged()
del cargador, para notificar al cargador sobre el cambio de datos. Loader automáticamente hace el resto para cargar los datos actualizados y llamar aonLoadFinished()
para actualizar la vista.
Para más detalles consulte esto: http://developer.android.com/reference/android/content/AsyncTaskLoader.html
Encontré esto muy útil para una explicación clara: http://www.androiddesignpatterns.com/2012/08/implementing-loaders.html
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-02-08 19:55:13
Bueno, creo que puede reiniciar el cargador en ciertos eventos. Por ejemplo, en mi caso tengo una actividad de TODOs. Al hacer clic en la opción' agregar', lanza una nueva actividad que tiene vista para alimentar nuevas tareas pendientes.
Estoy usando el siguiente código en onActivityResult()
getLoaderManager().restartLoader(0, null, this);
Funciona bien para mí. Por favor, comparta si hay algún enfoque mejor.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-02-08 14:26:54
Obtenga una referencia a su cargador mientras inicializa de la siguiente manera
Loader dayWeatherLoader = getLoaderManager().initLoader(LOADER_DAY_WEATHER, null, this);
Luego cree una clase que extienda ContentObserver de la siguiente manera
class DataObserver extends ContentObserver {
public DataObserver(Handler handler) {
super(handler);
}
@Override
public void onChange(boolean selfChange, Uri uri) {
dayWeatherLoader.forceLoad();
}
}
Luego registre el observador de contenido dentro del método de ciclo de vida de onResume de la siguiente manera
@Override
public void onResume() {
super.onResume();
getContext().getContentResolver().registerContentObserver(CONTENTPROVIDERURI,true,new DayWeatherDataObserver(new Handler()));
}
Siempre que haya un cambio en los datos subyacentes del proveedor de contenido, se llamará al método onChange de contentobserver donde puede pedirle a loader que cargue los datos nuevamente
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-09 01:23:34