連絡先の詳細情報を取得する

このレッスンでは、連絡先の詳細データ(メールアドレス、電話番号など)を取得する方法について説明します。ユーザーが連絡先を取得しようとするのは、詳細情報が必要なためです。このようなユーザーに対して、連絡先のすべての詳細情報を提供することも、メールアドレスなど、特定のタイプの詳細情報だけを表示することもできます。

このレッスンの手順では、ユーザーが選択した連絡先の ContactsContract.Contacts 行をすでに入手していることを前提としています。連絡先のリストを取得する方法については、連絡先名を取得するをご覧ください。

連絡先のすべての詳細情報を取得する

連絡先のすべての詳細情報を取得するには、ContactsContract.Data テーブルを対象に、連絡先の LOOKUP_KEY を含むすべての行を検索します。この列は、ContactsContract.Data テーブルで利用可能です。連絡先プロバイダが ContactsContract.Contacts テーブルと ContactsContract.Data テーブルの間で暗黙的に結合を行います。LOOKUP_KEY 列の詳細については、連絡先名を取得するをご覧ください。

注: 連絡先のすべての詳細情報を取得する場合、ContactsContract.Data テーブル内のすべての列を取得する必要があるため、デバイスのパフォーマンスが低下します。この方法を試す前に、パフォーマンスへの影響を考慮してください。

パーミッションをリクエストする

連絡先プロバイダから詳細情報を読み取るには、アプリに READ_CONTACTS パーミッションが必要です。このパーミッションをリクエストするには、マニフェスト ファイルの <manifest> 要素に次の子要素を追加します。

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

射影をセットアップする

行に含まれるデータ型に応じて、使用する列の数は変わります。また、データ型に応じて、データは異なる列に格納されます。可能な限りすべてのデータ型を対象に、可能な限りすべての列を取得するには、すべての列名を射影に追加する必要があります。結果 CursorListView にバインドする場合は、必ず Data._ID を取得するようにします。そうでないと、バインドは機能しません。また、Data.MIMETYPE を取得することにより、取得する各行のデータ型を識別できるようになります。たとえば、次のようになります。

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

この射影は、ContactsContract.Data テーブル内の 1 つの行を対象に、すべての列を取得します。その際、ContactsContract.Data クラスに定義されている列名を使用します。

必要に応じて、ContactsContract.Data クラス内で定義や継承が指定されている他の列定数を使用することもできます。ただし、SYNC1 列~ SYNC4 列は、同期アダプターが使用するためのものであり、そのデータは使用できません。

選択基準を定義する

選択句の定数や、選択引数を保持する配列、選択値を保持する変数を定義します。Contacts.LOOKUP_KEY 列を使用して、連絡先を見つけます。たとえば、次のようになります。

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
    

選択テキスト表現内のプレースホルダとして「?」を使用することにより、SQL コンパイルではなくバインドによって検索が生成されるようになります。このアプローチにより、悪意のある SQL インジェクションの可能性を排除できます。

ソート順を定義する

結果 Cursor 内のソート順を定義します。特定のデータ型のすべての行をまとめて保持するには、Data.MIMETYPE を基準にしてソートします。このクエリ引数は、すべてのメールアドレスの行、すべての電話番号の行などをグループ化します。たとえば、次のようになります。

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;
    

注: 一部のデータ型はサブタイプを使用しないため、サブタイプに基づいてソートすることはできません。代わりに、返された Cursor を反復処理して、現在の行のデータ型を判断し、サブタイプを使用する行のデータを格納する必要があります。カーソルの読み取りが終了したら、サブタイプを基準にして各データ型をソートし、結果を表示できます。

ローダーを初期化する

連絡先プロバイダ(および他のすべてのコンテンツ プロバイダ)から取得を行う際は、常にバックグラウンド スレッドで実行します。LoaderManager クラスと LoaderManager.LoaderCallbacks インターフェースによって定義されるローダー フレームワークを使用して、バックグラウンド取得を実行します。

行を取得する準備が整ったら、initLoader() を呼び出して、ローダー フレームワークを初期化します。メソッドに整数 ID を渡します。この ID は LoaderManager.LoaderCallbacks メソッドに渡されます。ID を使用することで、アプリ内で複数のローダーを区別して使用できるようになります。

ローダー フレームワークを初期化する方法を次のスニペットに示します。

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

onCreateLoader() を実装する

onCreateLoader() メソッドを実装します。このメソッドは、initLoader() を呼び出した直後にローダー フレームワークによって呼び出されます。このメソッドから CursorLoader を返します。現在 ContactsContract.Data テーブルを検索しているため、Data.CONTENT_URI 定数をコンテンツ URI として使用します。たとえば、次のようになります。

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

onLoadFinished() と onLoaderReset() を実装する

onLoadFinished() メソッドを実装します。連絡先プロバイダがクエリの結果を返すと、ローダー フレームワークは onLoadFinished() を呼び出します。たとえば、次のようになります。

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

結果 Cursor を支えるデータが変更されたことをローダー フレームワークが検出すると、onLoaderReset() メソッドが呼び出されます。この時点で、Cursor に対する既存の参照を null に設定することで削除します。この指定を行わなかった場合、ローダー フレームワークが古い Cursor を破棄しなくなり、メモリリークが発生します。たとえば、次のようになります。

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

連絡先の特定の詳細情報を取得する

「すべてのメール」など、連絡先の特定のデータ型を取得する場合、すべての詳細情報を取得する場合と同様のパターンで行います。ただし、連絡先のすべての詳細情報を取得するで示したコードリストに対して、以下の変更を加える必要があります。

プロジェクション
射影を編集して、データ型に固有の列を取得するようにします。また、データ型に対応する ContactsContract.CommonDataKinds サブクラスで定義されている列名定数を使用するように、射影を編集します。
選択
選択テキストを編集して、データ型に固有の MIMETYPE 値を検索するようにします。
ソート順
選択している詳細情報のタイプは 1 つだけであるため、返された CursorData.MIMETYPE に基づいてグループ化する処理は行いません。

上記の編集内容の詳細について、以降のセクションで説明します。

射影を定義する

データ型の ContactsContract.CommonDataKinds サブクラスにある列名定数を使用して、取得する列を定義します。CursorListView にバインドする場合は、必ず _ID 列を取得します。たとえば、メールデータを取得する場合、次のように射影を定義します。

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

なお、この射影は、ContactsContract.Data クラスで定義されている列名ではなく、ContactsContract.CommonDataKinds.Email クラスで定義されている列名を使用しています。メール固有の列名を使用することで、コードが読みやすくなります。

また、射影では、ContactsContract.CommonDataKinds サブクラスで定義されている他の列を使用することもできます。

選択基準を定義する

特定の連絡先の LOOKUP_KEY 行と、必要な詳細情報の Data.MIMETYPE 行を取得する検索テキスト表現を定義します。MIMETYPE 値を一重引用符で囲みます(定数の最初と最後に「'」(一重引用符)を付けます)。これを行わなかった場合、プロバイダは、定数のことを文字列値ではなく変数名として解釈します。ユーザー指定値ではなく定数を使用しているため、この値にプレースホルダを使用する必要はありません。たとえば、次のようになります。

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

ソート順を定義する

返された Cursor のソート順を定義します。特定のデータ型を取得しているため、MIMETYPE を基準とするソートは省略します。その代わり、検索する詳細データの型にサブタイプが含まれている場合は、そのサブタイプを基準にしてソートします。たとえば、メールデータの場合は Email.TYPE を基準にしてソートできます。

Kotlin

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

Java

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