Recuperar uma lista de contatos

Esta lição mostra como recuperar uma lista de contatos cujos dados correspondem a toda ou a parte de uma string de pesquisa, usando as seguintes técnicas:

Correspondência com nomes de contatos
Faça uma correspondência da string de pesquisa com todos ou com parte dos dados dos nomes dos contatos para recuperar uma lista de contatos. Como o Provedor de contatos permite várias instâncias do mesmo nome, essa técnica pode retornar uma lista de correspondências.
Correspondência com um tipo específico de dados, por exemplo, um número de telefone
Recupere uma lista de contatos pela correspondência da string de pesquisa a um tipo específico de dados detalhados, por exemplo, um endereço de e-mail. Por exemplo, essa técnica permite listar todos os contatos cujo endereço de e-mail corresponda à string de pesquisa.
Correspondência com qualquer tipo de dados
Recupere uma lista de contatos pela correspondência da string de pesquisa a qualquer tipo de dados detalhados, incluindo nome, número de telefone, endereço, endereço de e-mail e assim por diante. Por exemplo, essa técnica permite aceitar qualquer tipo de dados para uma string de pesquisa e listar os contatos com dados que correspondem à string.

Observação: todos os exemplos desta lição usam um CursorLoader para recuperar dados do Provedor de contatos. Um CursorLoader executa a respectiva consulta em uma linha de execução separada da linha de execução da IU. Isso garante que a consulta não atrase o tempo de resposta da IU nem cause uma experiência ruim para o usuário. Para saber mais, consulte a lição do treinamento do Android Carregar dados em segundo plano.

Solicitar permissão para ler o provedor

Para fazer qualquer tipo de pesquisa do Provedor de contatos, seu app precisa ter a permissão READ_CONTACTS. Para solicitá-la, adicione o elemento <uses-permission> ao seu arquivo de manifesto como um elemento filho de <manifest>:

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

Fazer a correspondência de um contato por nome e listar os resultados

Essa técnica tenta associar uma string de pesquisa ao nome de um contato ou contatos na tabela ContactsContract.Contacts do Provedor de contatos. Geralmente, é recomendável exibir os resultados em uma ListView para permitir que o usuário escolha entre os contatos associados.

Definir ListView e layouts de item

Para exibir os resultados da pesquisa em uma ListView, você precisa de um arquivo de layout principal que defina toda a IU, incluindo a ListView, e um arquivo de layout de item que defina uma linha da ListView. Por exemplo, você pode criar o arquivo de layout principal res/layout/contacts_list_view.xml com o seguinte XML:

    <?xml version="1.0" encoding="utf-8"?>
    <ListView xmlns:android="http://schemas.android.com/apk/res/android"
              android:id="@android:id/list"
              android:layout_width="match_parent"
              android:layout_height="match_parent"/>
    

Esse XML usa o android:id/list do widget ListView do Android.

Defina o arquivo de layout do item contacts_list_item.xml com o seguinte XML:

    <?xml version="1.0" encoding="utf-8"?>
    <TextView xmlns:android="http://schemas.android.com/apk/res/android"
              android:id="@android:id/text1"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:clickable="true"/>
    

Esse XML usa o android:text1 do widget TextView do Android.

Observação: esta lição não descreve a IU para receber uma string de pesquisa do usuário, porque é recomendável receber a string indiretamente. Por exemplo, você pode dar ao usuário a opção de procurar contatos cujo nome corresponda a uma string em uma mensagem de texto recebida.

Os dois arquivos de layout que você escreveu definem uma interface de usuário que mostra uma ListView. A próxima etapa é escrever o código que usa essa IU para exibir uma lista de contatos.

Definir um fragmento que exiba a lista de contatos

Para exibir a lista de contatos, comece definindo um Fragment carregado por uma Activity. O uso de um Fragment é uma técnica mais flexível, porque você pode usar um Fragment para exibir a lista e um segundo Fragment para exibir os detalhes de um contato que o usuário escolhe na lista. Usando essa abordagem, você pode combinar uma das técnicas apresentadas nesta lição com uma da lição Recuperar detalhes de um contato.

Para aprender a usar um ou mais objetos Fragment de uma Activity, leia a aula de treinamento Criar uma IU dinâmica com fragmentos.

