Retrofit+Gson+Picasso+AsyncTaskで、APIを叩いて画像を表示する

結構複雑なので、練習用にやったことを纏めておく。

# やりたいこと
1. Androidで
2. RetrofitでAPIを叩いて
2. 返却されたJSONをGSONで使いやすく整形して
3. レスポンスに含まれる画像URLをPicassoで叩いて取得して
4. 表示する
5. これらを非同期に行う

「初めてのRetrofit」「初めてのGSON」「初めてのPicasso」「久しぶりのAsyncTask」でなかなか時間を食った…

# 全部使えるようにする
これは`build.gradle`に書くだけ

```java:build.gradle
compile 'com.squareup.retrofit2:retrofit:2.1.0'
compile 'com.squareup.retrofit2:converter-gson:2.1.0'
compile 'com.squareup.picasso:picasso:2.5.2'
```

# RetrofitでAPIを叩く
これもすごく楽だった。試しにGitHubのAPIを叩くとこんな感じ。
## 利用したいAPIのURIをまとめておく
※下記、curlの``python -mjson.tool``は、curlでjsonを取得した時に見やすく整形してくれるコマンドです。
詳しくは[こちら:curlで返却されるJSONを見やすく整形して表示する](http://qiita.com/furusin_oriver/items/b22c6d076285e6634596)を御覧ください。

### 1. ユーザ情報取得API
``https://api.github.com/users/{user_name}``

叩いてみたときの返却値

```java:Terminal
$ curl https://api.github.com/users/furusin | python -mjson.tool
{
    "avatar_url": "https://avatars.githubusercontent.com/u/2215210?v=3",
    "bio": null,
    "blog": "http://www.furusin.net/",
    "company": null,
    "created_at": "2012-08-25T02:20:41Z",
    "email": null,
    "events_url": "https://api.github.com/users/furusin/events{/privacy}",
    "followers": 3,
    "followers_url": "https://api.github.com/users/furusin/followers",
    "following": 46,
    "following_url": "https://api.github.com/users/furusin/following{/other_user}",
    "gists_url": "https://api.github.com/users/furusin/gists{/gist_id}",
    "gravatar_id": "",
    "hireable": null,
    "html_url": "https://github.com/furusin",
    "id": 2215210,
    "location": null,
    "login": "furusin",
    "name": "furusin",
    "organizations_url": "https://api.github.com/users/furusin/orgs",
    "public_gists": 9,
    "public_repos": 2,
    "received_events_url": "https://api.github.com/users/furusin/received_events",
    "repos_url": "https://api.github.com/users/furusin/repos",
    "site_admin": false,
    "starred_url": "https://api.github.com/users/furusin/starred{/owner}{/repo}",
    "subscriptions_url": "https://api.github.com/users/furusin/subscriptions",
    "type": "User",
    "updated_at": "2016-12-19T06:56:29Z",
    "url": "https://api.github.com/users/furusin"
}

```

### 2. リポジトリ一覧取得API
``https://api.github.com/users/{user_name}/``

叩いてみたときの返却値（多いので省略）

```java:Terminal
[
    {
        "archive_url": "https://api.github.com/repos/furusin/NodejsHelloWorld/{archive_format}{/ref}",
        "assignees_url": "https://api.github.com/repos/furusin/NodejsHelloWorld/assignees{/user}",
        "blobs_url": "https://api.github.com/repos/furusin/NodejsHelloWorld/git/blobs{/sha}",
        "branches_url": "https://api.github.com/repos/furusin/NodejsHelloWorld/branches{/branch}",
・・・
},{
・・・
}
]
```

## RetrofitでAPIを叩く
APIの情報は`interface`にまとめるといいらしい。
まずは`Interface`を用意する。

```java:GitHubApi.java
public interface GitHubApi {
    @GET("users/{user_name}")
    Call<User> getUser(@Path("user_name") String user);

    @GET("users/{user_name}/{repos}")
    Call<Repos> getRepos(@Path("user_name") String user);
}
```

こんな感じで、叩きたいURIを``@GET(URI)``としてやりたいアクション（メソッド）の中に記載する。

## Gsonで取得したデータを格納する
APIを叩いた返却値が格納されるのが上記の``Call<hoge>``の`hoge`の部分となる。
hogeの部分は自由にクラスを定義でき、返却される値と同じインスタンス名にしていると、Gsonが勝手に格納してくれる。

例えば今回の場合、ユーザ情報取得APIを叩くと、こんな形で返却される。

| パラメータ | インスタンス名 |
|:-:|:-:|
| ユーザ名  | name  |
| ユーザID  | id  |
| アイコン（アバター）画像URL  | avatar_url  |

なので、全く同じインスタンス名で定義する。Getterもあれば便利。
ユーザ情報格納用（JSONで返却されるデータを全部定義しなくてもいい）

```java:User.java
public class User {
    private String avatar_url;
    private String id;
    private String name;

    public String getAvator_url() {
        return avatar_url;
    }

    public String getId() {
        return id;
    }

    public String getName() {
        return name;
    }
}
```

リポジトリ情報格納用

```java:Repos.java
public class Repos {
    private String id;
    private String name;
    private String url;
    private String description;

    public String getDescription() {
        return description;
    }

    public String getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public String getUrl() {
        return url;
    }
}
```

## 実際にAPIを叩いてみる
どうやらひとまずCallbackで書いてみる。

```java:MainActivity.java
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        String BASE_URL = "https://api.github.com/"; //周囲！ベースURLは最後"/"で終わらなければならない

        Retrofit retrofit = new Retrofit.Builder().baseUrl(BASE_URL).addConverterFactory
                (GsonConverterFactory.create()).build();

        final GitHubApi gitHubApi = retrofit.create(GitHubApi.class);

        Call<User> userCall = gitHubApi.getUser("furusin");
        userCall.enqueue(new Callback<User>() {
            @Override
            public void onResponse(Call<User> call, Response<User> response) {
                User user = response.body();
                Log.d("test", "ID = " + user.getId());
                Log.d("test", "name = " + user.getName());
                Log.d("test", "avator_url = " + user.getAvator_url());
            }

            @Override
            public void onFailure(Call<User> call, Throwable t) {
                Log.d("test", "onFailure");
            }
        });
    }
}
```

## AsyncTaskで叩いてみる
やってることは同じで、MyAsyncTaskに書き換える

```java:MainActivity.java
・・・
MyAsyncTask myAsyncTask = new MyAsyncTask(gitHubApi);
myAsyncTask.execute("furusin");
・・・
```

```java:MyAsyncTask.java
public class MyAsyncTask extends AsyncTask<String, Void, String> {
    private GitHubApi gitHubApi;
    public MyAsyncTask(GitHubApi gitHubApi){
        this.gitHubApi = gitHubApi;
    }

    @Override
    protected String doInBackground(String... params) {
        Call<User> userCall = gitHubApi.getUser(params[0]);
        User user;
        try{
            user = userCall.execute().body();
            Log.d("test", "ID = " + user.getId());
            Log.d("test", "name = " + user.getName());
            Log.d("test", "avator_url = " + user.getAvator_url());

        }catch(IOException e){

        }
        return null;
    }
}
```

## Picassoで画像を取得する
`doInBackground`内で取得したレスポンスデータ（User.avator_url)を基に、画像を取得する。
そのまま`doInBackground`でやるのではなく、`onPostExecute`で行う。
（doInBackgroundの後続処理、という感じ）

