El uso de CursorLoader para obtener correos electrónicos provoca la duplicación de correos electrónicos

Estoy tratando de obtener ids de correo electrónico de contactos utiliza. Para eso estoy utilizando Cursor Loader. Hay un problema que estoy recibiendo identificadores de correo electrónico duplicados también. Cómo eliminar duplicidad de correo electrónico. ¿Debo utilizar la consulta en bruto "SELECT DISTINCT" en lugar de utilizar CursorLoader o hay alguna otra solución?

@Override public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) { String[] projection = new String[] { ContactsContract.Contacts._ID, ContactsContract.Contacts.DISPLAY_NAME, ContactsContract.CommonDataKinds.Email.DATA}; String sortOrder = ContactsContract.Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC"; String selection = ContactsContract.Contacts.IN_VISIBLE_GROUP +"='1' AND " + Email.DATA +" IS NOT NULL AND " + Email.DATA +" != \"\" " ; //showing only visible contacts String[] selectionArgs = null; return new CursorLoader(this, ContactsContract.CommonDataKinds.Email.CONTENT_URI, projection, selection, selectionArgs, sortOrder); } 

Recientemente me encontré con este problema. Parece que el CursorLoader no tiene una implementación de "DISTINCT". Mi solución añade algunas líneas al método onLoadFinish y extiende el BaseAdapter para aceptar un parámetro List:

 @Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { String projection[] = { CommonDataKinds.Phone._ID, CommonDataKinds.Phone.DISPLAY_NAME, }; String select = "((" + CommonDataKinds.Phone.DISPLAY_NAME + " NOTNULL) and " + CommonDataKinds.Phone.HAS_PHONE_NUMBER + " > 0)"; String sort = CommonDataKinds.Phone.DISPLAY_NAME + " ASC"; CursorLoader loader = new CursorLoader( mContext, CommonDataKinds.Phone.CONTENT_URI, projection, select, null, sort ); return loader; } @Override public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { List<String> displayNames = new ArrayList<String>(); cursor.moveToFirst(); while(!cursor.isAfterLast()){ String name = cursor.getString(cursor.getColumnIndex(CommonDataKinds.Phone.DISPLAY_NAME)); if(!displayNames.contains(name)) displayNames.add(name); cursor.moveToNext(); } mAdapter.swapCursor(displayNames); } 

Aquí está mi clase BaseAdapter:

 public class AdapterAddContacts extends BaseAdapter{ private List<String> mData = new ArrayList<String>(); private Context mContext; public AdapterAddContacts(Context context,List<String> displayNames){ mData = displayNames; mContext = context; } @Override public int getCount() { if(mData != null) return mData.size(); else return 0; } @Override public Object getItem(int pos) { return mData.get(pos); } @Override public long getItemId(int id) { return id; } @Override public View getView(int pos, View convertView, ViewGroup parent) { LayoutInflater inflater = LayoutInflater.from(mContext); View view = inflater.inflate(R.layout.entry_add_contacts,parent,false); String data = mData.get(pos); TextView textName = (TextView)view.findViewById(R.id.my_contacts_add_display_name); textName.setText(data); textName.setTag(data); return view; } public void swapCursor(List<String> displayNames){ mData = displayNames; this.notifyDataSetChanged(); } 

Usted debe ser capaz de modificar esto específicamente para sus necesidades.

Inspirado por @mars, tengo una solución que no necesita una modificación del adaptador. La idea es eliminar los duplicados del cursor; Como no hay manera de hacerlo, creamos un cursor nuevo sin los duplicados.

Todo el código está en onLoadFinished :

 @Override public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { MatrixCursor newCursor = new MatrixCursor(PROJECTION); // Same projection used in loader if (cursor.moveToFirst()) { String lastName = ""; do { if (cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)).compareToIgnoreCase(lastName) != 0) { newCursor.addRow(new Object[]{cursor.getString(0), cursor.getString(1), cursor.getString(2) ...}); // match the original cursor fields lastName =cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME)); } } while (cursor.moveToNext()); } mContactsAdapter.swapCursor(newCursor); } 

He utilizado un pequeño hack en mi proyecto – una inyección de SQL, así:

 @Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { return new CursorLoader( this, MediaStore.Images.Media.EXTERNAL_CONTENT_URI, new String[] { "DISTINCT "+ MediaStore.Images.Media.BUCKET_ID, MediaStore.Images.Media.BUCKET_DISPLAY_NAME}, null, null, null); } 

Este código devuelve sólo nombres de bundle y sus ID de Galería. Así que, reescribiría tu código así:

 @Override public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) { String[] projection = new String[] { "DISTINCT " + ContactsContract.Contacts._ID, ContactsContract.Contacts.DISPLAY_NAME, ContactsContract.CommonDataKinds.Email.DATA}; String sortOrder = ContactsContract.Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC"; String selection = ContactsContract.Contacts.IN_VISIBLE_GROUP +"='1' AND " + Email.DATA +" IS NOT NULL AND " + Email.DATA +" != \"\" " ; //showing only visible contacts String[] selectionArgs = null; return new CursorLoader(this, ContactsContract.CommonDataKinds.Email.CONTENT_URI, projection, selection, selectionArgs, sortOrder); } 

Puede poner setDistinct en su proveedor de contenido.

  @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { ... final SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); qb.setDistinct(true); 

Si estás preocupado por el rendimiento y no quieres jugar con el cursor de nuevo en onLoadFinished (), entonces hay un pequeño hack

Me combiné siguiendo dos soluciones de SO.

  1. Seleccionar un valor distinto en android sqlite

  2. CursorLoader con rawQuery

Y aquí está mi solución de trabajo:

  @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { String tableName; /* * Choose the table to query and a sort order based on the code returned * for the incoming URI. */ switch (uriMatcher.match(uri)) { case NOTIFICATION: tableName = NOTIFICATIONS_TABLE_NAME; break; case NOTIFICATION_TIMESTAMP: Cursor cursor = db.query(true, NOTIFICATIONS_TABLE_NAME, projection, selection, selectionArgs, TIMESTAMP, null, sortOrder, null); cursor.setNotificationUri(getContext().getContentResolver(), uri); return cursor; case DOWNLOAD: tableName = DOWNLOADS_TABLE; break; default: throw new IllegalArgumentException("Unknown URI " + uri); } if (selection != null) { selection = selection + "=?"; } Cursor cursor = db.query(tableName, projection, selection, selectionArgs, null, null, sortOrder); // Tell the cursor what uri to watch, so it knows when its source data // changes cursor.setNotificationUri(getContext().getContentResolver(), uri); return cursor; } 

Si ves en este caso el nombre de la tabla es el mismo es primeros 2 casos, pero he creado un maniquí Uri para lograr esto. No puede ser un enfoque muy bueno, pero funciona perfectamente.

Encontré una solución Utilizar la palabra clave DISTINCT en la matriz de selección.

 String[] projection = new String[] { ContactsContract.Contacts._ID, ContactsContract.Contacts.DISPLAY_NAME, "DISTINCT" + ContactsContract.CommonDataKinds.Email.DATA}; 
  • Obtener SQLiteCursorLoader para observar los cambios de datos
  • Cursorloader no se actualiza cuando los datos subyacentes cambian
  • Buscar lista de nombres de género con canciones
  • ¿Cómo insertar correctamente valores en la base de datos SQLite utilizando el método insert () de ContentProvider mediante un CursorLoader?
  • Android: la posición de onItemClick () es relativa al área visible, no toda la lista
  • ¿Cómo (correctamente) la transición de startManagingCursor a CursorLoader?
  • Mostrar indicador de progreso antes de listview de relleno con CursorLoader
  • ¿Qué hace LoaderManager?
  • Uso de CursorLoader sin ContentProvider
  • Android OnLoadFinished () no se llama cuando el cursor cambia
  • Deslizar para eliminar un cardview hace parpadear durante un breve segundo antes de animar
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.