Recuperare i dettagli di un contatto

Questa lezione spiega come recuperare i dati dettagliati di un contatto, come indirizzi email, numeri di telefono e così via. Si tratta dei dettagli che gli utenti cercano quando recuperano un contatto. Puoi fornire tutti i dettagli di un contatto o visualizzare solo i dettagli di un determinato tipo, come gli indirizzi email.

I passaggi di questa lezione presuppongono che sia già presente una riga ContactsContract.Contacts per un contatto scelto dall'utente. La lezione Recupero dei nomi dei contatti mostra come recuperare un elenco di contatti.

Recupera tutti i dettagli per un contatto

Per recuperare tutti i dettagli su un contatto, cerca nella tabella ContactsContract.Data eventuali righe che contengono LOOKUP_KEY del contatto. Questa colonna è disponibile nella tabella ContactsContract.Data, perché il provider di contatti effettua un join implicito tra la tabella ContactsContract.Contacts e la tabella ContactsContract.Data. La colonna LOOKUP_KEY è descritta più dettagliatamente nella lezione Recupero dei nomi dei contatti.

Nota: il recupero di tutti i dettagli di un contatto riduce le prestazioni del dispositivo, in quanto deve recuperare tutte le colonne nella tabella ContactsContract.Data. Valuta l'impatto sulle prestazioni prima di utilizzare questa tecnica.

Richiedi autorizzazioni

Per leggere dal provider di contatti, la tua app deve disporre dell'autorizzazione READ_CONTACTS. Per richiedere questa autorizzazione, aggiungi il seguente elemento secondario di <manifest> al file manifest:

    <uses-permission android:name="android.permission.READ_CONTACTS" />

Configurare una proiezione

A seconda del tipo di dati contenuti in una riga, questa potrebbe utilizzare solo poche colonne o molte. Inoltre, i dati si trovano in colonne diverse a seconda del tipo di dati. Per assicurarti di ottenere tutte le colonne possibili per tutti i tipi di dati possibili, devi aggiungere tutti i nomi di colonna alla proiezione. Recupera sempre Data._ID se associ il risultato Cursor a un ListView; in caso contrario, l'associazione non funzionerà. Recupera anche Data.MIMETYPE in modo da poter identificare il tipo di dati di ogni riga recuperata. Ecco alcuni esempi:

Kotlin

private val PROJECTION: Array<out String> = arrayOf(
        ContactsContract.Data._ID,
        ContactsContract.Data.MIMETYPE,
        ContactsContract.Data.DATA1,
        ContactsContract.Data.DATA2,
        ContactsContract.Data.DATA3,
        ContactsContract.Data.DATA4,
        ContactsContract.Data.DATA5,
        ContactsContract.Data.DATA6,
        ContactsContract.Data.DATA7,
        ContactsContract.Data.DATA8,
        ContactsContract.Data.DATA9,
        ContactsContract.Data.DATA10,
        ContactsContract.Data.DATA11,
        ContactsContract.Data.DATA12,
        ContactsContract.Data.DATA13,
        ContactsContract.Data.DATA14,
        ContactsContract.Data.DATA15
)

Java

    private static final String[] PROJECTION =
            {
                ContactsContract.Data._ID,
                ContactsContract.Data.MIMETYPE,
                ContactsContract.Data.DATA1,
                ContactsContract.Data.DATA2,
                ContactsContract.Data.DATA3,
                ContactsContract.Data.DATA4,
                ContactsContract.Data.DATA5,
                ContactsContract.Data.DATA6,
                ContactsContract.Data.DATA7,
                ContactsContract.Data.DATA8,
                ContactsContract.Data.DATA9,
                ContactsContract.Data.DATA10,
                ContactsContract.Data.DATA11,
                ContactsContract.Data.DATA12,
                ContactsContract.Data.DATA13,
                ContactsContract.Data.DATA14,
                ContactsContract.Data.DATA15
            };

