AutoCompleteTextView respaldado por CursorLoader

Así que estoy teniendo problemas para extender elMultiAutoCompleteTextView y respaldándolo con unaCursorLoader, mientras que simultáneamente utiliza una costumbreTokenizer. El tema se eleva específicamente con elmAdapter.setCursorToStringConverter(); llamada. losconvertToString() El método que tiene un Cursor como argumento tiene un cursor válido y no cerrado en la primera llamada a este método. Sin embargo, las llamadas posteriores dan como resultado un cursor nulo o un cursor cerrado. Supongo que esto tiene algo que ver con cómoLoaderManager maneja elCursorLoader.

Si comento elsetCursorToStringConverter() fuera del método, luego veo una lista de opciones disponibles basadas en el texto que ingresé en esta vista. Sin embargo, como no hayconvertToString() método implementado, entonces elterminateToken() método de la costumbreTokenizer no recibe la cadena que pretendo que sea, sino una cadena representativa del objeto del cursor, ya que el cursor no se ha utilizado para obtener el valor de la cadena actual de una columna deseada en la consulta resultante.

¿Alguien ha podido implementar la combinación de las tres clases (CursorLoader/LoaderManger, MultiAutoCompleteTextViewyTokenizer)?

¿Voy en la dirección correcta con esto o simplemente no es posible?

He podido implementar una costumbre.MultiAutoCompleteTextView respaldado por unSimpleCursorAdapter junto con una costumbreTokenizer. Me preguntaba si es posible implementar esto usando unCursorLoader en cambio, ya que el modo estricto se queja sobre el cursor enMultiAutoCompleteTextView no siendo explícitamente cerrado.

Cualquier ayuda sería muy apreciada.

public class CustomMultiAutoCompleteTextView extends MultiAutoCompleteTextView
  implements LoaderManager.LoaderCallbacks<Cursor> {

    private final String DEBUG_TAG = getClass().getSimpleName().toString();
    private Messenger2 mContext;
    private RecipientsCursorAdapter mAdapter;
    private ContentResolver mContentResolver;
    private final char delimiter = ' ';
    private CustomMultiAutoCompleteTextView mView;

    // If non-null, this is the current filter the user has provided.
    private String mCurFilter;

    // These are the Contacts rows that we will retrieve.
    final String[] CONTACTS_SUMMARY_PROJECTION = new String[] {
        ContactsContract.Contacts._ID,
        ContactsContract.Contacts.DISPLAY_NAME };

    public CustomMultiAutoCompleteTextView(Context c) {
        super(c);
        init(c);
    }

    public CustomMultiAutoCompleteTextView(Context c, AttributeSet attrs) {
        super(c, attrs);
        init(c);
    }

    private void init(Context context) {
        mContext = (Messenger2) context;
        mContentResolver = mContext.getContentResolver();
        mView = this; 

        mAdapter = new RecipientsCursorAdapter(mContext, 0, null, new String[0], new int[0], mContext);

        mAdapter.setCursorToStringConverter(new CursorToStringConverter() {
            @Override
            public CharSequence convertToString(Cursor c) {
                String contactName = c.getString(c.getColumnIndexOrThrow(ContactsContract.Contacts.DISPLAY_NAME));
                return contactName;
            }
        });

        addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                Log.d(DEBUG_TAG, "onTextChanged()");
                if (!s.equals(""))
                    mCurFilter = s.toString();
                else
                    mCurFilter = "";

                mContext.getLoaderManager().restartLoader(0, null, mView);

            }

            @Override
            public void afterTextChanged(Editable s) {
            }
        });

        setAdapter(mAdapter);
        setTokenizer(new SpaceTokenizer());

        mContext.getLoaderManager().initLoader(0, null, this);

    }

    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        // This is called when a new Loader needs to be created. This
        // sample only has one Loader, so we don't care about the ID.
        // First, pick the base URI to use depending on whether we are
        // currently filtering.
        Log.d(DEBUG_TAG, "onCreateLoader()");
        Uri baseUri;
        if (mCurFilter != null) {
            baseUri = Uri.withAppendedPath( ContactsContract.Contacts.CONTENT_FILTER_URI,Uri.encode(mCurFilter));
        } else {
            baseUri = ContactsContract.Contacts.CONTENT_URI;
        }

        // Now create and return a CursorLoader that will take care of
        // creating a Cursor for the data being displayed.
        String selection = "((" + ContactsContract.Contacts.DISPLAY_NAME
                + " NOTNULL) AND ("
                + ContactsContract.Contacts.HAS_PHONE_NUMBER + "=1) AND ("
                + ContactsContract.Contacts.DISPLAY_NAME + " != '' ))";
        String sortOrder = ContactsContract.Contacts.DISPLAY_NAME
                + " COLLATE LOCALIZED ASC";

        return new CursorLoader(mContext, baseUri, CONTACTS_SUMMARY_PROJECTION,
                selection, null, sortOrder);
    }

    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        // Swap the new cursor in. (The framework will take care of closing
        // the old cursor once we return.)
        Log.d(DEBUG_TAG, "onLoadFinished()");
        mAdapter.swapCursor(data);

    }

    public void onLoaderReset(Loader<Cursor> loader) {
        // This is called when the last Cursor provided to onLoadFinished()
        // above is about to be closed. We need to make sure we are no
        // longer using it.
        Log.d(DEBUG_TAG, "onLoaderReset()");
        mAdapter.swapCursor(null);
    }

    private class SpaceTokenizer implements Tokenizer {

        public int findTokenStart(CharSequence text, int cursor) {
            int i = cursor;

            while (i > 0 && text.charAt(i - 1) != delimiter) {
                i--;
            }
            while (i < cursor && text.charAt(i) == delimiter) {
                i++;
            }

            return i;
        }

        public int findTokenEnd(CharSequence text, int cursor) {
            int i = cursor;
            int len = text.length();

            while (i < len) {
                if (text.charAt(i) == delimiter) {
                    return i;
                } else {
                    i++;
                }
            }

            return len;
        }

        public CharSequence terminateToken(CharSequence text) {
            Log.d(DEBUG_TAG, "terminateToken()");
            int i = text.length();
            while (i > 0 && text.charAt(i - 1) == delimiter) {
                i--;
            }

            if (i > 0 && text.charAt(i - 1) == delimiter) {
                return text;
            } else {

                CharSequence contactName = createContactBubble(text);

                return contactName;
            }
        }

    }

}