Para ajudar você a escrever consultas no Provedor de contatos, o framework do Android fornece uma classe de contratos chamada ContactsContract, que define constantes e métodos úteis para acessar o provedor. Quando você usa essa classe, não precisa definir suas constantes para URIs de conteúdo, nomes de tabelas ou colunas. Para usar essa classe, inclua a seguinte instrução:

Kotlin

    import android.provider.ContactsContract
    

Java

    import android.provider.ContactsContract;
    

Como o código usa um CursorLoader para recuperar dados do provedor, você precisa especificar que ele implementa a interface do carregador LoaderManager.LoaderCallbacks. Além disso, para ajudar a detectar qual contato o usuário seleciona na lista de resultados da pesquisa, implemente a interface do adaptador AdapterView.OnItemClickListener. Exemplo:

Kotlin

    ...
    import android.support.v4.app.Fragment
    import android.support.v4.app.LoaderManager
    import android.widget.AdapterView
    ...
    class ContactsFragment :
            Fragment(),
            LoaderManager.LoaderCallbacks<Cursor>,
            AdapterView.OnItemClickListener {
    

Java

    ...
    import android.support.v4.app.Fragment;
    import android.support.v4.app.LoaderManager.LoaderCallbacks;
    import android.widget.AdapterView;
    ...
    public class ContactsFragment extends Fragment implements
            LoaderManager.LoaderCallbacks<Cursor>,
            AdapterView.OnItemClickListener {
    

Definir variáveis globais

Defina variáveis globais usadas em outras partes do código:

Kotlin

    ...
    /*
     * Defines an array that contains column names to move from
     * the Cursor to the ListView.
     */
    @SuppressLint("InlinedApi")
    private val FROM_COLUMNS: Array<String> = arrayOf(
            if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)) {
                ContactsContract.Contacts.DISPLAY_NAME_PRIMARY
            } else {
                ContactsContract.Contacts.DISPLAY_NAME
            }
    )
    /*
     * Defines an array that contains resource ids for the layout views
     * that get the Cursor column contents. The id is pre-defined in
     * the Android framework, so it is prefaced with "android.R.id"
     */
    private val TO_IDS: IntArray = intArrayOf(android.R.id.text1)
    ...
    class ContactsFragment :
            Fragment(),
            LoaderManager.LoaderCallbacks<Cursor>,
            AdapterView.OnItemClickListener {
        ...
        // Define global mutable variables
        // Define a ListView object
        lateinit var contactsList: ListView
        // Define variables for the contact the user selects
        // The contact's _ID value
        var contactId: Long = 0
        // The contact's LOOKUP_KEY
        var contactKey: String? = null
        // A content URI for the selected contact
        var contactUri: Uri? = null
        // An adapter that binds the result Cursor to the ListView
        private val cursorAdapter: SimpleCursorAdapter? = null

    

Java

        ...
        /*
         * Defines an array that contains column names to move from
         * the Cursor to the ListView.
         */
        @SuppressLint("InlinedApi")
        private final static String[] FROM_COLUMNS = {
                Build.VERSION.SDK_INT
                        >= Build.VERSION_CODES.HONEYCOMB ?
                        ContactsContract.Contacts.DISPLAY_NAME_PRIMARY :
                        ContactsContract.Contacts.DISPLAY_NAME
        };
        /*
         * Defines an array that contains resource ids for the layout views
         * that get the Cursor column contents. The id is pre-defined in
         * the Android framework, so it is prefaced with "android.R.id"
         */
        private final static int[] TO_IDS = {
               android.R.id.text1
        };
        // Define global mutable variables
        // Define a ListView object
        ListView contactsList;
        // Define variables for the contact the user selects
        // The contact's _ID value
        long contactId;
        // The contact's LOOKUP_KEY
        String contactKey;
        // A content URI for the selected contact
        Uri contactUri;
        // An adapter that binds the result Cursor to the ListView
        private SimpleCursorAdapter cursorAdapter;
        ...
    

Observação: como o Contacts.DISPLAY_NAME_PRIMARY requer o Android 3.0 (API de nível 11) ou versões mais recentes, a definição da minSdkVersion do seu app como 10 ou um nível anterior gera um aviso do Android Lint no Studio. Para desativar esse aviso, adicione a anotação @SuppressLint("InlinedApi") antes da definição de FROM_COLUMNS.

Inicializar o fragmento

Inicialize o Fragment: Adicione o construtor público vazio exigido pelo sistema Android e infle a IU do objeto Fragment no método de callback onCreateView(). Exemplo:

Kotlin

        // A UI Fragment must inflate its View
        override fun onCreateView(
                inflater: LayoutInflater,
                container: ViewGroup?,
                savedInstanceState: Bundle?
        ): View? {
            // Inflate the fragment layout
            return inflater.inflate(R.layout.contact_list_fragment, container, false)
        }
    

Java

        // Empty public constructor, required by the system
        public ContactsFragment() {}

        // A UI Fragment must inflate its View
        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                Bundle savedInstanceState) {
            // Inflate the fragment layout
            return inflater.inflate(R.layout.contact_list_fragment,
                container, false);
        }
    

