ニコニコの検索APIをRetrofit2で書いてみる

#はじめに
[ニコニコのスナップショット検索API v2](http://site.nicovideo.jp/search-api-docs/snapshot.html)をこれまではOkHttpで直接getしてJSONに変換して...とやっていましたが、コードの複雑さにようやくRetrofitを使う決心をしました。
その手順を備忘録的に書きます。

#1. データ用のObjectを作成
検索結果(json)をパースするため、データ用のObjectを作成します。
サンプルデータから手動で作成するのはあまりにも時間がかかるので、
[jsonschema2pojo](http://www.jsonschema2pojo.org/)を使いました。

http://api.search.nicovideo.jp/api/v2/illust/contents/search?q=%E3%83%87%E3%82%A4%E3%83%AA%E3%83%BC%E3%83%91%E3%83%B3%E3%83%84&targets=tags&fields=contentId,title,description,tags,categoryTags,viewCounter,mylistCounter,commentCounter,startTime,thumbnailUrl&_sort=viewCounter&_context=apiguide (APIガイドより)にアクセスすると、

```json:a
{  
   "meta":{  
      "status":200,
      "totalCount":130,
      "id":"c5f7ed94-e188-4fff-bf72-1b1323e7d2e6"
   },
   "data":[  
      {  
         "startTime":"2015-06-18T23:03:57+09:00",
         "description":"「本当に君たちには失望したよ…この3日間、寄って集って20万回も女の子のスカートの中を覗きにくるだなんてね…」　目指すのは100万( ˘ω˘ )",
         "tags":"艦これ 艦隊これくしょん 夕立（艦これ） 時雨（艦これ） デイリーパンツ 静画殿堂入り いいスジしてる 伝説の始まり パンツ！パンツです！",
         "mylistCounter":12738,
         "categoryTags":"艦これ",
         "viewCounter":823962,
         "contentId":"im4980983",
         "title":"君には失望したよ…",
         "commentCounter":13557,
         "thumbnailUrl":"http://lohas.nicoseiga.jp/thumb/4980983z"
      }
      //...
    ]
}
```

上のような結果が返ってきます。
これを[jsonschema2pojo](http://www.jsonschema2pojo.org/)に貼り付けます。
この時の注意:
1. source typeはJSON SchemaではなくJSONを選択
2. annotation styleを使っているJSONライブラリによって変える(筆者はGSONを選択)
3. 
![SnapCrab_NoName_2017-5-20_20-23-48_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/130720/08919c47-20e2-a109-ab98-bc3fbede34db.png)

そうするとPOJOが生成されるので、classとして作成しましょう。

```java:SearchResult.java
public class SearchResult {

    @SerializedName("meta")
    @Expose
    private SearchResultMeta meta;
    @SerializedName("data")
    @Expose
    private List<SearchItem> data = null;

    public SearchResultMeta getMeta() {
        return meta;
    }

    public void setSearchResultMeta(SearchResultMeta meta) {
        this.meta = meta;
    }

    public List<SearchItem> getData() {
        return data;
    }

    public void setData(List<SearchItem> data) {
        this.data = data;
    }

    //検索結果のアイテム
    public static class SearchItem {

        @SeriaizedName("startTime")
        @Expose
        private String startTime;
        @SerializedName("description")
        @Expose
        private String description;
        @SerializedName("tags")
        @Expose
        private String tags;
        @SerializedName("mylistCounter")
        @Expose
        private Integer mylistCounter;
        @SerializedName("categoryTags")
        @Expose
        private String categoryTags;
        @SerializedName("viewCounter")
        @Expose
        private Integer viewCounter;
        @SerializedName("contentId")
        @Expose
        private String contentId;
        @SerializedName("title")
        @Expose
        private String title;
        @SerializedName("commentCounter")
        @Expose
        private Integer commentCounter;
        @SerializedName("thumbnailUrl")
        @Expose
        private String thumbnailUrl;


        //getter, setterなど
    }

    //メタデータ
    public static class SearchResultMeta {

        @SerializedName("status")
        @Expose
        private Integer status;
        @SerializedName("totalCount")
        @Expose
        private Integer totalCount;
        @SerializedName("id")
        @Expose
        private String id;

        //getter, setterなど
    }
}
```

#2. Interfaceを作成
```java:NiconicoSearchApiInterface.java
public interface NiconicoSearchApiInterface {
    String APP_CONTEXT = "nicopix";
    String END_POINT = "http://api.search.nicovideo.jp/";

    //検索したいサービスによってURLを変える
    @GET("api/v2/illust/contents/search")
    Call<SearchResult> getSearchResult(
            @Query("q") String query,
            @Query("targets") String targets,
            @Query("fields") String fields,
            @Query("filters") String filters,
            @Query("_sort") String _sort,
            @Query("_offset") Integer _offset,
            @Query("_limit") Integer _limit,
            @Query("_context") String _context);

}
```
パラメータは(http://site.nicovideo.jp/search-api-docs/snapshot.html) に基づいています。
上はニコニコ静画の場合ですが、違うサービスの場合はURLを変えておきましょう。
APP_CONTEXTには自分のアプリ名など入れておけば良さそうです(なんだかよくわかってない)。

#3. リクエストを作成->実行
毎回Serviceを作成して実行してもいいのですが、パラメータが多いため、SearchUtils.searchでパラメータを減らすようにしました。

```java:SearchUtils.java
public static Call<SearchResult> search(SearchType searchType, String query, Sort sort, int offset, int limit) {
    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(NiconicoSearchApiInterface.END_POINT)
            .addConverterFactory(GsonConverterFactory.create())
            .build();
    NiconicoSearchApiInterface service = retrofit.create(NiconicoSearchApiInterface.class);


    String targets = "";
    switch (searchType) {
        case KEYWORD:
            targets = "title,description,tags";
            query = query.replaceAll("\\s+", "+");
            break;
        case TAG:
            targets = "tags";
            query = "\"" + query + "\"";
            break;
    }
    List<String> fields = new ArrayList<>();
    fields.add("title");
    fields.add("description");
    fields.add("contentId");
    fields.add("viewCounter");
    fields.add("mylistCounter");
    fields.add("commentCounter");
    fields.add("startTime");
    return service.getSearchResult(
            query,
            targets,
            android.text.TextUtils.join(",", fields),
            null /*filterは今回使わないので省略*/,
            sort.getValue(),
            offset,
            limit,
            NiconicoSearchApiInterface.APP_CONTEXT);
}
```

```java:SearchType.java
public enum SearchType {
    KEYWORD("キーワード検索"),
    TAG("タグ検索");
    
    private final String description;

    SearchType(String description) {
        this.description = description;
    }

    public static List<String> getDescriptions() {
        List<String> descriptionsList = new ArrayList<>();
        for (SearchType searchType : values()) {
            descriptionsList.add(searchType.getDescription());
        }
        return descriptionsList;
    }

    public static SearchType valueOfDescription(String description) {
        for (SearchType searchType : values()) {
            if (description.equals(searchType.getDescription())) {
                return searchType;
            }
        }
        throw new IllegalArgumentException("no such enum object for the description: " + description);
    }

    public String getDescription() {
        return this.description;
    }
}
```

```java:Sort.java

public enum Sort {
    VIEW_COUNT_MINUS("閲覧数が多い順", "-viewCounter"),
    VIEW_COUNT_PLUS("閲覧数が多い順", "%2BviewCounter"),
    COMMENT_COUNT_MINUS("コメント数が多い順", "-commentCounter"),
    COMMENT_COUNT_PLUS("コメント数が少ない順", "%2BcommentCounter"),
    LAST_COMMENT_TIME_MINUS("コメントが新しい順", "-lastCommentTime"),
    LAST_COMMENT_TIME_PLUS("コメントが古い順", "%2BlastCommentTime"),
    MYLIST_COUNT_MINUS("お気に入り数が多い順", "-mylistCounter"),
    MYLIST_COUNT_PLUS("お気に入り数が少ない順", "%2BmylistCounter"),
    START_TIME_MINUS("投稿が新しい順", "-startTime"),
    START_TIME_PLUS("投稿が古い順", "%2BstartTime");
    
    private final String description;
    private final String value;

    Sort(String description, String value) {
        this.description = description;
        this.value = value;
    }
    //...
    public String getValue() {
        return this.value;
    }
    //...
}
```

あとはsearchメソッドを実行するだけでresponce.body.getData()からSearchItemのリストが取得できます！

```java
SearchUtils.search(SearchType.KEYWORD, query, sort, page * 100, 100/* 一度に取得できるアイテムは100まで */)
        .enqueue(new Callback<SearchResult>() {
            @Override
            public void onResponse(Call<SearchResult> call, Response<SearchResult> response) {
                SearchResult body = response.body();
                if (body != null) {
                    List<SearchResult.SearchItem> data = body.getData();
                    //楽！！！
                }
            }

            @Override
            public void onFailure(Call<SearchResult> call, Throwable t) {
            }
        });
```
#おわりに
これまでretrofitはよくわからず避けていましたが、使ってみるとかなり楽に使えました。
一番面倒だとおもっていたObject作成ですが、一瞬でした...  jsonschama(ry凄い...
ちなみに初記事なので、不備等ありましたらコメントでどうぞm(_ _)m