33

Tengo problemas para extender el MultiAutoCompleteTextView y respaldarlo con un CursorLoader, al mismo tiempo que utilizo un Tokenizer personalizado. El problema se plantea específicamente con la llamada mAdapter.setCursorToStringConverter();. El método convertToString() 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ómo el LoaderManager maneja el CursorLoader.AutoCompleteTextView respaldado por CursorLoader

Si comento el método setCursorToStringConverter(), entonces veo una lista de opciones disponibles basadas en el texto que ingresé en esta vista. Sin embargo, dado que no hay un método convertToString() implementado, el método terminateToken() del Tokenizer personalizado no recibe la cadena que pretendo que sea, sino más bien una cadena representativa del objeto cursor, ya que el cursor no se ha utilizado para obtener el valor de cadena actual de una columna deseada en la consulta resultante.

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

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

He podido implementar un MultiAutoCompleteTextView personalizado respaldado por un SimpleCursorAdapter junto con un Tokenizer personalizado. Me preguntaba si es posible implementar esto usando un CursorLoader en su lugar, ya que el modo estricto se queja de que el cursor en MultiAutoCompleteTextView no está cerrado explícitamente.

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 que estoy llamando el método en lugar del setCursorToStringConverter()setStringConversionColumn() como se sugiere @Olaf. Lo configuré en el onLoadFinished() ya que esta es la única vez que el Cursor está disponible ya que está implementando un LoaderManger.

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 la MultiAutoCompleteTextView, pero no le permitirán elementos que deben seleccionarse en el MultiAutoCompleteTextView.

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

ACTUALIZACIÓN 2

Así que han identificado el problema. El problema es el método onTextChanged() de TextWatcher. Después de hacer la selección para terminar el primer token (digamos que el token era "Joe Johnson"), ingresando más caracteres en este MultiAutoCompleteTextView (como al), el valor del argumento s que pasa al método onTextChanged() ahora contiene no solo los caracteres añadidos adicionalmente, pero también los caracteres del token que se ha terminado previamente (el valor de s en este momento es Joe Johnson al).Ahora el valor de mCursor se establece en Joe Johnson al, que posteriormente pasa a la consulta en onCreateLoader(), que obviamente no arrojará resultados. ¿Hay alguna forma de evitar esta situación? Estoy abierto a cualquier sugerencia.

ACTUALIZACIÓN 3

Cuando he implementado una costumbre MultiAutoCompleteTextView respaldado por una SimpleCursorAdapter junto con una costumbre Tokenizer fijo una FilterQueryProvider así:

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 el método runQuery() se llama dos veces desde el método onTextChanged() del TextWatcher:

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, la variable constraint que se pasa al método runQuery() la primera vez es Joe Johnson al. Luego, la segunda vez que se llama el método runQuery(), el valor de la variable constraint es al. No sé por qué el método runQuery() se ejecuta dos veces cuando solo se lo llama una vez en el método onTextChanged().

+0

En su caso, puede usar 'setStringConversionColumn()' en lugar de 'setCursorToStringConverter()'. –

+0

Solo para entender lo que está haciendo, está consultando una gran cantidad de datos por cambio de texto, ¿no? ¿Es esto realmente necesario en tu caso? – JanithaR

Respuesta

3

Básicamente, androids autocomplete textview no es muy potente, cuando tengo que lidiar con grandes cantidades de datos, lo que hago es mantener un detector de cambios de texto en el texto de edición para buscar, y luego cada vez que se cambia algo el texto de edición, consulta la base de datos.

Si esto podría ayudar a alguien, colocando un EditarTexto en onCreate

EditText etSearch = (EditText)findViewById(R.id.etSearchBox); 
etSearch.addTextChangedListener(filterTextWatcher); 

//The filterTextWatcher is 

private TextWatcher filterTextWatcher = new TextWatcher() { 
    @Override 
    public void afterTextChanged(Editable s) { 
    } 

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

    @Override 
    public void onTextChanged(CharSequence s, int start, int before,int count) { 
     adapter.getFilter().filter(s.toString()); 
     } 
    }; 

Así, en su adaptador, es necesario crear un método getFilter() ...

@Override 
    public Filter getFilter() { 
    if (nameFilter == null) { 
     nameFilter = new NameFilter(); 
    } 
    return nameFilter; 
} 

    private class NameFilter extends Filter { 

    @Override 
    protected FilterResults performFiltering(CharSequence constraint) { 
    FilterResults results = new FilterResults(); 
    Cursor cursor = null; 
    // get your cursor by passing appropriate query here 
    results.values = cursor; 
    results.count = cursor.getCount(); 
    return results; 
    } 

    @Override 
    protected void publishResults(CharSequence constraint, FilterResults results) { 
    notifyDataSetChanged(); 
     } 
    } 
Cuestiones relacionadas