OkHTTP + Retrofit + RxAndroid で RESTを試してみました

#はじめに
AndroidのRESTクライアントのネットワーク処理と非同期処理について、Volley + Gsonの組み合わせでAsyncTask or AsyncLoaderで別スレッドを作って通信して結果を取得してJSONをパースしていたりしていましたが今の流行りはOkHTTP + retorofit + RxAndroidのようなので試してみたいと思います。

#本記事のゴール
無料のlivedoor 天気情報APIを使ってデータを受け取る部分までをやりたいと思います。

APIの詳細は以下をご確認ください
http://weather.livedoor.com/weather_hacks/

#環境
- Android studio 1.4
- OkHTTP:HTTP クライアント用のライブラリ
- Retrofit:REST クライアント用のライブラリ
- RxAndroid:リアクティブプログラミングを Android で実装するためのライブラリ
- Gson:Googleが提供するJSONデータとJavaオブジェクトを相互に変換するためのライブラリ

#導入してみよう
ライブラリを Androidプロジェクトに導入する。

```ruby:Commnad
dependencies {
　　 compile 'com.squareup.okhttp:okhttp:2.5.0''
    compile 'com.squareup.retrofit:retrofit:1.9.0'
    compile 'io.reactivex:rxandroid:0.24.0'
    compile 'com.google.code.gson:gson:2.2.4'
}

```

#実装
JSONをGETしてくるサンプルを実装したいと思います。

##Entityを作成する
天気予報情報を保持するEntityクラスを作成します。
※天気予報以外の情報については割愛します。

```ruby:天気予報APIの結果で返却されるJSON
http://weather.livedoor.com/forecast/webservice/json/v1?city=200010

{
    "pinpointLocations": [
        {
            "link": "http://weather.livedoor.com/area/forecast/2020100",
            "name": "長野市"
        },
        {
            "link": "http://weather.livedoor.com/area/forecast/2020700",
            "name": "須坂市"
        },
        {
            "link": "http://weather.livedoor.com/area/forecast/2021100",
            "name": "中野市"
        },
        {
            "link": "http://weather.livedoor.com/area/forecast/2021200",
            "name": "大町市"
        },
        {
            "link": "http://weather.livedoor.com/area/forecast/2021300",
            "name": "飯山市"
        },
        {
            "link": "http://weather.livedoor.com/area/forecast/2021800",
            "name": "千曲市"
        },
        {
            "link": "http://weather.livedoor.com/area/forecast/2048100",
            "name": "池田町"
        },
        {
            "link": "http://weather.livedoor.com/area/forecast/2048200",
            "name": "松川村"
        },
        {
            "link": "http://weather.livedoor.com/area/forecast/2048500",
            "name": "白馬村"
        },
        {
            "link": "http://weather.livedoor.com/area/forecast/2048600",
            "name": "小谷村"
        },
        {
            "link": "http://weather.livedoor.com/area/forecast/2052100",
            "name": "坂城町"
        },
        {
            "link": "http://weather.livedoor.com/area/forecast/2054100",
            "name": "小布施町"
        },
        {
            "link": "http://weather.livedoor.com/area/forecast/2054300",
            "name": "高山村"
        },
        {
            "link": "http://weather.livedoor.com/area/forecast/2056100",
            "name": "山ノ内町"
        },
        {
            "link": "http://weather.livedoor.com/area/forecast/2056200",
            "name": "木島平村"
        },
        {
            "link": "http://weather.livedoor.com/area/forecast/2056300",
            "name": "野沢温泉村"
        },
        {
            "link": "http://weather.livedoor.com/area/forecast/2058300",
            "name": "信濃町"
        },
        {
            "link": "http://weather.livedoor.com/area/forecast/2058800",
            "name": "小川村"
        },
        {
            "link": "http://weather.livedoor.com/area/forecast/2059000",
            "name": "飯綱町"
        },
        {
            "link": "http://weather.livedoor.com/area/forecast/2060200",
            "name": "栄村"
        }
    ],
    "link": "http://weather.livedoor.com/area/forecast/200010",
    "forecasts": [
        {
            "dateLabel": "今日",
            "telop": "晴れ",
            "date": "2015-11-06",
            "temperature": {
                "min": {
                    "celsius": "4",
                    "fahrenheit": "39.2"
                },
                "max": {
                    "celsius": "20",
                    "fahrenheit": "68.0"
                }
            },
            "image": {
                "width": 50,
                "url": "http://weather.livedoor.com/img/icon/1.gif",
                "title": "晴れ",
                "height": 31
            }
        },
        {
            "dateLabel": "明日",
            "telop": "曇時々晴",
            "date": "2015-11-07",
            "temperature": {
                "min": null,
                "max": null
            },
            "image": {
                "width": 50,
                "url": "http://weather.livedoor.com/img/icon/9.gif",
                "title": "曇時々晴",
                "height": 31
            }
        }
    ],
    "location": {
        "city": "長野",
        "area": "信越・北陸",
        "prefecture": "長野県"
    },
    "publicTime": "2015-11-05T17:00:00+0900",
    "copyright": {
        "provider": [
            {
                "link": "http://tenki.jp/",
                "name": "日本気象協会"
            }
        ],
        "link": "http://weather.livedoor.com/",
        "title": "(C) LINE Corporation",
        "image": {
            "width": 118,
            "link": "http://weather.livedoor.com/",
            "url": "http://weather.livedoor.com/img/cmn/livedoor.gif",
            "title": "livedoor 天気情報",
            "height": 26
        }
    },
    "title": "長野県 長野 の天気",
    "description": {
        "text": " 日本付近は、広く高気圧に覆われています。\n\n 長野県内は、晴れまたは薄曇りとなっています。\n\n 5日は、高気圧に覆われる見込みです。\n このため、おおむね晴れるでしょう。\n\n 6日は、日中は高気圧に覆われますが、気圧の谷や湿った空気の影響を受\nける見込みです。\n このため、北部では、晴れで夜は曇りとなるでしょう。中部と南部では、\n晴れで朝晩は曇りとなるでしょう。\n\n<天気変化等の留意点>\n 5日18時から6日18時までの24時間に予想される降水量は、多い所\nで、北部で0ミリ、中部で0ミリ、南部で0ミリの見込みです。",
        "publicTime": "2015-11-05T16:39:00+0900"
    }
}
```