ACTUALIZACIÓN 1

Ahora estoy llamando alsetStringConversionColumn() método en lugar desetCursorToStringConverter() como sugirió @Olaf. He puesto esto en elonLoadFinished() ya que esta es la única vez que elCursor está disponible ya que esto está implementando unaLoaderManger.

public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
    // Swap the new cursor in. (The framework will take care of closing
    // the old cursor once we return.)
    Log.d(DEBUG_TAG, "onLoadFinished()");   
    mAdapter.setStringConversionColumn(data.getColumnIndexOrThrow(ContactsContract.Contacts.DISPLAY_NAME)); 
    mAdapter.swapCursor(data);
}

Esto funciona en la selección de un elemento para elMultiAutoCompleteTextView, pero no permitirá que se seleccionen varios elementos en elMultiAutoCompleteTextView.

Supongo que hay algún problema con elonTextChanged() método ya que llamarestartLoader(). Esto funciona para la primera entrada en esta vista pero no para las entradas posteriores. No estoy muy seguro en este punto de lo que está mal.

ACTUALIZACIÓN 2

Así que he identificado el problema. El problema es el TextWatcheronTextChanged() método. Después de hacer la selección para terminar el primer token (digamos que el token era "Joe Johnson"), luego ingrese más caracteres en esteMultiAutoCompleteTextView ( comoal ) el valor del args que se pasa a laonTextChanged() El método ahora contiene no solo los caracteres adicionales agregados sino también los caracteres del token que se terminó anteriormente (el valor des en este punto esJoe Johnson al ). Ahora el valor demCursor se establece enJoe Johnson al que posteriormente se pasa a la consulta enonCreateLoader() Lo que obviamente no devolverá resultados. ¿Hay alguna forma de evitar esta situación? Estoy abierto a cualquier sugerencia.

ACTUALIZACIÓN 3

Cuando implementé una costumbreMultiAutoCompleteTextView respaldado por unSimpleCursorAdapter junto con una costumbreTokenizer Puse unFilterQueryProvider Me gusta esto:

mAdapter.setFilterQueryProvider(new FilterQueryProvider() {
    @Override
    public Cursor runQuery(CharSequence constraint) {
    Log.d(DEBUG_TAG, "runQuery() : constraint " + constraint);
        Uri baseUri;
        if (constraint != null) {
            baseUri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_FILTER_URI,
                Uri.encode(constraint.toString()));
        } else {
            baseUri = ContactsContract.Contacts.CONTENT_URI;
            }

        String selection = "((" + ContactsContract.Contacts.DISPLAY_NAME
            + " NOTNULL) AND ("
            + ContactsContract.Contacts.HAS_PHONE_NUMBER + "=1) AND ("
            + ContactsContract.Contacts.DISPLAY_NAME + " != '' ))";

        final String[] CONTACTS_SUMMARY_PROJECTION = new String[] {
            ContactsContract.Contacts._ID,
            ContactsContract.Contacts.DISPLAY_NAME};
        String sortOrder = ContactsContract.Contacts.DISPLAY_NAME
            + " COLLATE LOCALIZED ASC";

        Cursor c = mContentResolver.query(baseUri,
    CONTACTS_SUMMARY_PROJECTION, selection, null, sortOrder);
        return c;
    }
});

Y por alguna razón larunQuery() método se llama dos veces desde el TextWatcheronTextChanged() método:

public void onTextChanged(CharSequence s, int start, int before,
                int count) {
    Log.d(DEBUG_TAG, "onTextChanged()  : s " + s);
    mAdapter.getFilterQueryProvider().runQuery(s);
}

Así que en mi ejemplo anterior, elconstraint variable que se pasa a larunQuery() método la primera vez esJoe Johnson al. Entonces la segunda vezrunQuery() método se llama el valor de laconstraint variable esal. No se porquerunQuery() método se ejecuta dos veces cuando se llama una sola vez en elonTextChanged() método.

Respuestas a la pregunta(1)

Su respuesta a la pregunta