The Android Developer Challenge is back! Submit your idea before December 2.

Recuperar detalhes de um contato

Esta lição mostra como recuperar dados detalhados de um contato, por exemplo, endereços de e-mail, números de telefone etc. Esses são os detalhes que os usuários procuram quando recuperam um contato. Você pode fornecer a eles todos os detalhes de um contato ou exibir apenas detalhes de um tipo específico, por exemplo, endereços de e-mail.

As etapas descritas nesta lição pressupõem que você já tenha uma linha ContactsContract.Contacts para um contato selecionado pelo usuário. A lição Como recuperar nomes de contatos mostra como recuperar uma lista de contatos.

Recuperar todos os detalhes de um contato

Para recuperar todos os detalhes de um contato, na tabela ContactsContract.Data procure linhas que contenham o LOOKUP_KEY do contato. Essa coluna está disponível na tabela ContactsContract.Data, porque o Provedor de contatos cria uma mesclagem implícita entre as tabelas ContactsContract.Contacts e ContactsContract.Data. A coluna LOOKUP_KEY é descrita em mais detalhes na lição Como recuperar nomes de contatos.

Observação a recuperação de todos os detalhes de um contato reduz o desempenho de um dispositivo, porque ele precisa recuperar todas as colunas da tabela ContactsContract.Data. Antes de usar essa técnica, considere o impacto no desempenho.

Solicitar permissões

Para fazer leituras no Provedor de contatos, seu app deve ter a permissão READ_CONTACTS. Para solicitar essa permissão, adicione o seguinte elemento filho de <manifest> ao seu arquivo de manifesto:

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

Configurar uma projeção

Dependendo do tipo de dados que uma linha contém, ela só pode usar algumas colunas ou muitas delas. Além disso, os dados estão em colunas diferentes, dependendo do tipo. Para garantir que você tenha todas as colunas possíveis para todos os tipos de dados possíveis, adicione todos os nomes de colunas à sua projeção. Sempre recupere Data._ID, se você estiver vinculando o Cursor do resultado a uma ListView; caso contrário, a vinculação não funcionará. Recupere também Data.MIMETYPE para que você possa identificar o tipo de dados de cada linha que você recuperar. Exemplo:

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

Essa projeção recupera todas as colunas de uma linha na tabela ContactsContract.Data, usando os nomes de coluna definidos na classe ContactsContract.Data.

Você também pode usar outras constantes de coluna definidas em ou herdadas pela classe ContactsContract.Data. Observe, no entanto, que as colunas de SYNC1 a SYNC4 devem ser usadas por adaptadores de sincronização; portanto, os dados correspondentes não são úteis.

Definir os critérios de seleção

Defina uma constante para sua cláusula de seleção, uma matriz para promover argumentos de seleção e uma variável para promover o valor da seleção. Use a coluna Contacts.LOOKUP_KEY para encontrar o contato. Exemplo:

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
    

O uso de "?" como um marcador na sua expressão de texto de seleção garante que a pesquisa resultante seja gerada por vinculação e não por compilação SQL. Essa abordagem elimina a possibilidade de injeção maliciosa de SQL.

Definir a ordem de classificação

Defina a ordem de classificação desejada no Cursor resultante. Para manter todas as linhas juntas para um determinado tipo de dados, classifique por Data.MIMETYPE. Esse argumento de consulta agrupa todas as linhas de e-mail, todas as linhas de telefone e assim por diante. Exemplo:

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;
    

Observação: alguns tipos de dados não usam um subtipo; portanto, não é possível classificá-los por subtipo. Em vez disso, é necessário iterar o Cursor retornado, determinar o tipo de dados da linha atual e armazenar dados para as linhas que usam um subtipo. Quando você terminar de ler o cursor, poderá classificar cada tipo de dados por subtipo e exibir os resultados.

Inicializar o carregador

Sempre faça recuperações a partir do Provedor de contatos (e de todos os outros provedores de conteúdo) em uma linha de execução em segundo plano. Use o framework do carregador definido pela classe LoaderManager e pela interface LoaderManager.LoaderCallbacks para fazer recuperações em segundo plano.