```ruby:WeatherEntity.java
public class WeatherEntity {

        @Expose
    @SerializedName("pinpointLocations")
    private List<PinpointLocations> pinpointLocations;

    @Expose
    @SerializedName("forecasts")
    private List<Forecasts> forecasts;

    @Expose
    @SerializedName("link")
    private String link;

    public List<Forecasts> getForecasts() {
        return forecasts;
    }

    public void setForecasts(List<Forecasts> forecasts) {
        this.forecasts = forecasts;
    }

    public String getLink() {
        return link;
    }

    public void setLink(String link) {
        this.link = link;
    }

    public List<PinpointLocations> getPinpointLocations() {
        return pinpointLocations;
    }

    public void setPinpointLocations(List<PinpointLocations> pinpointLocations) {
        this.pinpointLocations = pinpointLocations;
    }
}
```

```PinpointLocations.java
public class PinpointLocations{

    @Expose
    @SerializedName("link")
    private String link;

    @Expose
    @SerializedName("name")
    private String name;

    public String getLink() {
        return link;
    }

    public void setLink(String link) {
        this.link = link;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
```

##REST インターフェースを作成する

```ruby:WeatherApi.java
package com.example.kazuhiro.restsample.api;

import com.example.kazuhiro.restsample.entity.WeatherEntity;
import retrofit.http.GET;
import retrofit.http.Query;

import rx.Observable;

/**
 * Created by kazuhiro on 15/11/08.
 */
public interface WeatherApi {

    @GET("/forecast/webservice/json/v1")
    public Observable<WeatherEntity> getWeather(@Query("city") final String city);
}

```

##非同期で通信を行う
実際に通信して、結果データを返却するまでの処理は以下のようになります。
画面の表示については今回割愛します。

```ruby:MainActivity.java
package com.example.kazuhiro.restsample.activity;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;

import com.example.kazuhiro.restsample.api.WeatherApi;
import com.example.kazuhiro.restsample.entity.WeatherEntity;
import com.example.kazuhiro.restsample.R;
import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.internal.bind.DateTypeAdapter;

import java.util.Date;

import retrofit.RestAdapter;
import retrofit.android.AndroidLog;
import retrofit.converter.GsonConverter;
import rx.Observer;
import rx.android.schedulers.AndroidSchedulers;
import rx.schedulers.Schedulers;

public class MainActivity extends AppCompatActivity {

    private static final String END_POINT = "http://weather.livedoor.com";

    private static final String TAG = MainActivity.class.getSimpleName();


    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);


        Gson gson = new GsonBuilder()
                .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
                .registerTypeAdapter(Date.class, new DateTypeAdapter())
                .create();

        // RestAdapterを作成する
        RestAdapter adapter = new RestAdapter.Builder()
                .setEndpoint(END_POINT)
                .setConverter(new GsonConverter(gson))
                .setLogLevel(RestAdapter.LogLevel.FULL)
                .setLog(new AndroidLog("=NETWORK="))
                .build();

        // 天気予報情報を取得する
        //http://weather.livedoor.com/area/forecast/200010
        WeatherApi api =  adapter.create(WeatherApi.class);

        Observer observer = new Observer<WeatherEntity>() {
            @Override
            public void onCompleted() {
                Log.d(TAG, "onCompleted()");
                //必要な情報を取り出して画面に表示してください。
            }

            @Override
            public void onError(Throwable e) {
                Log.e(TAG, "Error : " + e.toString());
            }

            @Override
            public void onNext(WeatherEntity weather) {
                Log.d(TAG, "onNext()");
            }
        };

        api.getWeather("200010")
                .subscribeOn(Schedulers.newThread())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(observer);
    }
}

```

#まとめ
非常にシンプルに処理をかけました。今後新規にサーバーとの通信を行うようなアプリでは
採用して見てはいかがでしょうか。