Configurar o CursorAdapter para a ListView

Configure o SimpleCursorAdapter que vincula os resultados da pesquisa ao ListView. Para ver o objeto de ListView que exibe os contatos, é necessário chamar Activity.findViewById() usando a atividade pai do Fragment. Use o Context da atividade pai quando você chamar setAdapter(). Exemplo:

Kotlin

        override fun onActivityCreated(savedInstanceState: Bundle?) {
            super.onActivityCreated(savedInstanceState)
            ...
            // Gets the ListView from the View list of the parent activity
            activity?.also {
                contactsList = it.findViewById<ListView>(R.id.contact_list_view)
                // Gets a CursorAdapter
                cursorAdapter = SimpleCursorAdapter(
                        it,
                        R.layout.contact_list_item,
                        null,
                        FROM_COLUMNS, TO_IDS,
                        0
                )
                // Sets the adapter for the ListView
                contactsList.adapter = cursorAdapter
            }
        }
    

Java

        public void onActivityCreated(Bundle savedInstanceState) {
            super.onActivityCreated(savedInstanceState);
            ...
            // Gets the ListView from the View list of the parent activity
            contactsList =
                (ListView) getActivity().findViewById(R.layout.contact_list_view);
            // Gets a CursorAdapter
            cursorAdapter = new SimpleCursorAdapter(
                    getActivity(),
                    R.layout.contact_list_item,
                    null,
                    FROM_COLUMNS, TO_IDS,
                    0);
            // Sets the adapter for the ListView
            contactsList.setAdapter(cursorAdapter);
        }
    

Definir o listener do contato selecionado

Quando você exibe os resultados de uma pesquisa, geralmente quer que o usuário selecione um único contato para processamento adicional. Por exemplo, quando o usuário clica em um contato, você pode exibir o endereço do contato em um mapa. Para fornecer esse recurso, você primeiro define o Fragment atual como o listener de clique especificando que a classe deve implementar AdapterView.OnItemClickListener, como mostrado na seção Definir um fragmento que exibe a lista de contatos.

Para continuar configurando o listener, vincule-o à ListView chamando o método setOnItemClickListener() em onActivityCreated(). Exemplo:

Kotlin

        fun onActivityCreated(savedInstanceState:Bundle) {
            ...
            // Set the item click listener to be the current fragment.
            contactsList.onItemClickListener = this
            ...
        }
    

Java

        public void onActivityCreated(Bundle savedInstanceState) {
            ...
            // Set the item click listener to be the current fragment.
            contactsList.setOnItemClickListener(this);
            ...
        }
    

Como você especificou que o Fragment atual é o OnItemClickListener para a ListView, agora é preciso implementar o método obrigatório onItemClick() que processa o evento de clique. Isso é descrito em uma seção a seguir.

Definir uma projeção

Defina uma constante que contenha as colunas que você quer que sejam retornadas da sua consulta. Cada item na ListView mostra o nome de exibição do contato, que contém a forma principal do nome dele. No Android 3.0 (API de nível 11) e versões mais recentes, o nome dessa coluna é Contacts.DISPLAY_NAME_PRIMARY. Nas versões anteriores, o nome é Contacts.DISPLAY_NAME.

