AutoCompleteTextView respaldado pelo CursorLoader

Então estou tendo problemas para estender oMultiAutoCompleteTextView e apoiando-o com umCursorLoader, enquanto simultaneamente usa um costumeTokenizer. A questão surge especificamente com amAdapter.setCursorToStringConverter(); ligar. oconvertToString() método que tem um cursor como um argumento tem um cursor válido e não fechado após a primeira chamada para este método. No entanto, as chamadas subsequentes resultam em um cursor nulo ou em um cursor fechado. Eu estou supondo que isso tem algo a ver com como oLoaderManager gerencia oCursorLoader.

Se eu comentar osetCursorToStringConverter() método fora, então eu vejo uma lista de opções disponíveis com base no texto que eu entrei nesta visão. No entanto, como não háconvertToString() método implementado, então oterminateToken() método do costumeTokenizer não recebe a string que pretendo que seja, mas sim uma string representativa do objeto cursor, uma vez que o cursor não foi usado para obter o valor atual da string de uma coluna desejada na consulta resultante.

Alguém foi capaz de implementar a combinação das três classes (CursorLoader/LoaderManger, MultiAutoCompleteTextVieweTokenizer)

Eu estou indo na direção certa com isso, ou isso simplesmente não é possível?

Eu pude implementar um costumeMultiAutoCompleteTextView apoiado por umSimpleCursorAdapter junto com um costumeTokenizer. Eu só queria saber se é possível implementar isso usando umCursorLoader em vez disso, desde que o modo estrito reclama sobre o cursor emMultiAutoCompleteTextView não sendo explicitamente fechado.

Qualquer ajuda seria muito 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;
            }
        }

    }

}

ATUALIZAÇÃO 1

Agora estou chamando osetStringConversionColumn() método em vez dosetCursorToStringConverter() como sugerido pelo @Olaf. Eu coloquei isso noonLoadFinished() desde que esta é a única vez que oCursor está disponível, pois isso está implementando umLoaderManger.

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

Isso funciona na seleção de um item para oMultiAutoCompleteTextView, mas não permitirá que vários itens sejam selecionados noMultiAutoCompleteTextView.

Eu estou supondo que há algum problema com oonTextChanged() método desde que ele chamarestartLoader(). Isso funciona para a primeira entrada nesta exibição, mas não para entradas subsequentes. Eu não estou muito certo neste momento o que está errado.

ATUALIZAÇÃO 2

Então eu identifiquei o problema. O problema é o TextWatcheronTextChanged() método. Depois de fazer a seleção para terminar o primeiro token (digamos que o token era "Joe Johnson"), então inserindo mais caracteres para esteMultiAutoCompleteTextView ( tal comoal ) o valor do args que é passado para oonTextChanged() método agora contém não apenas os caracteres adicionados adicionalmente, mas também os caracteres do token que foi anteriormente encerrado (o valor des neste momento éJoe Johnson al ). Agora o valor demCursor fica definido paraJoe Johnson al que subsequentemente é passado para a consulta emonCreateLoader() que obviamente não retornará nenhum resultado. Existem maneiras de contornar essa situação? Eu estou aberto a quaisquer sugestões.

ATUALIZAÇÃO 3

Quando eu implementei um costumeMultiAutoCompleteTextView apoiado por umSimpleCursorAdapter junto com um costumeTokenizer Eu definir umFilterQueryProvider como isso:

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

E por algum motivo orunQuery() método é chamado duas vezes a partir do 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);
}

Então, no meu exemplo anterior, oconstraint variável que é passada para orunQuery() método pela primeira vez éJoe Johnson al. Então a segunda vezrunQuery() método é chamado o valor doconstraint variável éal. Eu não sei porque orunQuery() método é executado duas vezes quando é chamado apenas uma vez noonTextChanged() método.

questionAnswers(1)

yourAnswerToTheQuestion