Okhttp + Gson を使ったJavaによるWebAPI入門
大学の授業の副手でJavaでWebAPIを使ったプログラムについてまとめることになったのでここにまとめます。
使用するライブラリ
JavaでHTTP通信だったりJSONを扱うのに一から自分で書くのって結構つらいですよね。
またエラーハンドリングとかしっかりやらないととても使えるものにはならないと思います。
よって今回はライブラリを使います。
※ 勉強するのにライブラリ使って良いのかよって思う人もいると思いますが、今回はサクッと使えるようなサンプル用意してくれっていうオーダーだったのでライブラリは使用します。
使用するWebAPI
Livedoor Weather Web Service
Livedoorが提供している天気予報APIです。
てきとうに選びました。まぁ認証は無しのAPIならなんでも良かったです。
HTTP通信
通信部分はOkhttpを使います。
HttpUrl
後述の Request
インスタンスを作成するときにURLを指定するんですが、単純に文字列を渡してもいんですがUrlを作成するための HttpUrl
クラスも存在します。
こちらはいろんなエンドポイント用に文字列を用意しておいたり、パラメータの指定を簡単に書けるようにしてくれます。
作成するには HttpUrl.Builder
クラスが存在するので以下のように作成していきます。
HttpUrl.Builder builder = HttpUrl.Builder() .scheme(SCHEME) .host(HOST) .addPathSegment(FORECAST) .addPathSegment(WEB_SERVICE) .addPathSegment(JSON) .addPathSegment(ROOT);
上記で作成したURLは SCHEME://HOST/FORECAST/WEB_SERVICE/JSON/ROOT
になります。
またパラメータを指定するのも簡単で以下のように書きます。
builder.addQueryParameter("city", "400040");
これによって今回使用するURLが作成できました。
SCHEME://HOST/FORECAST/WEB_SERVICE/JSON/ROOT?city=400040
Request
通信するときは Request
インスタンスを作り OkHttpClient#newCall(Request request)
を使って Call
インスタンスを作ります。
通信方法には同期通信と非同期通信があり、同期通信は Call#execute()
を、非同期通信は Call#enqueue(Callback callback)
を使います。
また、 Request
を作るときにHTTPメソッドを指定して作成します。
今回はGETメソッドとPOSTメソッドについて説明します。
GET
Request.Builder
にURL渡して Request.Builder#get()
を叩くだけ。
まあ Request.Builder
のコンストラクタの実装を見るとわかると思いますが、デフォルトはGETメソッドが指定されてるので get()
メソッド叩かなくても良いかも。
Request request = new Request.Builder()
.url(builder.build())
.get()
.build();
※ builder
はHttpUrlの項目で作成したもの。
POST
POSTメソッドは以下のように使用します。
JSONオブジェクトをつけてPOSTの Request
を作成してみます。
MediaType jsonMedia = MediaType.parse("application/json; charset=utf-8"); Request request = new Request.Builder() .url(buildStateUrl()) .post(RequestBody.create(jsonMedia, "{\"key\": \"value\"}")) .build();
だいたいこんな感じです。他にもいろいろあるので気になる人は自分で調べてみてください。
通信
あと残りは仕上げです。
先程述べた通り通信には2種類あり、同期通信と非同期通信です。
また、通信するには Call
オブジェクトが必要です。
これまでに作ってきたRequestから以下のように作成します。
OkHttpClient client = new OkHttpClient();
Call call = client.newCall(request);
同期通信
特に難しいことはありません。 Call#execute()
を叩くだけです。
Response response = client.newCall(request).execute();
非同期通信
こちらもそんなに難しいことはありません。非同期通信なのでCallbackが必要になるだけです。
client.newCall(request).enqueue(new Callback() { @Override public void onFailure(Call call, IOException e) { // Error } @Override public void onResponse(Call call, Response response) { // Success } });
どちらも通信が終わったあとに Response
オブジェクトが返ってくるのでそこから結果を取得します。
今回使用するAPIではbody部分にJSONオブジェクトが返ってくるので以下の用に受け取ります。
String result = response.body().string();
これで通信部分は終了です。
次はWebAPIから取得したJSONオブジェクトGsonを使って実際に使えるようにします。
Gson
それでは取得できたJSONをJavaオブジェクトに変換して使えるようにしましょう。
まずJavaオブジェクトに変換するにはそれに対応するクラスを定義しないといけません。
例として以下のようなJSONがあるとします。
{ "id": 321, "name": "nshiba", "birth": { "country": "Japan", "day": "1990-01-01" }, "friends": [ 123, 234, 345, 456 ] }
これをJavaオブジェクトに変換しようと思ったら、全体用のクラスと birth
用のクラス、2つのクラスが必要です。
public class UserEntity { @SerializedName("id") private int id; @SerializedName("name") private String name; @SerializedName("birth") private BirthEntity birth; @SerializedName("friends") private List<Integer> friendList; public int getId() { return id; } public String getName() { return name; } public BirthEntity getBirth() { return birth; } public List<Integer> getFriendList() { return friendList; } }
public class BirthEntity { @SerializedName("country") private String country; @SerializedName("day") private String day; public String getCountry() { return country; } public String getDay() { return day; } }
これでクラスの宣言はできたので以下のようにGsonを使って変換します。
UserEntity userEntity = gson.fromJson(result, UserEntity.class);
まとめ
これで一通り入門できたのかもしれないです。
一応こんな感じで実装していけば問題ないかな、思う範囲でサクッと作ったのですが問題がでそうな部分があったら教えてもらえると幸いです。
質問などあればコメントでお願いします。
また、今回作成したサンプルは公開していますので、コードはそちらの方が参考にできると思います。
DroidKaigi2016に行ってきました
staff,speaker,等運営のかた、2日間お疲れ様でした!
いけなかった人は発表者の方が資料は公開してくれているので感謝しながら閲覧しましょう。 Day1, Day2
実際の開発の仕方やテストの書き方、ライブラリーの使い方等、自分の知らない技術をたくさん知れたのでとても勉強になり楽しかったです!
基本的にスライドにかなりまとまっているので見るだけでだいたい内容わかるので気になる方はぜひスライドみましょう。
僕自身も見たいセッションが重なっていたりしてみれないものが結構ありました。
後日、ビデオも公開されるみたいなのでビデオ公開されたら詳しく確認しようかな〜と思います。
今まであまり勉強会などは参加してなかったですが、今回参加してみて自分のしらない技術をしれるし、純粋にたのしいので今後は積極的に参加していこうかな、と思えました。
あとランチ美味しかったです。 1日目 2日目
丼 その2
この記事はCPS Advent Calendar 17日目の記事です。
昨日は ingressを通して考えたこと - 丼 でした。
今日は先日紹介した丼のAndroid版プロトタイプができたので紹介します。
続きを読む丼
この記事はCPS Advent Calendar 12日目の記事です。
昨日の記事は「esa.io が高いのでラボで代替ツールを作り始めた」でした。
我が研究室では、最近ドキュメント管理ツールとしてesaというサービスを利用していた。
チームを作りそのなかでやっていくのだが、1つのチームにつき一ヶ月アクティブユーザー1人、各500円の利用料がかかる。 しかし、はじめの2ヶ月間はフリートライアルだったので、とりあえず初めて見るか〜と軽い感じで導入したらとても使いやすかった。
しかし…しかし…12月1日でついにフリートライアルが終わってしまった。 このまま使い続けたいのだが、我が研究室は実は結構な人数が所属しており一月の利用料がだいぶ高くなってしまい泣く泣く断念へ…つらい…
そこで我々は諦めきれずに自分たちで代替サービスを作ることにした。その名は「丼」である。 Web版のプロトタイプはできており、自分の担当はAndroidアプリの作成だ。
すみません…間に合いませんでした…
〜つづく〜
神へと生まれ変わったScanSnap
この記事はCPS Advent Calendar 10日目の記事です。
前回の記事は 丼 でした。 http://staging.don.cps.im.dendai.ac.jp/articles/295
ところで、我が研究室には富士通からでている ScanSnap iX500 がある。
このiX500は1分間に両面25枚という超高速でスキャンでき、ひも付けされているPCに無線でデータを送ってくる、というめちゃくちゃ強い子だ。 そう1人で使うならば。 このひも付けなのだが1人にしか紐付けることができないため、研究室やオフィスなどの大人数でスキャンをするような場所ではとたんに無能に変貌するのだ。
我が研究室の人たちは思い出して欲しい。 この問題に我が研究室は、1つのPCにひたすら送りつけたり、なんとかNASやクラウドサービスに送れないかと、なんども話し合ったが納得できる答えが出なかった日々を。
しかし、この問題は唐突に解決したのである。 そう、なんと公式が「ScanSnapCloud」サービスを開始したのである。そう公式が。
遅いよって話だが、まぁいいだろう。
こうして、GoogleDriveやDropBox、Evernoteなど様々なCloudサービスにスキャンした瞬間からアップロードしてくれる神のツールへと生まれ変わったのである。
めでたし。めでたし。
FragmentManager.getFragments()で一部nullが返ってくる問題
8個のFragmentと4つの項目が入ったListViewからなるビューを作成する機会があった。
ListViewの項目をクリックしたらFragmentが置き換わる、というものだ。
クリックした際追加するFragmentは上限が8個で必ずしも8個ではない、という状況であった。
クリックされたらgetFragments()で今あるFragmentを取得し削除し、新たに選択された項目のFragmentを追加する、という簡単なものだと思い、以下のコードを書いた。
for( Fragment fragment : fragmentManager.getFragments() ) {
fragmentTransaction.remove(fragment);
}
しかしエラーがこんなエラーが…
java.lang.NullPointerException: Attempt to invoke virtual method
'boolean java.lang.String.equals(java.lang.Object)' on a null object reference
ん…?
なぜぬるぽが…getFragments()って現在保持しているFragmentを返すんじゃなかったのか…
ということですこし調べて見た結果、直前のコミットでremoveしたFragmentのところに新たにFragmentを追加していなかった場合、getFragments()を使用すると追加してないFragmentのみnullとなって返ってくるということが判明した。
結局上記のコードを
for( Fragment fragment : fragmentManager.getFragments() ) { if (fragment == null) continue; fragmentTransaction.remove(fragment); }
というふうに書き加え解決したが根本的な解決にはなっていない気がする…
どうしてこんな仕様なのか…自分のコードが間違っているのか…
とりあえず解決は出来ました。めでたしめでたし
Androidの無線LANデバッグ時の端末との接続を自動化
つい先日Androidの実機デバッグを無線LAN経由でやる方法を知った。
無線LAN経由でできる、というのは知っていたがなぜかやってなかった…
こんな便利なのに…
主なやり方は
AndroidとPCをUSB接続した後にadbコマンドで
adb tcpip ポート番号
↓
adb connect 端末のIP : 上で設定したポート番号
で接続できる。
端末のIPは設定→端末の状態から確認できる。
しかし常に同じ端末でデバッグをしない人からすると毎回IPを確認するのはとても面倒…
ということで自動で接続できるスクリプト書きました。
主にやってることは「adb shell netcfg」コマンドで取得したネットワーク状態からwlan0のIPを抽出してるだけ。
詳しいコードは以下を参照
utils/android_connect.sh at master · Nshiba/utils · GitHub
今までシェルスクリプトとか正規表現を全く勉強してなかったのでちょっと手こずった(というか強い人に教えてもらった)
これからはもっと勉強していこうな
※追記
デバッグ終了後は端末IPと設定したポート番号を知っていればだれでも接続できちゃうので危ない。
なので必ずUSB接続して「adb usb」コマンドを打つこと