わかりやすく、まずはImageViewを用意

```xml:activity_main.xml
    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        ・・・
        android:id="@+id/imageView" />

```

Picassoで画像を取得してImageViewに入れる

```java:MyAsynkTask.java
    @Override
    protected String doInBackground(String... params) {
        Call<User> userCall = gitHubApi.getUser(params[0]);
        User user = null;
        try{
            user = userCall.execute().body();
            Log.d("test", "ID = " + user.getId());
            Log.d("test", "name = " + user.getName());
            Log.d("test", "avator_url = " + user.getAvator_url());

        }catch(IOException e){

        }
        return user.getAvator_url();　//URLを渡してあげる
    }

    @Override
    protected void onPostExecute(String url) {
        super.onPostExecute(url);
        //受け取ったURLからPicassoで画像を取得し、ImageViewに入れる
        Picasso.with(context).load(url).into(imageView);　
    }
}
```

思ってた以上に簡単にできた！

# 悩み事
JSONが非常に深い構造だった場合には、情報格納用のクラス（ここだとUserやRepos）をどう定義したらいいのか悩んでる。

例えばこんな形だった場合。

```json:User
{
  name:hoge,
  id:111,
  location:{
    latitude:1111.1111,
    longitude:2222.2222
    },
    friends:[
      {
        name:fuga,
        id:222,
        location:{
          latitude:1111.1111,
          longitude:2222.2222
        },{
        ・・・
        }
      }
    ]
}
```

Userクラスの中にlocationクラスとfriendsクラスを定義するの…？とか考えて、ベストプラクティスはどうすればいいんだろう、と悩み中。
（`user.getLocation().getLatitude()`とか`user.getFriends().getLocation().getLatitude()`とか…なんかダサい）