web-dev-qa-db-fra.com

Utilisation de CursorLoader sans ContentProvider

La documentation du SDK Android indique que la méthode startManagingCursor() est obsolète:

Cette méthode est obsolète. Utilisez plutôt la nouvelle classe CursorLoader avec LoaderManager; cette option est également disponible sur les anciennes plates-formes via le package de compatibilité Android). Cette méthode permet à l'activité de gérer la gestion du cycle de vie du curseur, en fonction du cycle de vie de l'activité. est arrêté, il appellera automatiquement deactivate () sur le curseur donné et, lorsqu’il sera redémarré, appellera requery () à votre place. Lorsque l’activité sera détruite, tous les curseurs gérés seront automatiquement fermés. Si vous ciblez HONEYCOMB ou une version ultérieure , envisagez d'utiliser plutôt LoaderManager, disponible via getLoaderManager ()

Je voudrais donc utiliser CursorLoader. Mais comment puis-je l'utiliser avec CursorAdapter personnalisé et sans ContentProvider, quand j'ai besoin de l'URI dans le constructeur de CursorLoader?

107
sealskej

J'ai écrit un simple CursorLoader qui n'a pas besoin d'un fournisseur de contenu:

import Android.content.Context;
import Android.database.Cursor;
import Android.support.v4.content.AsyncTaskLoader;

/**
 * Used to write apps that run on platforms prior to Android 3.0. When running
 * on Android 3.0 or above, this implementation is still used; it does not try
 * to switch to the framework's implementation. See the framework SDK
 * documentation for a class overview.
 *
 * This was based on the CursorLoader class
 */
public abstract class SimpleCursorLoader extends AsyncTaskLoader<Cursor> {
    private Cursor mCursor;

    public SimpleCursorLoader(Context context) {
        super(context);
    }

    /* Runs on a worker thread */
    @Override
    public abstract Cursor loadInBackground();

    /* Runs on the UI thread */
    @Override
    public void deliverResult(Cursor cursor) {
        if (isReset()) {
            // An async query came in while the loader is stopped
            if (cursor != null) {
                cursor.close();
            }
            return;
        }
        Cursor oldCursor = mCursor;
        mCursor = cursor;

        if (isStarted()) {
            super.deliverResult(cursor);
        }

        if (oldCursor != null && oldCursor != cursor && !oldCursor.isClosed()) {
            oldCursor.close();
        }
    }

    /**
     * Starts an asynchronous load of the contacts list data. When the result is ready the callbacks
     * will be called on the UI thread. If a previous load has been completed and is still valid
     * the result may be passed to the callbacks immediately.
     * <p/>
     * Must be called from the UI thread
     */
    @Override
    protected void onStartLoading() {
        if (mCursor != null) {
            deliverResult(mCursor);
        }
        if (takeContentChanged() || mCursor == null) {
            forceLoad();
        }
    }

    /**
     * Must be called from the UI thread
     */
    @Override
    protected void onStopLoading() {
        // Attempt to cancel the current load task if possible.
        cancelLoad();
    }

    @Override
    public void onCanceled(Cursor cursor) {
        if (cursor != null && !cursor.isClosed()) {
            cursor.close();
        }
    }

    @Override
    protected void onReset() {
        super.onReset();

        // Ensure the loader is stopped
        onStopLoading();

        if (mCursor != null && !mCursor.isClosed()) {
            mCursor.close();
        }
        mCursor = null;
    }
}

Il n'a besoin que de la classe AsyncTaskLoader. Soit celui de Android 3.0 ou supérieur, soit celui fourni avec le package de compatibilité.

J'ai aussi écrit un ListLoader compatible avec le LoadManager et utilisé pour récupérer un Java.util.List collection.

154
Cristian

Écrivez votre propre chargeur qui utilise votre classe de base de données au lieu d’un fournisseur de contenu. Le moyen le plus simple consiste simplement à extraire le code source de la classe CursorLoader de la bibliothèque de compatibilité et à remplacer les requêtes du fournisseur par des requêtes dans votre propre classe d'assistance de base de données.

23
Nikolay Elenkov

SimpleCursorLoader est une solution simple, mais il ne prend pas en charge la mise à jour du chargeur lorsque les données sont modifiées. CommonsWare possède une bibliothèque loaderex qui ajoute un SQLiteCursorLoader et prend en charge les nouvelles requêtes sur les modifications de données.

https://github.com/commonsguy/cwac-loaderex

14
emmby

Une troisième option serait de simplement remplacer loadInBackground:

public class CustomCursorLoader extends CursorLoader {
    private final ForceLoadContentObserver mObserver = new ForceLoadContentObserver();

    @Override
    public Cursor loadInBackground() {
        Cursor cursor = ... // get your cursor from wherever you like

        if (cursor != null) {
            // Ensure the cursor window is filled
            cursor.getCount();
            cursor.registerContentObserver(mObserver);
        }

        return cursor;
    }
};

Cela permettra également de ré-interroger votre curseur lorsque la base de données change.

Seule réserve: vous devrez définir un autre observateur, car Google, dans sa sagesse infinie, a décidé de rendre son package privé. Si vous placez la classe dans le même package que celui d'origine (ou son compatriote), vous pouvez réellement utiliser l'observateur d'origine. L'observateur est un objet très léger et n'est utilisé nulle part ailleurs. Cela ne fait donc pas une grande différence.

12
Timo Ohr

La troisième option proposée par Timo Ohr, avec les commentaires de Yeung, fournit la réponse la plus simple (le rasoir d'Occam). Vous trouverez ci-dessous un exemple de classe complète qui fonctionne pour moi. Il existe deux règles d'utilisation de cette classe.

  1. Étendez cette classe abstraite et implémentez les méthodes getCursor () et getContentUri ().
  2. Chaque fois que la base de données sous-jacente change (par exemple, après une insertion ou une suppression), veillez à appeler

    getContentResolver().notifyChange(myUri, null);
    

    où myUri est le même que celui renvoyé par votre implémentation de la méthode getContentUri ().

Voici le code de la classe que j'ai utilisée:

package com.example.project;

import Android.content.Context;
import Android.database.Cursor;
import Android.content.CursorLoader;
import Android.content.Loader;

public abstract class AbstractCustomCursorLoader extends CursorLoader
  {
    private final Loader.ForceLoadContentObserver mObserver = new Loader.ForceLoadContentObserver();

    public AbstractCustomCursorLoader(Context context)
      {
        super(context);
      }

    @Override
    public Cursor loadInBackground()
      {
        Cursor cursor = getCursor();

        if (cursor != null)
          {
            // Ensure the cursor window is filled
            cursor.getCount();
            cursor.registerContentObserver(mObserver);
          }

        cursor.setNotificationUri(getContext().getContentResolver(), getContentUri());
        return cursor;
      }

    protected abstract Cursor getCursor();
    protected abstract Uri getContentUri();
  }
2
John Moore