RoboZombieでYahoo!の気象情報APIを使う方法メモ

Android 用の REST クライアントライブラリである RoboZombie を使って、 Yahoo! が提供している気象情報 API にアクセスしてみたときのメモ。

#前説
##RoboZombie
Android 用の REST クライアントライブラリ。

[sahan/RoboZombie | GitHub](https://github.com/sahan/RoboZombie)

##Yahoo! の気象情報API
[YOLP(地図):気象情報API - Yahoo!デベロッパーネットワーク](http://developer.yahoo.co.jp/webapi/map/openlocalplatform/v1/weather.html#response)

場所と時間を渡したら、降水量とかを返してくれる Web API。

#準備
##Yahoo! のアプリケーション ID を取得する
###Yahoo! Japan ID を取得する
https://account.edit.yahoo.co.jp/registration

###アプリケーション情報を登録する
https://e.developer.yahoo.co.jp/register

必要事項を入力して登録したら ID が発行されるので、それを控える。

登録した ID については [Yahoo!デベロッパーネットワーク](http://developer.yahoo.co.jp/) の「アプリケーションの管理」から確認できる。

#実装
##依存関係
```groovy:build.gradle
dependencies {
    compile 'com.lonepulse:robozombie:1.3.3'
}
```

##パーミッションの追加
ネットワークアクセスが必要なので、パーミッションを追加する。

```xml:AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET" />
```

##エンドポイントの定義
```java: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 を実行するクラスの作成
```java: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:コンソール出力結果
<?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` には型引数が３つある。
    - １つ目は、 `doInBackground()` に渡す引数の型。
    - ２つ目は、非同期処理中にコールバックされる `onProgressUpdate()` が受け取る引数の型。
    - ３つ目は、 `doInBackground()` の戻り値の型および、処理完了時にコールバックされる `onPostExecute()` メソッドの引数の型。
- 処理を実行するには、 `AsyncTask` の `execute()` メソッドを実行する。

#レスポンスを POJO にマッピングする
##依存関係の追加
```groovy:build.gradle
compile('org.simpleframework:simple-xml:2.7.1') {
    exclude module: 'stax'
    exclude module: 'stax-api'
    exclude module: 'xpp3'
}
```

- XML のパースに [Simple-XML](http://simple.sourceforge.net/home.php) という別のライブラリを使用する。
- 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 クラスを作成する
```java:
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 を謳っているだけあって、使い方は感覚で分かると思う。

##エンドポイントの定義を修正
```java: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 でデシリアライズすることを定義している。

##リクエストを送信するクラスを若干修正する
```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, 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 クラスに変更している。

##実行結果
```text:
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 クラスにマッピングできた。

#参考
- [【Android】メインスレッド(要は画面処理)でhttpリクエスト投げようとしたら怒られた。　〜NetworkOnMainThreadException〜 - 記すに足らず。](http://shirusu-ni-tarazu.hatenablog.jp/entry/2013/01/20/033030)
- [Using SimpleXML with Android and Gradle - Stack Overflow](http://stackoverflow.com/questions/18084285/using-simplexml-with-android-and-gradle)
- [イマドキなイカした Android のオープンソースライブラリ集 - Qiita](http://qiita.com/KeithYokoma/items/fb6872a72a75e9b6f2e6)