Android 用の REST クライアントライブラリである RoboZombie を使って、 Yahoo! が提供している気象情報 API にアクセスしてみたときのメモ。
前説
RoboZombie
Android 用の REST クライアントライブラリ。
Yahoo! の気象情報API
YOLP(地図):気象情報API - Yahoo!デベロッパーネットワーク
場所と時間を渡したら、降水量とかを返してくれる Web API。
準備
Yahoo! のアプリケーション ID を取得する
Yahoo! Japan ID を取得する
アプリケーション情報を登録する
必要事項を入力して登録したら ID が発行されるので、それを控える。
登録した ID については Yahoo!デベロッパーネットワーク の「アプリケーションの管理」から確認できる。
実装
依存関係
build.gradle
dependencies {
compile 'com.lonepulse:robozombie:1.3.3'
}
パーミッションの追加
ネットワークアクセスが必要なので、パーミッションを追加する。
AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET" />
エンドポイントの定義
YahooWeatherApi.java
package com.example.android;
import com.lonepulse.robozombie.annotation.Endpoint;
import com.lonepulse.robozombie.annotation.GET;
import com.lonepulse.robozombie.annotation.QueryParam;
@Endpoint("http://weather.olp.yahooapis.jp/v1/place")
public interface YahooWeatherApi {
@GET
String request(@QueryParam("appid") String appid,
@QueryParam("coordinates") String coordinates);
}
API を実行するクラスの作成
ApiService.java
package com.example.android;
import android.os.AsyncTask;
import com.lonepulse.robozombie.annotation.Bite;
import com.lonepulse.robozombie.proxy.Zombie;
public class ApiService extends AsyncTask<Void, Void, String> {
private static final String API_ID = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
@Bite
private YahooWeatherApi api;
{
Zombie.infect(this);
}
@Override
protected String doInBackground(Void... params) {
String coordinates = "34.702239, 135.495129"; // 大阪・梅田
String response = this.api.request(API_ID, coordinates);
return response;
}
@Override
protected void onPostExecute(String response) {
System.out.println(response);
}
}
実行結果
コンソール出力結果
<?xml version="1.0" encoding="UTF-8"?>
<YDF xmlns="http://olp.yahooapis.jp/ydf/1.0" firstResultPosition="1" totalResultsAvailable="1" totalResultsReturned="1">
<ResultInfo>
<Count>1</Count>
<Total>1</Total>
<Start>1</Start>
<Status>200</Status>
<Latency>0.003393</Latency>
<Description></Description>
<Copyright>(C) Yahoo Japan Corporation.</Copyright>
</ResultInfo>
<Feature>
<Id>201412092310_34.702239_135.49513</Id>
<Name>地点(34.702239,135.49513)の2014年12月09日 23時10分から60分間の天気情報</Name>
<Geometry>
<Type>point</Type>
<Coordinates>34.702239,135.49513</Coordinates>
</Geometry>
<Property>
<WeatherAreaCode />
<WeatherList>
<Weather>
<Type>observation</Type>
<Date>201412092310</Date>
<Rainfall />
</Weather>
<Weather>
<Type>forecast</Type>
<Date>201412092320</Date>
<Rainfall />
</Weather>
(以下略)
説明
エンドポイントの実装
- インターフェースを作成して、アクセスする API のエンドポイントの情報を定義する。
-
@Endpoint
アノテーションで各 API のベースとなる URI を指定する。 -
@GET
など HTTP メソッドに対応するアノテーションで、インターフェース上に定義したメソッドをアノテートする。- 今回は指定していないが、引数でパスを指定可能。
- パスには
{user}
のようにパスパラメータを定義することもできる。 - その場合、引数で
@PathParam("user")
という感じでアノテーションを使う。
- 引数にリクエストに設定したいパラメータの情報などをアノテーションを使って定義する。
-
@QueryParam
は、クエリパラメータを定義している。
-
エンドポイントに指定したインターフェースのインスタンスを取得する
- エンドポイントをインスタンスフィールドで宣言して、
@Bite
アノテーションでアノテートする。 - インスタンス初期化ブロック内で、
Zombie.infect(this)
すると、@Bite
でアノテートしたフィールドにインスタンスがインジェクションされる。
HTTP リクエストを実行するときは、 AsyncTask を継承したクラスを使用する
- Android 3.0 以降は、メインスレッド内で HTTP リクエストを実行すると
NetworkOnMainThreadException
がスローされる。 - これを回避するには、
AsyncTask
を継承したクラスを用意し、doInBackground()
メソッドをオーバーライドしてその中でリクエスト処理を記述するようにしなければならない。 -
AsyncTask
には型引数が3つある。- 1つ目は、
doInBackground()
に渡す引数の型。 - 2つ目は、非同期処理中にコールバックされる
onProgressUpdate()
が受け取る引数の型。 - 3つ目は、
doInBackground()
の戻り値の型および、処理完了時にコールバックされるonPostExecute()
メソッドの引数の型。
- 1つ目は、
- 処理を実行するには、
AsyncTask
のexecute()
メソッドを実行する。
レスポンスを POJO にマッピングする
依存関係の追加
build.gradle
compile('org.simpleframework:simple-xml:2.7.1') {
exclude module: 'stax'
exclude module: 'stax-api'
exclude module: 'xpp3'
}
- XML のパースに Simple-XML という別のライブラリを使用する。
- Simple-XML が依存する他のライブラリをそのまま入れると、 Android のビルド時に
conversion to dalvik format failed with error 1
というエラーが出てビルドできなくなる。 - Gradle で依存関係を解決するときに以下のエラーが出るので、どうも Android が内部で持っている同様のライブラリと競合して動かなくなっている?
WARNING: Dependency xpp3:xpp3:1.1.3.3 is ignored for debug as it may be conflicting with the internal version provided by Android.
マッピング用の POJO クラスを作成する
package com.example.android;
import java.util.List;
import org.simpleframework.xml.Attribute;
import org.simpleframework.xml.Element;
import org.simpleframework.xml.ElementList;
public class YahooWeatherApiResponse {
@Attribute
private int firstResultPosition;
@Attribute
private int totalResultsAvailable;
@Attribute
private int totalResultsReturned;
@Element(name="ResultInfo")
private ResultInfo resultInfo;
@Element(name="Feature")
private Feature feature;
private static class ResultInfo {
@Element(name="Count")
private int count;
@Element(name="Total")
private int total;
@Element(name="Start")
private int start;
@Element(name="Status")
private int status;
@Element(name="Latency")
private double latency;
@Element(name="Description", required=false)
private String description;
@Element(name="Copyright")
private String copyright;
@Override
public String toString() {
return "ResultInfo [count=" + count + ", total=" + total + ", start=" + start + ", status=" + status + ", latency=" + latency + ", description=" + description + ", copyright=" + copyright + "]";
}
}
private static class Feature {
@Element(name="Id")
private String id;
@Element(name="Name")
private String name;
@Element(name="Geometry")
private Geometry geometry;
@Element(name="Property")
private Property property;
@Override
public String toString() {
return "Feature [id=" + id + ", name=" + name + ", geometry=" + geometry + ", property=" + property + "]";
}
}
private static class Geometry {
@Element(name="Type")
private String type;
@Element(name="Coordinates")
private String coordinates;
@Override
public String toString() {
return "Geometry [type=" + type + ", coordinates=" + coordinates + "]";
}
}
private static class Property {
@Element(name="WeatherAreaCode")
private WeatherAreaCode weatherAreaCode;
@ElementList(name="WeatherList")
private List<Weather> weatherList;
@Override
public String toString() {
return "Property [weatherAreaCode=" + weatherAreaCode + ", weatherList=" + weatherList + "]";
}
}
private static class WeatherAreaCode {
}
private static class Weather {
@Element(name="Type")
private String type;
@Element(name="Date")
private String date;
@Element(name="Rainfall", required=false)
private String rainfall;
@Override
public String toString() {
return "Weather [type=" + type + ", date=" + date + ", rainfall=" + rainfall + "]";
}
}
@Override
public String toString() {
return "YahooWeatherApiResponse [firstResultPosition=" + firstResultPosition + ", totalResultsAvailable=" + totalResultsAvailable + ", totalResultsReturned=" + totalResultsReturned + ", resultInfo=" + resultInfo + ", feature=" + feature + "]";
}
}
- アノテーションで属性や要素のマッピングを定義している。
- Simple を謳っているだけあって、使い方は感覚で分かると思う。
エンドポイントの定義を修正
YahooWeatherApi.java
package com.example.android;
import static com.lonepulse.robozombie.annotation.Entity.ContentType.*;
import com.lonepulse.robozombie.annotation.Deserialize;
import com.lonepulse.robozombie.annotation.Endpoint;
import com.lonepulse.robozombie.annotation.GET;
import com.lonepulse.robozombie.annotation.QueryParam;
@Deserialize(XML)
@Endpoint("http://weather.olp.yahooapis.jp/v1/place")
public interface YahooWeatherApi {
@GET
YahooWeatherApiResponse request(@QueryParam("appid") String appid,
@QueryParam("coordinates") String coordinates);
}
-
@Deserialize
アノテーションを追加して、 XML でデシリアライズすることを定義している。
リクエストを送信するクラスを若干修正する
package com.example.android;
import android.os.AsyncTask;
import com.lonepulse.robozombie.annotation.Bite;
import com.lonepulse.robozombie.proxy.Zombie;
public class ApiService extends AsyncTask<Void, Void, YahooWeatherApiResponse> {
private static final String API_ID = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
@Bite
private YahooWeatherApi api;
{
Zombie.infect(this);
}
@Override
protected YahooWeatherApiResponse doInBackground(Void... params) {
String coordinates = "34.702239, 135.495129"; // 大阪・梅田
YahooWeatherApiResponse response = this.api.request(API_ID, coordinates);
return response;
}
@Override
protected void onPostExecute(YahooWeatherApiResponse response) {
System.out.println(response);
}
}
- 型引数を POJO クラスに変更している。
実行結果
YahooWeatherApiResponse [firstResultPosition=1, totalResultsAvailable=1, totalResultsReturned=1, resultInfo=ResultInfo [count=1, total=1, start=1, status=200, latency=0.003112, description=null, copyright=(C) Yahoo Japan Corporation.], feature=Feature [id=201412100005_34.702239_135.49513, name=地点(34.702239,135.49513)の2014年12月10日 00時05分から60分間の天気情報, geometry=Geometry [type=point, coordinates=34.702239,135.49513], property=Property [weatherAreaCode=com.example.android.YahooWeatherApiResponse$WeatherAreaCode@41e8b398, weatherList=[Weather [type=observation, date=201412100005, rainfall=null], Weather [type=forecast, date=201412100015, rainfall=null], Weather [type=forecast, date=201412100025, rainfall=null], Weather [type=forecast, date=201412100035, rainfall=null], Weather [type=forecast, date=201412100045, rainfall=null], Weather [type=forecast, date=201412100055, rainfall=null], Weather [type=forecast, date=201412100105, rainfall=null]]]]]
なんとか POJO クラスにマッピングできた。