Quando estiver pronto para recuperar as linhas, inicialize o framework do carregador chamando initLoader(). Transmita um identificador de número inteiro ao método; esse identificador é transmitido aos métodos LoaderManager.LoaderCallbacks. O identificador ajuda você a usar vários carregadores em um app, permitindo diferenciá-los.

O seguinte snippet mostra como inicializar o framework do carregador:

Kotlin

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

    class DetailsFragment : Fragment(), LoaderManager.LoaderCallbacks<Cursor> {
        ...
        override fun onActivityCreated(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;
        ...
        /*
         * Invoked when the parent Activity is instantiated
         * and the Fragment's UI is ready. Put final initialization
         * steps here.
         */
        @Override
        onActivityCreated(Bundle savedInstanceState) {
            ...
            // Initializes the loader framework
            getLoaderManager().initLoader(DETAILS_QUERY_ID, null, this);
    

Implementar onCreateLoader()

Implemente o método onCreateLoader(), chamado pelo framework do carregador imediatamente depois de chamar initLoader(). Retorne um CursorLoader a partir desse método. Como você está pesquisando a tabela ContactsContract.Data, use a constante Data.CONTENT_URI como o URI do conteúdo. Por exemplo:

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

Implementar onLoadFinished() e onLoaderReset()

Implemente o método onLoadFinished(). O framework do carregador chama onLoadFinished() quando o Provedor de contatos retorna os resultados da consulta. Exemplo:

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

O método onLoaderReset() é chamado quando o framework do carregador detecta que os dados que suportam o Cursor resultante foram alterados. Nesse momento, remova as referências a Cursor já existentes, definindo-as como nulas. Caso contrário, o framework do carregador não destruirá o Cursor antigo, e ocorrerá vazamento de memória. Exemplo:

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

Recuperar detalhes específicos de um contato

A recuperação de um tipo de dados específico para um contato, por exemplo, todos os e-mails, segue o mesmo padrão que o usado na recuperação de todos os detalhes. Estas são as únicas alterações que você precisa fazer no código listado em Recuperar todos os detalhes de um contato:

Projeção
Modifique sua projeção para recuperar as colunas específicas ao tipo de dados. Modifique também a projeção para usar as constantes do nome da coluna definidas na subclasse ContactsContract.CommonDataKinds correspondente ao tipo de dados.
Seleção
Modifique o texto da seleção para procurar o valor MIMETYPE específico ao seu tipo de dados.
Ordem de classificação
Como você está selecionando apenas um único tipo de detalhe, não agrupe o Cursor retornado por Data.MIMETYPE.

Essas modificações são descritas nas seções a seguir.

Definir uma projeção

Defina as colunas que você quer recuperar, usando as constantes do nome da coluna na subclasse de ContactsContract.CommonDataKinds para o tipo de dados. Se você planeja vincular seu Cursor a uma ListView, recupere a coluna _ID. Por exemplo, para recuperar dados de e-mail, defina a seguinte projeção:

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

Observe que essa projeção usa os nomes de colunas definidas na classe ContactsContract.CommonDataKinds.Email, em vez dos nomes das colunas definidas na classe ContactsContract.Data. O uso dos nomes de colunas específicas de e-mail torna o código mais legível.

Na projeção, você também pode usar qualquer uma das outras colunas definidas na subclasse ContactsContract.CommonDataKinds.

Definir critérios de seleção

Defina uma expressão de texto de pesquisa que recupere linhas para o LOOKUP_KEY um contato específico e o Data.MIMETYPE dos detalhes desejados. Coloque o valor MIMETYPE entre aspas simples, concatenando um caractere "'" (aspas simples) no início e no final da constante. Caso contrário, o provedor interpretará a constante como um nome de variável e não como um valor de string. Você não precisa usar um marcador para esse valor, porque você está usando um valor constante em vez de um valor fornecido pelo usuário. Exemplo:

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 = { "" };
    

Definir uma ordem de classificação

Defina uma ordem de classificação para o Cursor retornado. Como você está recuperando um tipo de dados específico, omita a classificação por MIMETYPE. Em vez disso, se o tipo de dados detalhados que você estiver pesquisando incluir um subtipo, classifique-os por subtipo. Por exemplo, para dados de e-mail, você pode classificar por Email.TYPE:

Kotlin

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

Java

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