retrofitでAPIを楽に使う
androidから何かサーバ側のAPIを叩く場合、色々と用意しなければならない。OkHttpなどのHTTP通信を行うインスタンス、通信は基本非同期処理なのでコールバックを渡すためのラッパー、それにAPIのURLとパラメータのチェック、APIから返ってきた値をコンバートするためのものなど…それらの面倒事を一手に引き受けてくれるのがretrofitだ。
概要
retrofitはHTTPクライアント。javaのインターフェース形式でAPIを定義出来る。こんな風に。
public interface GitHubService { @GET("users/{user}/repos") Call<List<Repo>> listRepos(@Path("user") String user); }
/users/{user}/reposというパスに対してGETアクセスをするというメソッドがこれだけで定義できる。{user}は可変のユーザー名。userに入る引数とともにこのlistReposというメソッドを呼んであげればそれでHTTPGETが出来る。返り値はRepoクラスのインスタンスのListとなる。あとはbuilderを使ってこのインターフェースを実装したインスタンスを作ればオールOK。
Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://api.github.com") .build(); GitHubService service = retrofit.create(GitHubService.class); Call<List<Repo>> repos = service.listRepos("octocat");
当然GETだけでなくPOSTやPUTやDELETEも可能。formEncordedなものや画像データ、マルチパートも投げることができる。 内部的にはOkHttpやVolleyなどをクライアントとして利用することができる。なのでOkHttpに噛ませたインターセプターなどともバッティングすることなく利用することができる。 また、レスポンスはjacksonやgsonなどのコンバータがサポートされているので、サポートされているもののうち好きなコンバータを使えばレスポンスjson=>javaオブジェクトへの変換も勝手にやってくれる。
導入
android studioでgradleを使っているなら以下をいれてsyncすればOK。
compile 'com.squareup.retrofit2:retrofit:2.0.0-beta3'
バージョンは2をしていしたほうがいいだろう。それまではバージョン1.9がメインだったものの、夏ごろにメジャーバージョンがあがった。で、メジャーバージョンがあがったのにつれてインターフェースの宣言の仕方などがかなり変わったようだし、今のところは2.0に関しての情報も十分にあるので1.9を選ぶ必要はない。以降はバージョン2.0での使い方を説明する。
jsonのコンバーターを指定する場合はそれ用のライブラリも必要となることに注意。自分はjacksonを使っているので、以下も一緒にbuild.gradleに書き加えてある。
compile 'com.squareup.retrofit:converter-jackson:2.0.0-beta2'
使ってみよう
使う前にはまずレスポンスを格納するEntityクラスが必要なのでそれを作っておく。jacksonをつかっているので、jackson形式でのEntityを定義している。ここは適宜gsonなどに変えればいい。
public class Repo { public int id; public String name; public String url; public boolean private; }
そして重複するがインターフェースの宣言。
public interface GitHubService { @GET("users/{user}/repos") Call<List<Repo>> listRepos(@Path("user") String user); }
あとはこれを呼ぶ。
public class GithubActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_github); Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://api.github.com") .build(); GitHubService service = retrofit.create(GitHubService.class); Call<List<Repo>> call = service.listRepos("shim0mura"); call.enqueue(new Callback<List<Repo>>() { @Override public void onResponse(Response<List<Repo>> response) { if (response.isSuccess()) { List<Repo> result = response.body(); } } @Override public void onFailure(Throwable t) { } }); } }
serviceという変数にretrofitのインターフェースを実装したインスタンスをいれる。そしてそれから定義したメソッドを呼ぶ。そのメソッドの返り値にenqueueしたところで通信を始める。enqueueメソッドの引数はコールバック。通信が終了したところでコールバック内のメソッドが実行される。基本的に20x系のレスポンスでも50xや40xなどのエラー系のレスポンスでも全部onResponseが呼び出される。なので20x系の成功時のレスポンスを判定したいときはisSuccessメソッドで判定を行う。あとはonResponseの仮引数、この場合はresponseというインスタンスのbodyメソッドを呼べば、jsonなどの生のレスポンスは先ほど定義したEntityクラスインスタンスに勝手にコンバートしてくれる。楽ちん。
enqueueを使わずにcall.execute()とすれば非同期でない同期的な通信もできるらしい。使ったこと無いけど…
POSTも出来る
GET以外については以下のようにメソッドを定義する。
@POST("/user") Call<User> createUser( @Body HashMap<String, User>user );
@Bodyアノテーションでpostしたいオブジェクトを指定すればいい。呼び出し時も
new HashMap<String, User> postUser = new HashMap<>(); User user = new User(); user.name = "shim0mura"; user.job ="unemployed"; postUser.put("user", user); Call<User> call = service.createUser(postUser);
とGETとほとんど変わることなく呼び出せる。enqueueなどのコールバック処理も全く同じ。インターフェース側だけ変えればいいだけなのである。 ちなみにわざわざHashMapを渡さず
@POST("/user") Call<User> createUser( @Body User user );
と直にEntityを渡す事もできる。というかそっちの方が普通な使い方。じゃあ何故HashMapなんか使ったのかというと、サーバ側でparams[:user][:name]というrailsっぽい形式で取得したかったため。後者のような普通なpostだとparams[:CreateUser]の中にnameやjobが入っているという形式になってしまう。そのままだとどうやらメソッド名(しかもアッパーキャメル)で内容がpostされてしまう。HashMapを使えば、hashのキーがpost内容のキーになるのでrails使いには楽。
OkHttpを使う
更に、retrofitの内部のHTTPクライアントとしてOkHttpやVolleyなどを使うこともできる。 使い方というか指定の仕方は簡単。
OkHttpClient client = new OkHttpClient(); Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://api.github.com") .client(client) .build();
これだけ。buildの際にclientメソッドを呼び出し、その引数としてOkHttpのインスタンスを入れればいいだけ。なのでそのインスタンスをclientに入れる前にインターセプターを噛ましていても問題ない。
OkHttpClient client = new OkHttpClient(); client.interceptors().add(new Interceptor() { @Override public Response intercept(Chain chain) throws IOException { Response response = chain.proceed(chain.request()); // Do anything with response here return response; } }); Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://api.github.com") .client(client) .build();
これでちゃんとインターセプターは動作する。
コンバータの指定
更に更にコンバータもHTTPクライアントと同じようにして指定することができる。
Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://api.nuuneoi.com/base/") .addConverterFactory(JacksonConverterFactory.create()) .build();
これだけ。コンバータをgsonに変えたくなったら、GsonConverterFactory.create()に変えてやればいい。(もちろんbuilde.gradleでgsonコンバータを加えている前提)
という感じで自前の面倒なコンバータやAPIラッパーを作らなくてもretrofitさえあれば大体のことはまかなえる。素敵。