LoginSignup
5
4

More than 5 years have passed since last update.

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

Posted at

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

やりたいこと

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

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

全部使えるようにする

これはbuild.gradleに書くだけ

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を見やすく整形して表示するを御覧ください。

1. ユーザ情報取得API

https://api.github.com/users/{user_name}

叩いてみたときの返却値

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

叩いてみたときの返却値(多いので省略)

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を用意する。

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で返却されるデータを全部定義しなくてもいい)

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

リポジトリ情報格納用

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で書いてみる。

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に書き換える

MainActivity.java
・・・
MyAsyncTask myAsyncTask = new MyAsyncTask(gitHubApi);
myAsyncTask.execute("furusin");
・・・
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を用意

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

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

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)をどう定義したらいいのか悩んでる。

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

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()とか…なんかダサい)

5
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4