Questa proiezione recupera tutte le colonne per una riga nella tabella ContactsContract.Data, utilizzando i nomi delle colonne definiti nella classe ContactsContract.Data.

Facoltativamente, puoi utilizzare anche qualsiasi altra costante di colonna definita o ereditate dalla classe ContactsContract.Data. Tuttavia, tieni presente che le colonne da SYNC1 a SYNC4 sono destinate all'uso da parte di adattatori di sincronizzazione, pertanto i relativi dati non sono utili.

Definisci i criteri di selezione

Definisci una costante per la clausola di selezione, un array per contenere gli argomenti di selezione e una variabile per contenere il valore di selezione. Utilizza la colonna Contacts.LOOKUP_KEY per trovare il contatto. Ecco alcuni esempi:

Kotlin

// Defines the selection clause
private const val SELECTION: String = "${ContactsContract.Data.LOOKUP_KEY} = ?"
...
// Defines the array to hold the search criteria
private val selectionArgs: Array<String> = arrayOf("")
/*
 * Defines a variable to contain the selection value. Once you
 * have the Cursor from the Contacts table, and you've selected
 * the desired row, move the row's LOOKUP_KEY value into this
 * variable.
 */
private var lookupKey: String? = null

Java

    // Defines the selection clause
    private static final String SELECTION = Data.LOOKUP_KEY + " = ?";
    // Defines the array to hold the search criteria
    private String[] selectionArgs = { "" };
    /*
     * Defines a variable to contain the selection value. Once you
     * have the Cursor from the Contacts table, and you've selected
     * the desired row, move the row's LOOKUP_KEY value into this
     * variable.
     */
    private lateinit var lookupKey: String

L'uso di "?" come segnaposto nell'espressione di testo di selezione garantisce che la ricerca risultante venga generata da un'associazione anziché da una compilazione SQL. Questo approccio elimina la possibilità di operazioni SQL injection dannose.

Definire l'ordinamento

Definisci l'ordinamento desiderato nel Cursor risultante. Per mantenere insieme tutte le righe di un determinato tipo di dati, ordina per Data.MIMETYPE. Questo argomento di query raggruppa tutte le righe di email, tutte le righe di numeri di telefono insieme e così via. Ecco alcuni esempi:

Kotlin

/*
 * Defines a string that specifies a sort order of MIME type
 */
private const val SORT_ORDER = ContactsContract.Data.MIMETYPE

Java

    /*
     * Defines a string that specifies a sort order of MIME type
     */
    private static final String SORT_ORDER = ContactsContract.Data.MIMETYPE;

Nota:alcuni tipi di dati non utilizzano un sottotipo, quindi non puoi ordinarli in base al sottotipo. Devi invece eseguire l'iterazione del valore Cursor restituito, determinare il tipo di dati della riga corrente e archiviare i dati delle righe che utilizzano un sottotipo. Al termine della lettura del cursore, puoi ordinare ogni tipo di dati per sottotipo e visualizzare i risultati.

Inizializza il Loader

Esegui sempre i recuperi dal fornitore di contatti (e da tutti gli altri fornitori di contenuti) in un thread in background. Utilizza il framework Loader definito dalla classe LoaderManager e dall'interfaccia LoaderManager.LoaderCallbacks per eseguire il recupero in background.

Quando è tutto pronto per recuperare le righe, inizializza il framework del caricatore chiamando initLoader(). Passa un identificatore numero intero al metodo; questo identificatore viene trasmesso ai metodi LoaderManager.LoaderCallbacks. L'identificatore consente di utilizzare più caricatori in un'app consentendoti di distinguerli.

Lo snippet seguente mostra come inizializzare il framework del caricatore:

Kotlin

// Defines a constant that identifies the loader
private const val DETAILS_QUERY_ID: Int = 0

