結構複雑なので、練習用にやったことを纏めておく。
やりたいこと
- Androidで
- RetrofitでAPIを叩いて
- 返却されたJSONをGSONで使いやすく整形して
- レスポンスに含まれる画像URLをPicassoで叩いて取得して
- 表示する
- これらを非同期に行う
「初めてのRetrofit」「初めてのGSON」「初めてのPicasso」「久しぶりのAsyncTask」でなかなか時間を食った…
全部使えるようにする
これは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}
叩いてみたときの返却値
$ 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}/
叩いてみたときの返却値(多いので省略)
[
{
"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
を用意する。
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で返却されるデータを全部定義しなくてもいい)
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;
}
}
リポジトリ情報格納用
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で書いてみる。
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に書き換える
・・・
MyAsyncTask myAsyncTask = new MyAsyncTask(gitHubApi);
myAsyncTask.execute("furusin");
・・・
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を用意
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
・・・
android:id="@+id/imageView" />
Picassoで画像を取得してImageViewに入れる
@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)をどう定義したらいいのか悩んでる。
例えばこんな形だった場合。
{
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()
とか…なんかダサい)