超簡易的なandroidアプリ開発のレベルから一歩先へ行くための勉強として、Hotpepperのapiを使って雑なクライアントアプリの開発をしてみる。今回はその2。
#天気アプリを読み解く!
天気アプリは完成!(≧∇≦)/ってなってるけどこれって結局ただ人のソース丸パクリしただけで結局本質は理解できてない…
ということで、この天気アプリを参考にしながら本命のHotpepperの食べログ的なアプリを作る方にシフトしていく。
まずはなんとなくで天気アプリのコードをHotpepper側に貼り付けて変数名とか変えてみても全くうまくいかない。
まあそりゃそうだってかんじだ。ということで、天気アプリをちゃんと読み解いて理解してからじゃないと作れないっぽいということがわかった。
##MainActivityを見てみる
まずはMainActivityのほうを追って見る。書いてる部分は
private static final String API = "http://api.openweathermap.org/data/2.5";
private static final float LAT = 35.681382f;
private static final float LON = 139.766084f;
RestAdapter adapter = new RestAdapter.Builder()
.setEndpoint(API)
.build();
WeatherApi api = adapter.create(WeatherApi.class);
api.getWeather(LAT, LON, new Callback<WeatherData>() {
@Override
public void success(WeatherData weatherData, Response response) {
for (Weather w : weatherData.getWeather()) {
System.out.println("Weather Description : " + w.getDescription());
}
}
@Override
public void failure(RetrofitError error) {
}
});
こんな感じで追記している。ノリとしては、LATとLONって言うのが緯度と経度なのかな。それを突っ込んでデータを引っ張ってきている感じがする。
細かく見ていくと、まずRestAdapterってものを知らない。調べてみるとそもそもいまやろうとしていることがREST通信というものらしい。IT用語辞典によると、
一般によく使われる狭義のRESTは、パラメータを指定して特定のURLにHTTPでアクセスすると、XMLで記述されたメッセージが送られてくるようなシステムおよび呼び出しインターフェース(「RESTful API」と呼ばれる)のことを指す。システムの状態やセッションに依存せず、同じURLやパラメータの組み合わせからは常に同じ結果が返されることが期待される。ただし、厳密な技術的定義が共有されているわけではなく、「SOAPやRPCなどを必要としない、XMLベースの単純なWebインターフェース」くらいの意味で用いられる場合が多い。
とのこと。URLを指定してデータ持ってくるようなもののよう。Web周りにも疎いので、まあそういうものがあるんだ、とおぼえておくことにした。
で、それをつないでくれるのがRestAdapterらしい。(雑)
setEndpointというメソッドの引数にAPIのリクエストURLをつこんでおけば、まあとりあえずうまくやってくれるらしい。
次にWeatherApiクラスの変数が宣言される。WeatherApiはインタフェイスとして実装する必要があるらしい。確かに言われてみれば、サイトに言われるがままに書いてた。ここがどうやらweb上のapiを使う上で肝になるみたい。一旦そっちに注目
##WeatherApiインターフェイスについて
Javaのインターフェイスについてはここでは割愛。他のひとの記事を読む方が絶対わかりやすい。
ここでソースを見てみると
public interface WeatherApi {
@GET("/weather")
public void getWeather(@Query("lat") float lat, @Query("lon") float lon, Callback<WeatherData> response);
}
となっている。ここで気になる、@が付いているやつ。昔Java勉強した時にやった気がする。だめだ、忘れた。
ということでとりあえずgoogle先生に相談。アノテーションというらしい。javaではこれが大変便利で重宝されてるらしい。また無知を再確認。
雑な感じで紹介すると、@GETはweb情報をURLをたよりにgetしてくるぜ!みたいなもの。ただしMainActivity.javaで書いたAPIのURLの部分は書く必要がない。ブラウザで確認するjsonを生成するURLを確認するとわかるが、リクエストURL以降の謎の'?'が出てくるまでの部分を括弧内に書く。これをリクエストのホスト部以降って言うらしい。わからん人にはわからんシリーズだわこれも。
そしてインターフェイス内でメソッドが用意されている。これを呼ぶことでjsonとしてデータをwebから引っ張ってきて、それをjavaの形に成形する、みたいな処理をしているっぽい。
@QUERYはそのまんま。検索クエリが括弧内に入る。そのクエリを引数として、CallBack Responseってことをするらしい。CallBackってなんだろ。わかんね。
雑にいえば、そのjsonのデータをWeatherDataクラスのインスタンスフィールドとして持たせることができるっぽい。カタカナ多いと何言ってるのか分からなくなるね。つらい。
##再びMainActivityを考える
ここでまたMainActivityを見ると、WeatherApiのapiを宣言して、それをさっき話題に上がったメソッドに突っ込む。jsonがうまくjavaになったらsuccessメソッドが呼ばれて、うまくいかんかったらfailureメソッドにいくみたい。というか、そうなっててくれ。頼む。
successメソッドの中身を見てみると、拡張for文使ってる。jsonから生成されるのがWeatherDataクラスのインスタンスで、そこから受け取れる天気(Weatherクラスのインスタンス)はリストになってるらしい。ココらへんは全部jsonschema2pojoがやってくれたのを、あとから**ああ〜〜なるほどね〜〜**って言いながら見返すとわかる。はず。リストのfor文ってこうやって書けるんだねぇ〜とまたここで少し賢くなった。
このfor文の中がSystem.out.println(...);
ってなっていたので適当にtextViewに表示させるようにしてみた。これでひとしきり天気アプリからHotpepperクライアントアプリを作るための知識は身についた…きがする。
#ラスボス、Hotpepperクライアントアプリとの対峙
準備は整った。そもそも、最初からラスボスに挑もうとしてたのが馬鹿だったって話だ。ここまで武器(=やっつけの知識)が揃えば、倒せるはず。いくぞ!
##まずはapiリファレンスのサンプルクエリからjson生成
懐かしのapiリファレンスから、jsonを召喚!
ででーん
で、こいつをjsonschema2pojoに貼り付ける、と
コピーして貼り付けると、全部一行なんでこんなかんじで横にめちゃ長くなるね
あと、sourcetypeはjsonに、annotationstyleはNoneにし忘れるの、何回かやったので注意
からの、previewで
生成完了!後は気合でandroid studio上でjavaクラスをpreviewで出てくる分だけ作って張り付けていく!
こんな感じ。
天気の時より多いからめちゃ疲れた。
そこから、インターフェイスの実装に入る。
package com.example.hogehoge;
import retrofit.Callback;
import retrofit.http.GET;
import retrofit.http.Query;
@GET("/")
void getNew(@Query("key") String key, @Query("large_area") String large_area, @Query("format") String format, Callback<Pepper> response);
}
hotpepperのapiではリクエストのホスト以降の部分がないので、@GETの括弧内はスラッシュのみ。あとはAPIリファレンスを見ながら、今回は一個目のサンプルクエリをそのまま再現してみる方針で、実装してみる。
ちなみにapp内のbuild.gradeのdependenciesはこんな感じのものを追加している。
compile 'com.squareup.retrofit:retrofit:1.6.1'
compile 'javax.annotation:jsr250-api:1.0'
したのやつは、アノテーションを使うのに必要みたいで、これがないと@GETとかが使えない。
##最後の最後で
ここまでうまくいった。やれることはやった。だけど僕は重要なことを忘れていた。
**"アプリのデザイン"**だ。
これを何も考えていなかったから、またここで作業は止まる。
さっきの天気アプリの時と同じように書いてしまうと、for文で最後に現れた要素のみがtextviewに表示される、というものになる。jsonのデータをみていると、店の情報というのは10件ほど取得できているのに。一つしか表示できない…?そんな悲しい事があって良いだろうか(いや、ない(反語))
というわけで、取得したすべての店の名前を動的に表示できる機能を実装しよう、というのが最終目標として決まった。
#動的なリストへの追加
ラスボスを倒す最後の手がかりを、ボクは探しに行った(googleで検索するなどして)。結構都合の良いものが見つかった。ListViewというもので、文字通り、リスト状のViewらしい。これがピッタリだと思い、早速実装へとはいる。
これでこと足りた。結構初歩的な内容だったらしく、すぐ実装できて一安心。
#これでMainActivityも完成!
ソースはこんな感じ。
package com.example.hogehoge;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
import retrofit.Callback;
import retrofit.RestAdapter;
import retrofit.RetrofitError;
import retrofit.client.Response;
public class MainActivity extends AppCompatActivity {
TextView textView;
ListView listView;
static List<String> dataList = new ArrayList<String>();
ArrayAdapter<String> adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView = (ListView)findViewById(R.id.listView);
adapter = new ArrayAdapter<String>( this, android.R.layout.simple_list_item_1, dataList);
listView.setAdapter(adapter);
RestAdapter restAdapter = new RestAdapter.Builder()
.setEndpoint("http://webservice.recruit.co.jp/hotpepper/gourmet/v1")
.build();
RetroFitApi service = restAdapter.create(RetroFitApi.class);
service.getNew("48df5459672d88dd", "Z011", "json", new Callback<Pepper>() {
@Override
public void success(Pepper pepper, Response response) {
for (Shop s : pepper.getResults().getShop()) {
dataList.add(s.getName());
}
}
@Override
public void failure(RetrofitError error) {
textView.setText(String.valueOf(error));
}
});
}
//以下省略
#感想
感想としては、やっぱりいい勉強になったなということ。知らないことだらけだし、今もちょっとブラックボックスのままみたいなぶぶんも多いし。機能としては大したことないけど、いろいろ勉強になったということでよしとしようって感じ。
あと、書くの大変。こういう記事書くのめちゃ時間かかるけど、どうせわすれるだろうから、それよりはくろうして、復習がてら書くのは良いなあと思った。