web-dev-qa-db-fra.com

Comment CursorLoader met-il automatiquement à jour la vue même si l'application est inactive?

J'ai travaillé sur une petite application de liste de tâches. J'ai utilisé CursorLoader pour mettre à jour ToDolistview à partir d'un fournisseur de contenu. J'ai écrit une fonction onNewItemAdded(), qui est appelée lorsque l'utilisateur entre un nouvel élément dans la vue texte et clique sur Entrée. Référez-vous ci-dessous:

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
    }
}

J'ai lu, CursorLoader met automatiquement à jour la vue, chaque fois qu'il y a un changement de données dans le fournisseur de contenu db. Cela signifie que je suppose que getLoaderManager().restartLoader(0, null, this) doit être appelé implicitement chaque fois qu'il y a un changement de données, non? Mais cela ne se produit pas. Chaque fois que j'ajoute un nouvel élément (l'élément est ajouté à la base de données à partir de onNewItemAdded, mais restartLoader n'est pas explicitement appelé), suspendez cette activité et reprenez-la. Je ne vois aucun appel implicite à restartLoader (même si la base de données est modifiée) et la liste n'est pas non plus mise à jour avec un nouvel élément ajouté. Pourquoi donc? Comment un CursorLoader met-il automatiquement à jour la vue même si l'application n'est pas active ??? Merci :)

EDIT: J'ai également utilisé getContext().getContentResolver().notifyChange(insertedId, null) dans l'insertion de mon fournisseur de contenu.

24
Gopal S Akshintala

J'ai trouvé la réponse à ma question. En général, CursorLoader ne détecte pas automatiquement les modifications de données et ne les charge pas pour les afficher. Nous devons suivre l'URI pour les modifications. Cela peut être fait en suivant les étapes:

  1. Enregistrement d'un observateur dans le résolveur de contenu via le curseur en utilisant: (Fait dans la méthode de requête de ContentProvider)
    cursor.setNotificationUri(getContext().getContentResolver(), uri);

  2. Maintenant, quand il y a un changement dans les données sous-jacentes de l'URI en utilisant insert()/delete()/update(), nous informons le ContentResolver du changement en utilisant:

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

  3. Ceci est reçu par l'observateur, nous l'avons enregistré à l'étape 1 et cela appelle à ContentResolver.query(), qui appelle à son tour la méthode query() de ContentProvider pour renvoyer un nouveau curseur à LoaderManager. LoaderManager appelle onLoadFinished() en passant ce curseur, avec le CursorLoader où nous mettons à jour la vue (en utilisant Adapter.swapCursor()) avec de nouvelles données.

Pour les AsyncTaskLoaders personnalisés:

Parfois, nous avons besoin de notre chargeur personnalisé au lieu de CursorLoader. Ici, nous pouvons utiliser un objet autre que le curseur pour pointer vers les données chargées (comme la liste, etc.). En cela, nous n'aurons pas la possibilité de notifier ContentResolver via le curseur. L'application peut également ne pas avoir de fournisseur de contenu, pour suivre les modifications d'URI. Dans ce scénario, nous utilisons BroadcastReceiver ou ContentObserver explicite pour réaliser la mise à jour automatique de la vue. C'est comme suit:

  1. Nous devons définir notre chargeur personnalisé qui étend AsyncTaskLoader et implémente toutes ses méthodes abstraites. Contrairement à CursorLoader, notre chargeur personnalisé peut ou non utiliser un fournisseur de contenu et son constructeur peut ne pas appeler ContentResolver.query(), lorsque ce chargeur est installé. Nous utilisons donc un récepteur de diffusion pour répondre à cet objectif.
  2. Nous devons instancier une BroadCastReceiver ou ContentObserver dans la méthode OnStartLoading() de la classe abstraite AsyncTaskLoader.
  3. Ce récepteur BroadCast doit être défini pour recevoir des diffusions de changement de données du fournisseur de contenu ou de tout événement système (comme une nouvelle application installée) et il doit appeler la méthode onContentChanged() du chargeur pour informer le chargeur du changement de données. Loader fait automatiquement le reste pour charger les données mises à jour et appelle onLoadFinished() pour mettre à jour la vue.

Pour plus de détails, reportez-vous à ceci: http://developer.Android.com/reference/Android/content/AsyncTaskLoader.html

J'ai trouvé cela très utile pour une explication claire: http://www.androiddesignpatterns.com/2012/08/implementing-loaders.html

72
Gopal S Akshintala

Eh bien, je pense que vous pouvez redémarrer le chargeur sur certains événements. Par exemple. dans mon cas j'ai une activité de TODOs. En cliquant sur l'option 'ajouter', il lance une nouvelle activité qui permet d'alimenter de nouveaux TODO.

J'utilise le code suivant dans onActivityResult () de l'activité parent

getLoaderManager().restartLoader(0, null, this);

Ça fonctionne bien pour moi. Veuillez partager s'il y a une meilleure approche.

3
iuq

obtenir une référence à votre chargeur lors de l'initialisation comme suit

 Loader dayWeatherLoader = getLoaderManager().initLoader(LOADER_DAY_WEATHER, null, this);

puis créez une classe qui étend ContentObserver comme suit

 class DataObserver extends ContentObserver {

    public DataObserver(Handler handler) {
        super(handler);
    }

    @Override
    public void onChange(boolean selfChange, Uri uri) {
        dayWeatherLoader.forceLoad();
    }
}

Enregistrez ensuite l'observateur de contenu dans la méthode de cycle de vie onResume comme suit

@Override
public void onResume() {
    super.onResume();
    getContext().getContentResolver().registerContentObserver(CONTENTPROVIDERURI,true,new DayWeatherDataObserver(new Handler()));
}

Chaque fois qu'il y a un changement dans les données sous-jacentes du fournisseur de contenu, la méthode onChange de contentobserver sera appelée où vous pouvez demander au chargeur de charger à nouveau les données

2
rijogeorge7