class DetailsFragment : Fragment(), LoaderManager.LoaderCallbacks<Cursor> {
    ...
    override fun onCreate(savedInstanceState: Bundle?) {
        ...
        // Initializes the loader framework
        loaderManager.initLoader(DETAILS_QUERY_ID, null, this)

Java

public class DetailsFragment extends Fragment implements
        LoaderManager.LoaderCallbacks<Cursor> {
    ...
    // Defines a constant that identifies the loader
    static int DETAILS_QUERY_ID = 0;
    ...
    @Override
    public void onCreate(Bundle savedInstanceState) {
        ...
        // Initializes the loader framework
        getLoaderManager().initLoader(DETAILS_QUERY_ID, null, this);

Implementare onCreateLoader()

Implementa il metodo onCreateLoader(), che viene richiamato dal framework del caricatore subito dopo la chiamata a initLoader(). Restituisci un CursorLoader da questo metodo. Poiché stai cercando la tabella ContactsContract.Data, utilizza la costante Data.CONTENT_URI come URI dei contenuti. Ecco alcuni esempi:

Kotlin

override fun onCreateLoader(loaderId: Int, args: Bundle?): Loader<Cursor> {
    // Choose the proper action
    mLoader = when(loaderId) {
        DETAILS_QUERY_ID -> {
            // Assigns the selection parameter
            selectionArgs[0] = lookupKey
            // Starts the query
            activity?.let {
                CursorLoader(
                        it,
                        ContactsContract.Data.CONTENT_URI,
                        PROJECTION,
                        SELECTION,
                        selectionArgs,
                        SORT_ORDER
                )
            }
        }
        ...
    }
    return mLoader
}

Java

@Override
    public Loader<Cursor> onCreateLoader(int loaderId, Bundle args) {
        // Choose the proper action
        switch (loaderId) {
            case DETAILS_QUERY_ID:
            // Assigns the selection parameter
            selectionArgs[0] = lookupKey;
            // Starts the query
            CursorLoader mLoader =
                    new CursorLoader(
                            getActivity(),
                            ContactsContract.Data.CONTENT_URI,
                            PROJECTION,
                            SELECTION,
                            selectionArgs,
                            SORT_ORDER
                    );
    }

Implementare onLoadFinished() e onLoaderReset()

Implementa il metodo onLoadFinished(). Il framework del caricatore chiama onLoadFinished() quando il provider di contatti restituisce i risultati della query. Ecco alcuni esempi:

Kotlin

    override fun onLoadFinished(loader: Loader<Cursor>, data: Cursor) {
        when(loader.id) {
            DETAILS_QUERY_ID -> {
                /*
                 * Process the resulting Cursor here.
                 */
            }
            ...
        }
    }

Java

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
        switch (loader.getId()) {
            case DETAILS_QUERY_ID:
                    /*
                     * Process the resulting Cursor here.
                     */
                }
                break;
            ...
        }
    }

Il metodo onLoaderReset() viene richiamato quando il framework del caricatore rileva che i dati a supporto del risultato Cursor sono cambiati. A questo punto, rimuovi tutti i riferimenti esistenti a Cursor impostandoli su null. In caso contrario, il framework del caricatore non eliminerà il vecchio Cursor e causerai una perdita di memoria. Ecco alcuni esempi:

Kotlin

    override fun onLoaderReset(loader: Loader<Cursor>) {
        when (loader.id) {
            DETAILS_QUERY_ID -> {
                /*
                 * If you have current references to the Cursor,
                 * remove them here.
                 */
            }
            ...
        }
    }

Java

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
        switch (loader.getId()) {
            case DETAILS_QUERY_ID:
                /*
                 * If you have current references to the Cursor,
                 * remove them here.
                 */
                }
                break;
    }

Recuperare dettagli specifici su un contatto

Il recupero di un tipo di dati specifico per un contatto, ad esempio tutte le email, segue lo stesso pattern del recupero di tutti i dettagli. Queste sono le uniche modifiche che devi apportare al codice elencato in Recuperare tutti i dettagli per un contatto:

Previsione
Modifica la proiezione per recuperare le colonne specifiche per il tipo di dati. Modifica anche la proiezione per utilizzare le costanti dei nomi di colonna definite nella sottoclasse ContactsContract.CommonDataKinds corrispondente al tipo di dati.
Selezione
Modifica il testo di selezione per cercare il valore MIMETYPE specifico per il tuo tipo di dati.
Ordinamento
Poiché stai selezionando un solo tipo di dettaglio, non raggruppare il valore Cursor restituito per Data.MIMETYPE.

Queste modifiche sono descritte nelle sezioni seguenti.

Definisci una proiezione

Definisci le colonne da recuperare utilizzando le costanti dei nomi di colonna nella sottoclasse di ContactsContract.CommonDataKinds per il tipo di dati. Se prevedi di associare Cursor a ListView, assicurati di recuperare la colonna _ID. Ad esempio, per recuperare i dati delle email, definisci la seguente proiezione:

Kotlin

private val PROJECTION: Array<String> = arrayOf(
        ContactsContract.CommonDataKinds.Email._ID,
        ContactsContract.CommonDataKinds.Email.ADDRESS,
        ContactsContract.CommonDataKinds.Email.TYPE,
        ContactsContract.CommonDataKinds.Email.LABEL
)

Java

    private static final String[] PROJECTION =
            {
                ContactsContract.CommonDataKinds.Email._ID,
                ContactsContract.CommonDataKinds.Email.ADDRESS,
                ContactsContract.CommonDataKinds.Email.TYPE,
                ContactsContract.CommonDataKinds.Email.LABEL
            };

Tieni presente che questa proiezione utilizza i nomi delle colonne definiti nella classe ContactsContract.CommonDataKinds.Email, anziché i nomi delle colonne definiti nella classe ContactsContract.Data. L'utilizzo di nomi di colonna specifici per email rende il codice più leggibile.

Nella proiezione, puoi anche utilizzare una qualsiasi delle altre colonne definite nella sottoclasse ContactsContract.CommonDataKinds.

Definisci i criteri di selezione

Definisci un'espressione di testo di ricerca che recupera le righe relative a LOOKUP_KEY di un contatto specifico e a Data.MIMETYPE dei dettagli desiderati. Racchiudi il valore MIMETYPE tra virgolette singole concatenando un carattere "'" (virgolette singole) all'inizio e alla fine della costante; in caso contrario, il provider interpreta la costante come un nome di variabile anziché come un valore stringa. Non è necessario utilizzare un segnaposto per questo valore, perché stai utilizzando una costante anziché un valore fornito dall'utente. Ecco alcuni esempi:

Kotlin

/*
 * Defines the selection clause. Search for a lookup key
 * and the Email MIME type
 */
private const val SELECTION =
        "${ContactsContract.Data.LOOKUP_KEY} = ? AND " +
        "${ContactsContract.Data.MIMETYPE} = '${Email.CONTENT_ITEM_TYPE}'"
...
// Defines the array to hold the search criteria
private val selectionArgs: Array<String> = arrayOf("")

Java

    /*
     * Defines the selection clause. Search for a lookup key
     * and the Email MIME type
     */
    private static final String SELECTION =
            Data.LOOKUP_KEY + " = ?" +
            " AND " +
            Data.MIMETYPE + " = " +
            "'" + Email.CONTENT_ITEM_TYPE + "'";
    // Defines the array to hold the search criteria
    private String[] selectionArgs = { "" };

Definire un ordinamento

Definisci un ordinamento per Cursor restituito. Poiché stai recuperando un tipo di dati specifico, ometti l'ordinamento in MIMETYPE. Se invece il tipo di dati dettagliati che stai cercando include un sottotipo, puoi ordinarlo in base a questo criterio. Ad esempio, per i dati delle email puoi ordinare in base a Email.TYPE:

Kotlin

private const val SORT_ORDER: String = "${Email.TYPE} ASC"

Java

    private static final String SORT_ORDER = Email.TYPE + " ASC ";