A coluna Contacts._ID é usada pelo processo de vinculação SimpleCursorAdapter. Contacts._ID e LOOKUP_KEY são usados juntos para construir um URI de conteúdo para o contato que o usuário seleciona.

Kotlin

    ...
    @SuppressLint("InlinedApi")
    private val PROJECTION: Array<out String> = arrayOf(
            ContactsContract.Contacts._ID,
            ContactsContract.Contacts.LOOKUP_KEY,
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
                ContactsContract.Contacts.DISPLAY_NAME_PRIMARY
            else
                ContactsContract.Contacts.DISPLAY_NAME
    )
    

Java

    ...
    @SuppressLint("InlinedApi")
    private static final String[] PROJECTION =
            {
                Contacts._ID,
                Contacts.LOOKUP_KEY,
                Build.VERSION.SDK_INT
                        >= Build.VERSION_CODES.HONEYCOMB ?
                        ContactsContract.Contacts.DISPLAY_NAME_PRIMARY :
                        ContactsContract.Contacts.DISPLAY_NAME

            };
    

Definir constantes para os índices da coluna do Cursor

Para ver os dados de uma coluna individual em um Cursor, você precisa do índice da coluna dentro do Cursor. Você pode definir constantes para os índices das colunas do Cursor, porque os índices são iguais à ordem dos nomes das colunas na sua projeção. Exemplo:

Kotlin

    // The column index for the _ID column
    private const val CONTACT_ID_INDEX: Int = 0
    // The column index for the CONTACT_KEY column
    private const val CONTACT_KEY_INDEX: Int = 1
    

Java

    // The column index for the _ID column
    private static final int CONTACT_ID_INDEX = 0;
    // The column index for the CONTACT_KEY column
    private static final int CONTACT_KEY_INDEX = 1;
    

Especificar os critérios de seleção

Para especificar os dados desejados, crie uma combinação de expressões e variáveis de texto que digam ao provedor as colunas de dados a serem pesquisadas e os valores a serem encontrados.

Para a expressão de texto, defina uma constante que liste as colunas de pesquisa. Embora essa expressão possa conter valores também, a prática preferível é representar os valores com um marcador "?". Durante a recuperação, o marcador é substituído pelos valores de uma matriz. O uso de "?" como marcador garante que a especificação de pesquisa seja gerada por vinculação e não por compilação SQL. Essa prática elimina a possibilidade de injeção maliciosa de SQL. Exemplo:

Kotlin

    // Defines the text expression
    @SuppressLint("InlinedApi")
    private val SELECTION: String =
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
                "${ContactsContract.Contacts.DISPLAY_NAME_PRIMARY} LIKE ?"
            else
                "${ContactsContract.Contacts.DISPLAY_NAME} LIKE ?"
    ...
        // Defines a variable for the search string
        private val searchString: String = ...
        // Defines the array to hold values that replace the ?
        private val selectionArgs = arrayOf<String>(searchString)
    

Java

        // Defines the text expression
        @SuppressLint("InlinedApi")
        private static final String SELECTION =
                Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB ?
                Contacts.DISPLAY_NAME_PRIMARY + " LIKE ?" :
                Contacts.DISPLAY_NAME + " LIKE ?";
        // Defines a variable for the search string
        private String searchString;
        // Defines the array to hold values that replace the ?
        private String[] selectionArgs = { searchString };
    

Definir o método onItemClick()

Na seção anterior, você definiu o listener de clique do item para a ListView. Agora, implemente a ação para o listener, definindo o método AdapterView.OnItemClickListener.onItemClick().

Kotlin

        override fun onItemClick(parent: AdapterView<*>, view: View?, position: Int, id: Long) {
            // Get the Cursor
            val cursor: Cursor? = (parent.adapter as? CursorAdapter)?.cursor?.apply {
                // Move to the selected contact
                moveToPosition(position)
                // Get the _ID value
                contactId = getLong(CONTACT_ID_INDEX)
                // Get the selected LOOKUP KEY
                contactKey = getString(CONTACT_KEY_INDEX)
                // Create the contact's content Uri
                contactUri = ContactsContract.Contacts.getLookupUri(contactId, mContactKey)
                /*
                 * You can use contactUri as the content URI for retrieving
                 * the details for a contact.
                 */
            }
        }
    

