初心者のためのKotlinによるスマホ内の楽曲情報取得アプリ作成入門

# 何をするか
アンドロイドアプリ開発初心者（または入門者、入門したい人）に対してのKotlinを使ってスマホ内にある楽曲情報をコンテンツプロバイダを使って取得し、リスト表示するアプリを開発していきます。コンテンツプロバイダとはスマホ内にあるアプリを横断して利用可能な共有のリレーショナルデータベースのようなものです。
またこの記事では[Android用(ちょっと)本格的なミュージックプレーヤの開発 part1](http://blog.livedoor.jp/hiroumauma/archives/1624511.html)を参考にしています。
そして全文は[github](https://github.com/ogata-k/GetTrackExample)に挙げてあります。コードに関してはGetTrackExample/app/src/main/java/com/example/owner/gettrackexample/の中身を見てください。

# 環境
この記事では2018年5月初旬あたりの環境で考えています。
開発言語はKotlinで開発環境はAndroidStudio3.1.2で行っています。

# この記事の対象読者
AndroidStudioでKotlinプログラムとしてのHelloWorldアプリを作成できる程度を基準としています。要はKotlinようにプロジェクトを生成できる人を基準としています。わからない人は「[はじめてのAndroidプログラミング 第3版（金田浩明著）](https://www.amazon.co.jp/dp/B07CCKXZMG/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1)」を参考にてください。この書籍はサンプルアプリもバリエーション豊かに掲載されており、KotlinやAndroidStudio3.xにも対応しているおすすめの書籍です。

# プロジェクトの作成
さて最初にAndroidStudioを立ち上げプロジェクトを作成します。設定は次のようにします。

| 画面 | 設定項目 |
|:--------------------------:|:-----------------------------------------------------------|
|  Create Android Project | Application name: **GetTrackExample**<br>Include Kotlin support: チェックを入れる|
| Target Android Device | Phone and Tablet: チェックを入れる<br>Minimum SDK: API 17: Android4.2(Jelly Bean)|
| Add an Activity to Mobile| Empty Activity: チェックを入れる |
|  Configure Activity | Activity Name: MainActivity<br>Layout Name: activity_main |

さて無事作成出来たら次に進みましょう。

# activity_main.xmlの編集
さて次はレイアウトを編集していきましょう。今回は簡単に作っていきます。
まず最初に入っているTextViewを削除しておきましょう。
次にConstraintLayoutにTextViewをBottom以外接続してLayoutMarginを8としておきましょう。そしてidはtrack_list_titleとしておき、text属性は右側にある…を選択してAdd new resourceからtrack_list_title_textという名前で「トラック一覧」としておきましょう。さらにtextColorにて右側にある…を選択し、Colorからandroidを選択後blackを選択しOKをクリックします。加えてtextStyleでBoldを選び太字にしておきます。
次にWidgetsからHorizontal Dividerを選択してtrack_list_titleのBottomに接続し、左右はConstraintLayoutに接続しておきます。ここでもLyoutMarginは8にしておきます。そしてlayout_widthが変わっていたらmatch_constraintに変えておきましょう。さらに右側にあるAttributesの両側矢印をクリックした後にbackgroundにて先ほどと同様にBlackを選択しておきます。するとdividerが見やすくなります。
そして最後にLegacyからListViewを選択し、先ほど設置したdividerの下に設置します。その時全方位に接続LayoutMarginを8で接続します。そしてここでも高さや幅が変わっていたらmatch_constraintに変えておきます。
ここまで行けば外観は次の画像のようになるはずです。
![activity_main_layut.jpg](https://qiita-image-store.s3.amazonaws.com/0/167385/a5e94160-695e-bc75-dcc3-07bd342dc046.jpeg)

# リストを表示できるようにしていく
## ListViewを扱うときの基礎知識
androidのListViewのような大量のデータを一覧形式で表示するViewは基本的にadapterという表示用のViewのようなものを保持するようになっています。
その為実装の流れとしては
1. ListViewを設置してidを付ける。ここではlistとつけておく。
2. adapter用のレイアウトを作成する。
3. adapterを使用するデータとともに初期化する。（空データで初期化後にデータを加えていく方法もあり）
4. listにadapterをセットする。
といった形になります。1は先ほど作成しました。
## 2 adapter用のレイアウトを作成する。
今回は楽曲情報（以下trackと呼ぶ）を取得して表示するということなので再生は考えないことにします。
最初に挙げた[参考サイト](http://blog.livedoor.jp/hiroumauma/archives/1624511.html)によると[こちら](https://developer.android.com/reference/android/provider/MediaStorアe.Audio.AudioColumns)で検索可能な検索先のデータの列名を知ることができます。そして今回はこの中から曲名（TITLE）、アルバム名（ALBUM）、アーティスト名（ARTIST）、再生時間（DURATION ミリ秒）を表示していきます。
そこでレイアウトをactivity_main.xmlというレイアウトファイルがあるLayoutフォルダ内にadapter_layout.xmlという名前でレイアウトファイルを作成します。例えば次の画像のように作成します。もし画像のように作りたいがdurationのTextViewがうまく近づかないならtitleに接続した後に右図の下の丸の中に数字が書かれたものをつかんでめいっぱい左に移動して配置を左寄りの配置にしてください。
![adapter_layout.jpg](https://qiita-image-store.s3.amazonaws.com/0/167385/755c0e59-6a9f-9f41-44b6-d844f8771bae.jpeg)

## 3 adapterを作成する
### モデルのためのクラスを作成する
まずモデルのためのクラスを作成します。MainActivityクラスと同じ階層にTrackクラスを作成します。このときコンテンツプロバイダから情報を取得するときにExternalStorageへの読み取りの許可が必要になるのでapplication部分に入る前に ```<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>``` を記述し使用する権限を宣言しておきましょう。ここで注意しないといけないのがアンドロイドのバージョンによって権限の取り扱い方が変わります。[こちら](https://qiita.com/imaizume/items/178cb540b697cd9419fd)を参考にしてください。そして許可を取る方法は[こちら](https://qiita.com/niusounds/items/f8852534bd3135d89dcc)を参考にしてください。今回はアプリで権限を確認せずに最初の実行後スマホの設定から直接権限を与える形で進んでいこうと思います。ですのでここは割愛します。

そしてTrackクラスを

```Track.kt
class Track(val title: String, val album: String, val artist: String, val duration: Long){
    //  Track検索時に取得できるcursorを用いて取得するためのコンストラクタ
    constructor(cursor: Cursor) : this(
            cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.TITLE)),
            cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.ALBUM)),
            cursor.getString(cursor.getColumnIndex(MediaStore.Audio.Media.ARTIST)),
            cursor.getLong(cursor.getColumnIndex(MediaStore.Audio.Media.DURATION))
            )
    companion object {
        @JvmField  // 検索するデータの列名
        val PROJECTION = arrayOf(
                MediaStore.Audio.Media._ID,
                MediaStore.Audio.Media.TITLE,
                MediaStore.Audio.Media.DURATION,
                MediaStore.Audio.Media.ALBUM,
                MediaStore.Audio.Media.ARTIST
        )
        @JvmField  // 検索するデータ先一覧
        val SEARCH_URIS = arrayOf(
                MediaStore.Audio.Media.INTERNAL_CONTENT_URI,
                MediaStore.Audio.Media.EXTERNAL_CONTENT_URI
                )
        fun getItems(activity: Context): List<Track>{
            val tracks = mutableListOf<Track>()
            val resolver = activity.contentResolver
            SEARCH_URIS.map{  // 各データ先のuriに対して
                // データを検索して
                val cursor = resolver.query(it, Track.PROJECTION,null, null, null)
                while(cursor.moveToNext()){  // データの次がある限り(最初のindexは-1)
                    tracks.add(Track(cursor))  // データを加える
                }
                cursor.close()  // 後処理
            }
            // 結果をソートしておく
            tracks.sortBy { 
                it.title
            }
            return tracks
        }
    }
}
```
このように作成します。コメントを読めばわかるので説明は省きます。
### adapter用のクラスを作成する
そしてadapter用のクラスをListTrackAdapterという名前で次のように作成します。

```ListTrackAdapter.kt
package com.example.owner.gettrackexample

import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.TextView

//  リスト表示用の基本アダプターArrayAdapterを継承
class ListTrackAdapter(context: Context, data: List<Track>): ArrayAdapter<Track>(context, 0, data) {
    var mInflater: LayoutInflater  // レイアウト生成用のビルダー

    init {  // 初期化
        mInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
    }

    override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
        val item = getItem(position)
        // convertViewはListViewの表示用の部品一つ一つ
        var resultView = convertView

        //  以下の方法で実はしなくてもいいが処理コスト削減のために行う
        class ViewHolder(  // それぞれの表示用アイテムを保持したクラス
            val trackTextView: TextView,
            val durationTextView: TextView,
            val albumTextView: TextView,
            val artistTextView: TextView
        )
        var holder: ViewHolder
        if(resultView == null){ // 各パーツにおいて初めて表示するとき
            resultView = mInflater.inflate(R.layout.adapter_layout, null)  //  adapter用のレイアウトを作成
            holder = ViewHolder(  // レイアウト上のViewを取得する
                    resultView.findViewById(R.id.title),
                    resultView.findViewById(R.id.duration),
                    resultView.findViewById(R.id.album),
                    resultView.findViewById(R.id.artist)
                    )
                    resultView.tag = holder  // viewとholderを結び付ける
        }else{  // すでに一度は表示に使われていた時
            holder = resultView.tag as ViewHolder
        }

        val dm = item.duration/60000  // Trackの時間(ミリ秒)から分を取得
        val ds = (item.duration-(dm*60000))/1000  // Trackの時間(ミリ秒)から秒を取得

        // 各Viewの表示文字列を変更
        holder.trackTextView.text = item.title
        holder.durationTextView.text = String.format("(%d:%02d)",dm,ds)
        holder.albumTextView.text = item.album
        holder.artistTextView.text = item.artist
        
        return resultView!! 
    }
}
```
## 4 listにadapterをセットする
MainActivityのonResume関数をオーバーロードして以下を追記します。(そのままonCreateで初期化した方が良かったかもしれない)

```MainActivity.kt
val trackList = this.findViewById<ListView>(R.id.track_list)  // ListViewを取得
val tracks = Track.getItems(this)  // 配置するデータを取得する
val adapter = ListTrackAdapter(this, tracks)  // adapterを用意する
trackList.adapter = adapter  // adapterを配置する

trackList.setOnItemClickListener { parent, view, position, id -> // 各Track選択時に行われるイベント
    val item = parent.getItemAtPosition(position) as Track  // itemを取得
    val str = "「${item.title}」 by ${item.artist}"
    Toast.makeText(this, str, Toast.LENGTH_LONG).show() // トースト表示（長め）
}
```

# 実行してみる
ここまで来たらうまく動くはずです。実機または仮想デバイスで動かしてみましょう。（仮想デバイスだと楽曲情報がないかもしれません。）しかし最初は必ず落ちると思うので落ちた後は設定のアプリから権限を与えて再実行してみましょう。するとうまく動くはずです。今回の場合こんな画面になったら成功です。
![list_example1.png](https://qiita-image-store.s3.amazonaws.com/0/167385/ee3f7adb-e7a4-82d7-87e6-737b93ffa063.png)

# まとめ
記事としては長い方ですが最後まで見てくださった方はありがとうございます。本記事は初心者の初心者による初心者のための紹介記事なので至らない点は多々あると思いますがご指摘してくださるとありがたいです。ではお疲れさまでした。