¿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.

Author: Jameson, 2013-03-20

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:

  1. 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);

  2. Ahora cuando hay algún cambio en los datos subyacentes de URI usando insert()/delete()/update(), notificamos al ContentResolver sobre el cambio usando:

    getContext().getContentResolver().notifyChange(insertedId, null);

  3. Esto es recibido por el observador, nos registramos en el paso-1 y esto llama a ContentResolver.query(), que inturn llama al método ContentProvider's query() para devolver un cursor fresco a LoaderManager. LoaderManager llama a onLoadFinished() pasando este cursor, junto con el CursorLoader donde actualizamos la vista (usando Adapter.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:

  1. Necesitamos definir nuestro cargador personalizado que extiende AsyncTaskLoader e implementa todos sus métodos abstractos. A diferencia de CursorLoader, nuestro Cargador personalizado puede o no use un Proveedor de contenido y su constructor no puede llamar a ContentResolver.query(), cuando este cargador se instatiza. Así que usamos un receptor de transmisión para servir el propósito.
  2. Necesitamos instanciar una BroadCastReceiver o ContentObserver en el método OnStartLoading() de la clase abstracta AsyncTaskLoader.
  3. 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 a onLoadFinished() 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

 69
Author: Gopal S Akshintala,
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.

 3
Author: iuq,
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

 2
Author: rijogeorge7,
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