Java

        @Override
        public void onItemClick(
            AdapterView<?> parent, View item, int position, long rowID) {
            // Get the Cursor
            Cursor cursor = parent.getAdapter().getCursor();
            // Move to the selected contact
            cursor.moveToPosition(position);
            // Get the _ID value
            contactId = cursor.getLong(CONTACT_ID_INDEX);
            // Get the selected LOOKUP KEY
            contactKey = cursor.getString(CONTACT_KEY_INDEX);
            // Create the contact's content Uri
            contactUri = Contacts.getLookupUri(contactId, mContactKey);
            /*
             * You can use contactUri as the content URI for retrieving
             * the details for a contact.
             */
        }
    

Inicializar o carregador

Como você está usando um CursorLoader para recuperar dados, é necessário inicializar a linha de execução em segundo plano e outras variáveis que controlam a recuperação assíncrona. Execute a inicialização em onCreate(), como mostrado no exemplo a seguir:

Kotlin

    class ContactsFragment :
            Fragment(),
            LoaderManager.LoaderCallbacks<Cursor> {
        ...
        override fun onCreate(savedInstanceState: Bundle?) {
            // Always call the super method first
            super.onCreate(savedInstanceState)
            ...
            // Initializes the loader
            loaderManager.initLoader(0, null, this)
    

Java

    public class ContactsFragment extends Fragment implements
            LoaderManager.LoaderCallbacks<Cursor> {
        ...
        // Called just before the Fragment displays its UI
        @Override
        public void onCreate(Bundle savedInstanceState) {
            // Always call the super method first
            super.onCreate(savedInstanceState);
            ...
            // Initializes the loader
            getLoaderManager().initLoader(0, null, this);
    

Implementar onCreateLoader()

Implemente o método onCreateLoader(), chamado pelo framework do carregador imediatamente depois que você chama initLoader().

Em onCreateLoader(), configure o padrão de string de pesquisa. Para transformar uma string em um padrão, insira caracteres "%" (porcentagem), para representar uma sequência de zero ou mais caracteres, ou caracteres "_" (sublinhado), para representar um único caractere, ou ambos. Por exemplo, o padrão "%Jefferson%" corresponderia a "Thomas Jefferson" e "Jefferson Davis".

Retorne um novo CursorLoader do método. Para o URI de conteúdo, use Contacts.CONTENT_URI. Esse URI refere-se à tabela inteira, conforme mostrado no exemplo a seguir:

Kotlin

        ...
        override fun onCreateLoader(loaderId: Int, args: Bundle?): Loader<Cursor> {
            /*
             * Makes search string into pattern and
             * stores it in the selection array
             */
            selectionArgs[0] = "%$mSearchString%"
            // Starts the query
            return activity?.let {
                return CursorLoader(
                        it,
                        ContactsContract.Contacts.CONTENT_URI,
                        PROJECTION,
                        SELECTION,
                        selectionArgs,
                        null
                )
            } ?: throw IllegalStateException()
        }
    

Java

        ...
        @Override
        public Loader<Cursor> onCreateLoader(int loaderId, Bundle args) {
            /*
             * Makes search string into pattern and
             * stores it in the selection array
             */
            selectionArgs[0] = "%" + searchString + "%";
            // Starts the query
            return new CursorLoader(
                    getActivity(),
                    ContactsContract.Contacts.CONTENT_URI,
                    PROJECTION,
                    SELECTION,
                    selectionArgs,
                    null
            );
        }
    

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. Nesse método, coloque o resultado Cursor no SimpleCursorAdapter. Isso atualiza automaticamente a ListView com os resultados da pesquisa:

Kotlin

        override fun onLoadFinished(loader: Loader<Cursor>, cursor: Cursor) {
            // Put the result Cursor in the adapter for the ListView
            cursorAdapter?.swapCursor(cursor)
        }
    

Java

        @Override
        public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
            // Put the result Cursor in the adapter for the ListView
            cursorAdapter.swapCursor(cursor);
        }
    

O método onLoaderReset() é invocado quando o framework do carregador detecta que o resultado Cursor contém dados desatualizados. Exclua a referência SimpleCursorAdapter ao Cursor já existente. Caso contrário, o framework do carregador não reciclará o Cursor, o que causará vazamento de memória. Exemplo:

Kotlin

        override fun onLoaderReset(loader: Loader<Cursor>) {
            // Delete the reference to the existing Cursor
            cursorAdapter?.swapCursor(null)
        }
    

Java

        @Override
        public void onLoaderReset(Loader<Cursor> loader) {
            // Delete the reference to the existing Cursor
            cursorAdapter.swapCursor(null);

        }
    

Agora você tem as principais partes de um app que faz a correspondência de uma string de pesquisa a nomes de contatos e retorna o resultado em uma ListView. O usuário pode clicar no nome de um contato para selecioná-lo. Isso aciona um listener, em que você pode trabalhar mais com os dados do contato. Por exemplo, você pode recuperar os detalhes do contato. Para saber como fazer isso, continue na próxima lição, Recuperar detalhes de um contato.

Para saber mais sobre interfaces de usuário de pesquisa, leia o guia da API Criar uma interface de pesquisa.

As seções restantes desta lição demonstram outras maneiras de encontrar contatos no Provedor de contatos.

Fazer a correspondência de um contato por um tipo específico de dados

Essa técnica permite especificar o tipo de dados que você quer na correspondência. A recuperação por nome é um exemplo específico desse tipo de consulta, mas você também pode fazer isso para qualquer um dos tipos de dados detalhados associados a um contato. Por exemplo, você pode recuperar contatos que tenham um CEP específico. Nesse caso, a string de pesquisa precisa corresponder aos dados armazenados em uma linha de CEP.

Para implementar esse tipo de recuperação, primeiro implemente o seguinte código, conforme listado nas seções anteriores:

  • Solicitar permissão para ler o provedor
  • Definir ListView e layouts de item
  • Definir um fragmento que exiba a lista de contatos
  • Definir variáveis globais
  • Inicializar o fragmento
  • Configurar o CursorAdapter para a ListView
  • Definir o listener do contato selecionado
  • Definir constantes para os índices da coluna do Cursor.

    Embora você esteja recuperando dados de uma tabela diferente, a ordem das colunas na projeção é a mesma; portanto, você pode usar os mesmos índices para o Cursor.

  • Definir o método onItemClick()
  • Inicializar o carregador
  • Implementar onLoadFinished() e onLoaderReset()

As etapas a seguir mostram o código adicional necessário para corresponder uma string de pesquisa a um tipo específico de dados detalhados e exibir os resultados.

Escolher o tipo de dados e a tabela

Para procurar um tipo específico de dados detalhados, é necessário conhecer o valor do tipo MIME personalizado para o tipo de dados. Cada tipo de dados possui um valor exclusivo de tipo MIME definido por uma constante CONTENT_ITEM_TYPE na subclasse de ContactsContract.CommonDataKinds associada ao tipo de dados. As subclasses têm nomes que indicam o tipo de dados. Por exemplo, a subclasse de dados de e-mail é ContactsContract.CommonDataKinds.Email, e o Tipo MIME personalizado para dados de e-mail é definido pela constante Email.CONTENT_ITEM_TYPE.

Use a tabela ContactsContract.Data para a pesquisa. Todas as constantes necessárias para sua projeção, cláusula de seleção e ordem de classificação são definidas nessa tabela ou herdadas por ela.

Definir uma projeção

Para definir uma projeção, escolha uma ou mais das colunas definidas em ContactsContract.Data ou as classes das quais ela é herdada. O Provedor de contatos faz uma mesclagem implícita entre ContactsContract.Data e outras tabelas antes de retornar linhas. Exemplo:

Kotlin

    @SuppressLint("InlinedApi")
    private val PROJECTION: Array<out String> = arrayOf(
            /*
             * The detail data row ID. To make a ListView work,
             * this column is required.
             */
            ContactsContract.Data._ID,
            // The primary display name
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB)
                ContactsContract.Data.DISPLAY_NAME_PRIMARY
            else
                ContactsContract.Data.DISPLAY_NAME,
            // The contact's _ID, to construct a content URI
            ContactsContract.Data.CONTACT_ID,
            // The contact's LOOKUP_KEY, to construct a content URI
            ContactsContract.Data.LOOKUP_KEY
    )
    

Java

        @SuppressLint("InlinedApi")
        private static final String[] PROJECTION =
            {
                /*
                 * The detail data row ID. To make a ListView work,
                 * this column is required.
                 */
                ContactsContract.Data._ID,
                // The primary display name
                Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB ?
                        ContactsContract.Data.DISPLAY_NAME_PRIMARY :
                        ContactsContract.Data.DISPLAY_NAME,
                // The contact's _ID, to construct a content URI
                ContactsContract.Data.CONTACT_ID,
                // The contact's LOOKUP_KEY, to construct a content URI
                ContactsContract.Data.LOOKUP_KEY // A permanent link to the contact
            };
    

Definir critérios de pesquisa

Para procurar uma string dentro de um tipo específico de dados, construa uma cláusula de seleção a partir do seguinte:

  • O nome da coluna que contém sua string de pesquisa. Esse nome varia de acordo com o tipo de dados. Portanto, você precisa encontrar a subclasse de ContactsContract.CommonDataKinds que corresponde ao tipo de dados e escolher o nome da coluna dessa subclasse. Por exemplo, para procurar endereços de e-mail, use a coluna Email.ADDRESS.
  • A própria string de pesquisa, representada como o caractere "?" na cláusula de seleção.
  • O nome da coluna que contém o valor do tipo MIME personalizado. Esse nome é sempre Data.MIMETYPE.
  • O valor do tipo MIME personalizado para o tipo de dados. Conforme descrito anteriormente, essa é a constante CONTENT_ITEM_TYPE na subclasse ContactsContract.CommonDataKinds. Por exemplo, o valor do tipo MIME para dados de e-mail é Email.CONTENT_ITEM_TYPE. Coloque o valor entre aspas simples concatenando um caractere "''" (aspas simples) no início e no final da constante; caso contrário, o provedor interpreta o valor 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 está usando um valor constante e não um valor fornecido pelo usuário.

Exemplo:

Kotlin

    /*
     * Constructs search criteria from the search string
     * and email MIME type
     */
    private val SELECTION: String =
            /*
             * Searches for an email address
             * that matches the search string
             */
            "${Email.ADDRESS} LIKE ? AND " +
            /*
             * Searches for a MIME type that matches
             * the value of the constant
             * Email.CONTENT_ITEM_TYPE. Note the
             * single quotes surrounding Email.CONTENT_ITEM_TYPE.
             */
            "${ContactsContract.Data.MIMETYPE } = '${Email.CONTENT_ITEM_TYPE}'"
    

Java

        /*
         * Constructs search criteria from the search string
         * and email MIME type
         */
        private static final String SELECTION =
                /*
                 * Searches for an email address
                 * that matches the search string
                 */
                Email.ADDRESS + " LIKE ? " + "AND " +
                /*
                 * Searches for a MIME type that matches
                 * the value of the constant
                 * Email.CONTENT_ITEM_TYPE. Note the
                 * single quotes surrounding Email.CONTENT_ITEM_TYPE.
                 */
                ContactsContract.Data.MIMETYPE + " = '" + Email.CONTENT_ITEM_TYPE + "'";
    

Em seguida, defina variáveis para conter o argumento de seleção:

Kotlin

        private var searchString: String? = null
        private val selectionArgs: Array<String> = arrayOf("")
    

Java

        String searchString;
        String[] selectionArgs = { "" };
    

Implementar onCreateLoader()

Agora que você especificou os dados desejados e como encontrá-los, defina uma consulta na sua implementação de onCreateLoader(). Retorne um novo CursorLoader desse método, usando sua projeção, expressão de texto de seleção e matriz de seleção como argumentos. Para um URI de conteúdo, use Data.CONTENT_URI. Exemplo:

Kotlin

        override fun onCreateLoader(id: Int, args: Bundle?): Loader<Cursor> {
            // OPTIONAL: Makes search string into pattern
            searchString = "%$mSearchString%"

            searchString?.also {
                // Puts the search string into the selection criteria
                selectionArgs[0] = it
            }
            // Starts the query
            return activity?.let {
                CursorLoader(
                        it,
                        ContactsContract.Data.CONTENT_URI,
                        PROJECTION,
                        SELECTION,
                        selectionArgs,
                        null
                )
            } ?: throw IllegalStateException()
        }
    

Java

    @Override
        public Loader<Cursor> onCreateLoader(int loaderId, Bundle args) {
            // OPTIONAL: Makes search string into pattern
            searchString = "%" + searchString + "%";
            // Puts the search string into the selection criteria
            selectionArgs[0] = searchString;
            // Starts the query
            return new CursorLoader(
                    getActivity(),
                    Data.CONTENT_URI,
                    PROJECTION,
                    SELECTION,
                    selectionArgs,
                    null
            );
        }
    

Esses snippets de código são a base de uma pesquisa inversa simples com base em um tipo específico de dados detalhados. Essa é a melhor técnica a ser usada se seu app se concentra em um tipo específico de dados, por exemplo, e-mails, e você quer que os usuários vejam os nomes associados a um dado.

Fazer a correspondência de um contato por qualquer tipo de dados

A recuperação de um contato com base em qualquer tipo de dados retornará contatos se um deles corresponder à string de caracteres de pesquisa, incluindo nome, endereço de e-mail, endereço postal, número de telefone e assim por diante. Isso resulta em um conjunto grande de resultados de pesquisa. Por exemplo, se a string de pesquisa for "Doe", a pesquisa de qualquer tipo de dados retornará o contato "John Doe"; também retornará contatos que moram na "Doe Street".

Para implementar esse tipo de recuperação, primeiro implemente o seguinte código, conforme listado nas seções anteriores:

  • Solicitar permissão para ler o provedor
  • Definir ListView e layouts de item
  • Definir um fragmento que exiba a lista de contatos
  • Definir variáveis globais
  • Inicializar o fragmento
  • Configurar o CursorAdapter para a ListView
  • Definir o listener do contato selecionado
  • Definir uma projeção
  • Definir constantes para os índices da coluna do Cursor.

    Para esse tipo de recuperação, você está usando a mesma tabela usada na seção Fazer a correspondência de um contato por nome e listar os resultados. Use os mesmos índices de coluna também.

  • Definir o método onItemClick()
  • Inicializar o carregador
  • Implementar onLoadFinished() e onLoaderReset()

As etapas a seguir mostram o código adicional necessário para corresponder uma string de pesquisa a qualquer tipo de dados e exibir os resultados.

Remover critérios de seleção

Não defina as constantes SELECTION ou a variável mSelectionArgs. Elas não são usadas nesse tipo de recuperação.

Implementar onCreateLoader()

Implemente o método onCreateLoader(), retornando um novo CursorLoader. Você não precisa converter a string de pesquisa em um padrão, porque o Provedor de contatos faz isso automaticamente. Use Contacts.CONTENT_FILTER_URI como o URI base e anexe sua string de pesquisa a ele chamando Uri.withAppendedPath(). O uso desse URI aciona automaticamente a pesquisa de qualquer tipo de dados, conforme mostrado no seguinte exemplo:

Kotlin

        override fun onCreateLoader(loaderId: Int, args: Bundle?): Loader<Cursor> {
            /*
             * Appends the search string to the base URI. Always
             * encode search strings to ensure they're in proper
             * format.
             */
            val contentUri: Uri = Uri.withAppendedPath(
                    ContactsContract.Contacts.CONTENT_FILTER_URI,
                    Uri.encode(searchString)
            )
            // Starts the query
            return activity?.let {
                CursorLoader(
                        it,
                        contentUri,
                        PROJECTION2,
                        null,
                        null,
                        null
                )
            } ?: throw IllegalStateException()
        }
    

Java

        @Override
        public Loader<Cursor> onCreateLoader(int loaderId, Bundle args) {
            /*
             * Appends the search string to the base URI. Always
             * encode search strings to ensure they're in proper
             * format.
             */
            Uri contentUri = Uri.withAppendedPath(
                    Contacts.CONTENT_FILTER_URI,
                    Uri.encode(searchString));
            // Starts the query
            return new CursorLoader(
                    getActivity(),
                    contentUri,
                    PROJECTION,
                    null,
                    null,
                    null
            );
        }
    

Esses snippets de código são a base de um app que faz uma pesquisa ampla no Provedor de contatos. A técnica é útil para apps que implementam funcionalidades semelhantes à tela da lista de contatos do app Pessoas.