iTunes APIを使ったAndroidアプリハンズオン

# iTunes APIとは
https://www.apple.com/itunes/affiliates/resources/documentation/itunes-store-web-service-search-api.html

登録不要で使えて、例えば、

```
https://itunes.apple.com/search?term=beatles&country=JP&lang=ja_jp&media=music
```

で、「beatles」を検索すると、

```json
{
  "resultCount":50,
  "results": [
    {
      "wrapperType":"track", 
      "kind":"song", 
      "artistId":136975, 
      "collectionId":458032395, 
      "trackId":458032429, 
      "artistName":"ビートルズ", 
      "collectionName":"1", 
      "trackName":"Let It Be", 
      "collectionCensoredName":"1", 
      "trackCensoredName":"Let It Be", 
      "artistViewUrl":"https://itunes.apple.com/jp/artist/bitoruzu/id136975?uo=4", 
      "collectionViewUrl":"https://itunes.apple.com/jp/album/let-it-be/id458032395?i=458032429&uo=4", 
      "trackViewUrl":"https://itunes.apple.com/jp/album/let-it-be/id458032395?i=458032429&uo=4", 
      "previewUrl":"http://a1770.phobos.apple.com/us/r1000/059/Music4/v4/cf/b4/36/cfb43624-f3e0-269f-f81a-911f9876732c/mzaf_4722855266064184302.plus.aac.p.m4a", 
      "artworkUrl30":"http://is5.mzstatic.com/image/pf/us/r30/Features/85/fe/95/dj.kfrgxzbp.30x30-50.jpg", 
      "artworkUrl60":"http://is4.mzstatic.com/image/pf/us/r30/Features/85/fe/95/dj.kfrgxzbp.60x60-50.jpg", 
      "artworkUrl100":"http://is1.mzstatic.com/image/pf/us/r30/Features/85/fe/95/dj.kfrgxzbp.100x100-75.jpg", 
      "collectionPrice":2000.00, 
      "trackPrice":250.00, 
      "releaseDate":"2000-11-13T08:00:00Z", 
      "collectionExplicitness":"notExplicit", 
      "trackExplicitness":"notExplicit", 
      "discCount":2, 
      "discNumber":1, 
      "trackCount":27, 
      "trackNumber":26, 
      "trackTimeMillis":230773, 
      "country":"JPN", 
      "currency":"JPY", 
      "primaryGenreName":"ロック"
    }, {
      ...
    }, {
      ...
    }
  ]
}
```

が返ってきます。

今回は、

* trackName（曲や動画のタイトル）
* artistName（アーティスト名）
* artworkUrl100（アートワーク画像URL）
* previewUrl（曲や動画の試聴用データURL）

あたりを使って、Androidアプリを作ってみます。

# プロジェクトを作成する
Android Studioを開きます。
![スクリーンショット 2015-06-03 14.02.44.png](https://qiita-image-store.s3.amazonaws.com/0/80314/cb529bbc-dd94-6f4f-cb32-73ab7027d949.png)

![スクリーンショット 2015-06-03 14.05.33.png](https://qiita-image-store.s3.amazonaws.com/0/80314/658218df-1b39-be50-79e0-41e7b78706f4.png)

![スクリーンショット 2015-06-03 14.07.06.png](https://qiita-image-store.s3.amazonaws.com/0/80314/d6e379c2-2dc5-86fc-f7e7-ec9e6ec97d71.png)

![スクリーンショット 2015-06-03 14.09.20.png](https://qiita-image-store.s3.amazonaws.com/0/80314/b3f1583b-fb63-d812-1f28-6f82a7e97ba9.png)

## 自動importを設定する
Preferences > Editor > General > Auto Import > 
![スクリーンショット 2015-06-04 21.30.21.png](https://qiita-image-store.s3.amazonaws.com/0/80314/38ec0648-02ec-788c-e690-4b891f198486.png)

# Volleyをインストールする
## build.gradleに設定を書く
build.gradle (Module: app)を開いて、dependenciesの中に以下を追加します。

```
compile 'com.mcxiaoke.volley:library:1.0.16'
```

![スクリーンショット 2015-06-03 14.20.48.png](https://qiita-image-store.s3.amazonaws.com/0/80314/09a4aa5f-d080-2b84-b16f-632f2b2e88be.png)

## インストールする
![スクリーンショット 2015-06-03 14.21.05.png](https://qiita-image-store.s3.amazonaws.com/0/80314/d8281d5b-f515-7fb3-e996-04ff3c82e6cc.png)

# iTunes APIと通信して、結果を一覧画面に表示する

## Volleyで使う画像キャッシュクラスを作る
ルートパッケージ上で右クリックして、New > Java Class。
![スクリーンショット 2015-06-03 15.43.32.png](https://qiita-image-store.s3.amazonaws.com/0/80314/3c5932e7-f8a2-8e9b-c1ac-1c1f6fa0cd72.png)

![スクリーンショット 2015-06-03 15.45.20.png](https://qiita-image-store.s3.amazonaws.com/0/80314/9278e7e9-809f-551c-fa0f-60524ae452d8.png)

以下のように書きます。先頭行のpackageの行は書いてませんので、コピペするときはpackageの行を消さないように気をつけてください。

```java
import android.graphics.Bitmap;
import android.util.LruCache;

import com.android.volley.toolbox.ImageLoader;


class LruImageCache implements ImageLoader.ImageCache {

    private LruCache<String, Bitmap> mCache;

    public LruImageCache() {
        int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        int cacheSize = maxMemory / 8;

        mCache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap bitmap) {
                return bitmap.getByteCount() / 1024;
            }
        };
    }

    @Override
    public Bitmap getBitmap(String url) {
        return mCache.get(url);
    }

    @Override
    public void putBitmap(String url, Bitmap bitmap) {
        mCache.put(url, bitmap);
    }
}
```

これは、メモリ内に、使用可能なメモリ内の一定の量まで画像をキャッシュし、いっぱいになったら使用頻度が低い画像から破棄されるようになっています。

## 一覧画面のClassを作る
ルートパッケージ上で右クリックして、New > Activity > Blank Activity。
![71158ac0-9fbc-de15-ae8e-34972db40650.png](https://qiita-image-store.s3.amazonaws.com/0/80314/f80a02e0-e703-d5c0-2d66-63c8c0530ec3.png)

Launcher ActivityをONにすると、アプリ起動時に表示される画面になります。
![スクリーンショット 2015-06-03 15.57.37.png](https://qiita-image-store.s3.amazonaws.com/0/80314/04086839-8645-2ac7-1aa3-7982049b8c9c.png)

3つのファイルが作成されます。ListActivity.javaは、Java Classで処理を書くところです。activity_list.xmlは、画面のレイアウトを書くところです。menu_list.xmlは、メニューを書くところです。
![スクリーンショット 2015-06-03 16.03.57.png](https://qiita-image-store.s3.amazonaws.com/0/80314/f5ff8e9e-7d0b-0cf4-b1e2-a0ec5116d351.png)

加えて、AndroidManifest.xmlにも、activity要素が追加されます。Java ClassからActivityを作成した場合は、自分でAndroidManifest.xmlに追加する必要があります。AndroidManifest.xmlに追加しないで呼び出そうとすると実行時エラーになります。activity要素の下のintent-filter要素は、アプリ起動時に表示される画面であることが書かれています（Launcher ActivityをONで作成した場合）。
![スクリーンショット 2015-06-03 16.11.42.png](https://qiita-image-store.s3.amazonaws.com/0/80314/b741c3e5-6666-8474-94ab-6ead5d0ae544.png)

今回はメニューを使わないので、ListActivityのonCreateOptionsMenu(Menu)とonOptionsItemSelected(MenuItem)は削除していいです。menu_list.xmlも削除していいです。
![スクリーンショット 2015-06-03 16.17.08.png](https://qiita-image-store.s3.amazonaws.com/0/80314/de9f3984-7f8b-5899-e423-d0f6a6d2dd7a.png)

## インターネットを使う権限を設定する
AndroidManifest.xmlを開いて、manifest要素の中に以下を追加します。これを書かないと、このアプリからインターネットを使うことができません。

```xml
    <uses-permission android:name="android.permission.INTERNET" />
```

## 一覧画面のActionBarのタイトルをアプリ名にする
AndroidManifest.xmlを開いて、activity要素のandroid:label属性を以下のように変更します。

```xml
        <activity
            android:name=".ListActivity"
            android:label="@string/app_name" >
```

## 一覧画面のLayout XMLを書く
activity_list.xmlを開いて、以下のように書きます。

```xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                xmlns:tools="http://schemas.android.com/tools"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                tools:context="com.example.kentoriumi.itunesmusicsearch.ListActivity">

    <EditText android:id="@+id/edit_text"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:layout_alignParentTop="true"
              android:hint="Keyword"
              android:inputType="text"/>
    <ListView android:id="@+id/list_view"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:layout_alignParentBottom="true"
              android:layout_below="@id/edit_text"/>

</RelativeLayout>
```

![スクリーンショット 2015-06-03 16.29.50.png](https://qiita-image-store.s3.amazonaws.com/0/80314/e757332a-d71e-bb32-6b2a-9336b65c3ea7.png)

## 一覧画面の行のLayout XMLを書く
app/res/layout上で右クリックして、New > Layout resource file。
![スクリーンショット 2015-06-03 16.33.17.png](https://qiita-image-store.s3.amazonaws.com/0/80314/3645541d-98c6-b074-8cc6-7b4b0778abe2.png)

![スクリーンショット 2015-06-03 16.35.32.png](https://qiita-image-store.s3.amazonaws.com/0/80314/0eae125d-8652-bd97-b231-b8aeb637b609.png)

list_item.xmlを開いて、以下のように書きます。

```xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                xmlns:tools="http://schemas.android.com/tools"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:padding="10dp">

    <ImageView
        android:id="@+id/image_view"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:layout_marginRight="10dp"/>

    <TextView android:id="@+id/track_text_view"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:layout_alignParentTop="true"
              android:layout_toRightOf="@id/image_view"
              android:ellipsize="end"
              android:lines="1"
              android:textSize="20sp"
              tools:text="track"/>

    <TextView android:id="@+id/artist_text_view"
              android:layout_width="wrap_content"
              android:layout_height="wrap_content"
              android:layout_alignParentBottom="true"
              android:layout_toRightOf="@id/image_view"
              android:ellipsize="end"
              android:lines="1"
              tools:text="artist"/>

</RelativeLayout>
```

![スクリーンショット 2015-06-03 16.41.02.png](https://qiita-image-store.s3.amazonaws.com/0/80314/296b4667-40dd-66d9-8e6c-be1b5e25c626.png)

再度、activity_list.xmlを開いて、ListView要素の中に以下を追記すると、プレビューの一覧画面に先ほど作った行が表示されるようになります。

```xml
    <ListView android:id="@+id/list_view"
              ...
              tools:listitem="@layout/list_item"/>
```

![スクリーンショット 2015-06-03 16.45.29.png](https://qiita-image-store.s3.amazonaws.com/0/80314/7e711d4a-30ac-a4de-716b-b5a5dd6ce9f2.png)

## Volleyを準備する
ListActivity.javaを開いて、ListActivityの中に

```java
    private RequestQueue mRequestQueue;
    private ImageLoader mImageLoader;
```

を、onCreate(Bundle)の中に

```java
        mRequestQueue = Volley.newRequestQueue(this);
        mImageLoader = new ImageLoader(mRequestQueue, new LruImageCache());
```

を追加します。importの追加を聞かれたらOKしてください。

## 行を表示する処理を書く
ListActivity.javaを開いて、ListActivityの中に以下を追加します。importの追加を聞かれたらOKしてください。Adapterは、ListViewのように、保持している大量のデータの中から一部だけを表示するViewを使うときに、Viewとデータの橋渡しをします。

```java
private class ListAdapter extends ArrayAdapter<JSONObject> {

        public ListAdapter(Context context, int resource) {
            super(context, resource);
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            if (convertView == null) {
                // 再利用可能なViewがない場合は作る
                convertView = getLayoutInflater().inflate(R.layout.list_item, null);
            }

            ImageView imageView = (ImageView) convertView.findViewById(R.id.image_view);
            TextView trackTextView = (TextView) convertView.findViewById(R.id.track_text_view);
            TextView artistTextView = (TextView) convertView.findViewById(R.id.artist_text_view);

            ImageLoader.ImageContainer imageContainer = (ImageLoader.ImageContainer) imageView.getTag();
            if (imageContainer != null) {
                imageContainer.cancelRequest(); // 画像取得中のリクエストをキャンセルする（再利用された時）
            }
            imageView.setImageBitmap(null); // 残ってる画像を消す（再利用された時）

            // 表示する行番号のデータを取り出す
            JSONObject result = getItem(position);

            ImageLoader.ImageListener listener = ImageLoader.getImageListener(imageView, 0, 0);
            imageView.setTag(mImageLoader.get(result.optString("artworkUrl100"), listener));

            trackTextView.setText(result.optString("trackName"));
            artistTextView.setText(result.optString("artistName"));

            return convertView;
        }
    }
```

さらに、ListActivityの中に

```java
    private ListAdapter mAdapter;
```

を、onCreate(Bundle)の中に

```java
        mAdapter = new ListAdapter(this, R.layout.list_item);
        ListView listView = (ListView) findViewById(R.id.list_view);
        listView.setAdapter(mAdapter);
```

を追加します。先ほど作ったAdapterをListViewに設定してます。

## 検索の処理を書く
ListActivity.javaを開いて、ListActivityの中に以下を追加します。importの追加を聞かれたらOKしてください。View.OnKeyListenerは、キーボードのキーを押されたりしたときに呼び出されるところです。Enterキーが上がったときに検索処理を実行し、実行結果をAdapterに詰める（詰めたら画面は随時更新される）処理を書いています。

```java
private class OnKeyListener implements View.OnKeyListener {

        @Override
        public boolean onKey(View view, int keyCode, KeyEvent keyEvent) {
            if (keyEvent.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_ENTER) {
                EditText editText = (EditText) view;

                // キーボードを閉じる
                InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
                imm.hideSoftInputFromWindow(editText.getWindowToken(), 0);

                String text = editText.getText().toString();
                try {
                    // url encode　例. スピッツ > %83X%83s%83b%83c
                    text = URLEncoder.encode(text, "UTF-8");
                } catch (UnsupportedEncodingException e) {
                    Log.e("", e.toString(), e);
                    return true;
                }
                if (!TextUtils.isEmpty(text)) {
                    String url =
                            "https://itunes.apple.com/search?term=" + text + "&country=JP&media=music&lang=ja_jp";
                    mRequestQueue.add(new JsonObjectRequest(Request.Method.GET, url,
                            new Response.Listener<JSONObject>() {
                                @Override
                                public void onResponse(JSONObject response) {
                                    Log.d("", response.toString());

                                    mAdapter.clear();

                                    JSONArray results = response.optJSONArray("results");
                                    if (results != null) {
                                        for (int i = 0; i < results.length(); i++) {
                                            mAdapter.add(results.optJSONObject(i));
                                        }
                                    }
                                }
                            },
                            null));
                }
                return true;
            }
            return false;
        }
    }
```

さらに、onCreate(Bundle)の中に

```java
        final EditText editText = (EditText) findViewById(R.id.edit_text);
        editText.setOnKeyListener(new OnKeyListener());
```

を追加します。先ほど作ったOnKeyListenerをEditTextに設定してます。

ここまでで、ListActivity.javaは以下のようになってるはずです。先頭行のpackageの行は書いてませんので、コピペするときはpackageの行を消さないように気をつけてください。

```java
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;

import com.android.volley.Request;
import com.android.volley.RequestQueue;
import com.android.volley.Response;
import com.android.volley.toolbox.ImageLoader;
import com.android.volley.toolbox.JsonObjectRequest;
import com.android.volley.toolbox.Volley;

import org.json.JSONArray;
import org.json.JSONObject;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;


public class ListActivity extends Activity {

    private RequestQueue mRequestQueue;
    private ImageLoader mImageLoader;

    private ListAdapter mAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_list);

        mRequestQueue = Volley.newRequestQueue(this);
        mImageLoader = new ImageLoader(mRequestQueue, new LruImageCache());

        mAdapter = new ListAdapter(this, R.layout.list_item);
        ListView listView = (ListView) findViewById(R.id.list_view);
        listView.setAdapter(mAdapter);
    }

    private class ListAdapter extends ArrayAdapter<JSONObject> {

        public ListAdapter(Context context, int resource) {
            super(context, resource);
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            if (convertView == null) {
                // 再利用可能なViewがない場合は作る
                convertView = getLayoutInflater().inflate(R.layout.list_item, null);
            }

            ImageView imageView = (ImageView) convertView.findViewById(R.id.image_view);
            TextView trackTextView = (TextView) convertView.findViewById(R.id.track_text_view);
            TextView artistTextView = (TextView) convertView.findViewById(R.id.artist_text_view);

            ImageLoader.ImageContainer imageContainer = (ImageLoader.ImageContainer) imageView.getTag();
            if (imageContainer != null) {
                imageContainer.cancelRequest(); // 画像取得中のリクエストをキャンセルする（再利用された時）
            }
            imageView.setImageBitmap(null); // 残ってる画像を消す（再利用された時）

            // 表示する行番号のデータを取り出す
            JSONObject result = getItem(position);

            ImageLoader.ImageListener listener = ImageLoader.getImageListener(imageView, 0, 0);
            imageView.setTag(mImageLoader.get(result.optString("artworkUrl100"), listener));

            trackTextView.setText(result.optString("trackName"));
            artistTextView.setText(result.optString("artistName"));

            return convertView;
        }
    }

    private class OnKeyListener implements View.OnKeyListener {

        @Override
        public boolean onKey(View view, int keyCode, KeyEvent keyEvent) {
            if (keyEvent.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_ENTER) {
                EditText editText = (EditText) view;

                // キーボードを閉じる
                InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
                imm.hideSoftInputFromWindow(editText.getWindowToken(), 0);

                String text = editText.getText().toString();
                try {
                    // url encode　例. スピッツ > %83X%83s%83b%83c
                    text = URLEncoder.encode(text, "UTF-8");
                } catch (UnsupportedEncodingException e) {
                    Log.e("", e.toString(), e);
                    return true;
                }
                if (!TextUtils.isEmpty(text)) {
                    String url =
                            "https://itunes.apple.com/search?term=" + text + "&country=JP&media=music&lang=ja_jp";
                    mRequestQueue.add(new JsonObjectRequest(Request.Method.GET, url,
                            new Response.Listener<JSONObject>() {
                                @Override
                                public void onResponse(JSONObject response) {
                                    Log.d("", response.toString());

                                    mAdapter.clear();

                                    JSONArray results = response.optJSONArray("results");
                                    if (results != null) {
                                        for (int i = 0; i < results.length(); i++) {
                                            mAdapter.add(results.optJSONObject(i));
                                        }
                                    }
                                }
                            },
                            null));
                }
                return true;
            }
            return false;
        }
    }
}
```

## 実行してみましょう

# 詳細画面に遷移して、試聴する
## 詳細画面のClassを作る
ルートパッケージ上で右クリックして、New > Activity > Blank Activity。
![スクリーンショット 2015-06-03 18.16.19.png](https://qiita-image-store.s3.amazonaws.com/0/80314/760e4f86-b3a4-ff69-0b73-2a47e5e1dbd2.png)

![スクリーンショット 2015-06-03 18.16.32.png](https://qiita-image-store.s3.amazonaws.com/0/80314/2ddff94d-31d6-0310-986c-4e807ad738fd.png)

今回はメニューを使わないので、DetailActivityのonCreateOptionsMenu(Menu)とonOptionsItemSelected(MenuItem)は削除していいです。menu_detail.xmlも削除していいです。

## 詳細画面のLayout XMLを書く
activity_detail.xmlを開いて、以下のように書きます。

```java
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                xmlns:tools="http://schemas.android.com/tools"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                tools:context="com.example.kentoriumi.itunesmusicsearch.DetailActivity">

    <VideoView android:id="@+id/video_view"
               android:layout_width="match_parent"
               android:layout_height="match_parent"/>

</RelativeLayout>
```

![スクリーンショット 2015-06-03 18.22.23.png](https://qiita-image-store.s3.amazonaws.com/0/80314/29a0630b-d3b4-fa0f-253f-0bdc4ccc39af.png)

## 一覧画面から詳細画面に遷移する処理を書く
ListActivity.javaを開いて、ListActivityの中に以下を追加します。importの追加を聞かれたらOKしてください。AdapterView.OnItemClickListenerは、ListViewの行がタップされたときに呼び出されるところです。タップされた行番号のデータを取り出し、必要なデータを詳細画面に渡しています。

```java
    private class OnItemClickListener implements AdapterView.OnItemClickListener {

        @Override
        public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
            Intent intent = new Intent(ListActivity.this, DetailActivity.class);

            // タップされた行番号のデータを取り出す
            JSONObject result = mAdapter.getItem(position);
            intent.putExtra("track_name", result.optString("trackName"));
            intent.putExtra("preview_url", result.optString("previewUrl"));

            startActivity(intent);
        }
    }
```

さらに、onCreate(Bundle)の中に

```java
        listView.setOnItemClickListener(new OnItemClickListener());
```

を追加します。先ほど作ったOnItemClickListenerをListViewに設定してます。

## 詳細画面で試聴する処理を書く
DetailActivity.javaを開いて、以下のように書きます。先頭行のpackageの行は書いてませんので、コピペするときはpackageの行を消さないように気をつけてください。

```java
import android.app.Activity;
import android.net.Uri;
import android.os.Bundle;
import android.text.TextUtils;
import android.widget.MediaController;
import android.widget.VideoView;


public class DetailActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_detail);

        // ActionBarのタイトルを設定する
        String trackName = getIntent().getExtras().getString("track_name");
        getActionBar().setTitle(trackName);

        String previewUrl = getIntent().getExtras().getString("preview_url");
        if (!TextUtils.isEmpty(previewUrl)) {
            VideoView videoView = (VideoView) findViewById(R.id.video_view);
            videoView.setMediaController(new MediaController(this)); // 再生ボタンとかをつける
            videoView.setVideoURI(Uri.parse(previewUrl)); // URLを設定する
            videoView.start(); // 再生する
        }
    }
}
```

## 実行してみましょう

おわり。お疲れさまでした。

# 完成品はここにあります
AndroidStudioのプロジェクト構成になっているので、Eclipseの場合はごにょごにょしてください。
https://codebreak.com/git/kenchan1837/iTunesMusicSearchAndroid/