技術顧問の増田です。前回は、固定URLを使って RSS を取得してリストにして表示させました。今回は、もう少し発展させて Web API を使ってリストに表示するところ方法を解説します。
バックナンバー
- 第1回 C#で自作Android アプリを作ろう:出帆準備編
- 第2回 C#で自作Android アプリを作ろう:試験運用編
- 第3回 C#で自作Android アプリを作ろう:時計アプリを作る
- 第4回 C#で自作Android アプリを作ろう:RSSを取得してリスト表示
- 第5回 C#で自作Android アプリを作ろう:Web APIで路線情報を表示
- 第6回 C#で自作Android アプリを作ろう:Twitter APIでファボリストを取得
- 第7回 C#で自作Android アプリを作ろう:簡易アンケートを作ろう
- 第8回 C#で自作Android アプリを作ろう:カメラ機能を使おう
Web APIとは何か?
簡単に言えば、HTTP サーバーに、GETやPOSTで要求を出して、JSON形式やXML形式でデータを取ってくるのが Web API です。RESTfull になると、GET/POST/PUT/DELETE と要求を出すときの「メソッド」を分けるのですが、今回は GET だけを使います。と言いますか、このあたりは HTTP サーバーの都合で作られていることが多いので、そっちに合わせることになります。
社内で Web API を作るときには、きちんと切り分けたほうが Android 側の開発がスムースになるので、気を付けて設計をしていきましょう。
駅データ.jp を利用する
今回は、オープンデータとして提供されている「駅データ.jp」 ekidata.jp を使います。
路線 API を呼び出すと、路線の各駅のデータを取得することができます。ちなみに、路線ID 自体は、会員ログインをすると CSV 形式でダウンロードができるようになります。残念ながら新幹線は路線APIに対応していないので、通常の在来線なのですがJRだけじゃなくて地下鉄の駅データも取得できるので、そこそこ便利です。
まあ、駅自体はそんなに頻繁に変わるものではないので、適宜ダウンロードしながら使ってもいいんですが…それは Web APIの練習用に有り難く使わせていただくということで。
JSON形式で取得する
前回と同じように、いきなり Android から動かすのではなくてコンソールを使って実験していきます。サンプルコードは、http://github.com/moonmile/sg-xamarin-sample の sgStationData 内のプロジェクトを利用してください。
NuGet で Newtonsoft.Json をインストールする
.NET Framework には標準で JSON 形式を扱うクラスライブラリはありません…ありませんが、その手の必要なライブラリは大抵 NuGet で取得することができます。
ソリューションエクスプローラーで、プロジェクトを右クリックして「NuGetパッケージの管理」を選択すると、NuGet.org からクラスライブラリをダウンロードできる画面がでています。
ここで「json」で検索して「Newtonsoft.Json」をインストールします。JSON形式のデータをあれこれしてくれるライブラリです。ちなみに JSON は Javascript の連想配列の形式になっているので、ブラウザなどで扱うときに便利ですよね。大抵の Web API は JSON 形式で返すものが多いので、こっちに慣れておくと効率的に開発できます。
データクラスを作る
RSS の時と同じように、データを入れるだけのクラスを作ります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
public class JRLine { public int line_cd { get; set; } public string line_name { get; set; } public double line_lon { get; set; } public double line_lat { get; set; } public int line_zoom { get; set; } public List station_l { get; set; } public override string ToString() { return line_name; } } public class JRStation { public int station_cd { get; set; } public int station_g_cd { get; set; } public string station_name { get; set; } public double lon { get; set; } public double lat { get; set; } public override string ToString() { return station_name; } } |
このクラスも、最終的には ListView で表示しやすいように ToString メソッドをオーバーライドしておきます(実験しながらでもいいです)。プロパティ名が小文字になっているのは、JSON形式の名前のほうに合わせたためです。
コンソールで実験する
路線APIを使ってデータを取ってくるところが、こんな感じになります。試しに千歳線(11109)のコードを入れてデータを取ってきています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
class Program { static void Main(string[] args) { var prog = new Program(); var t = prog.Go(); t.Wait(); var data = t.Result; int i = 0; foreach( var it in data.station_l ) { i++; Console.WriteLine(string.Format("{0}: {1}", i, it.station_name)); } return; } public async Task Go() { var line_cd = 11109; // ① var url = $"http://www.ekidata.jp/api/l/{line_cd}.json"; // ② var hc = new HttpClient(); var res = await hc.GetAsync(url); // ③ var json = await res.Content.ReadAsStringAsync(); // ④ Console.WriteLine(json); // 余分なjavascriptを取り除く json = json.Replace("if(typeof(xml)=='undefined') xml = {};", ""); // ⑤ json = json.Replace("xml.data = ", ""); json = json.Replace("if(typeof(xml.onload)=='function') xml.onload(xml.data);", ""); var js = new Newtonsoft.Json.JsonSerializer(); // ⑥ var jr = new Newtonsoft.Json.JsonTextReader(new System.IO.StringReader(json)); var data = js.Deserialize (jr); // ⑦ return data; } } |
コードの解説をざっとしましょう。
- 路線コードを設定する。
- 路線APIの呼び出しは、最後に「.json」を付けて JSON 形式で読み込む。
- GetAsync メソッドで非同期呼び出し
- ReadAsStringAsync メソッドを使って、一気に文字列として取り込む。
- 何故か、路線APIの戻り値は無駄なJavascriptコードが入っているの削除する(バグでしょうか?)
- JSON のシリアライザを準備する
- Deserialize メソッドで、出力先の JRLine クラスに一気に出力する
動作結果
このように単品で動くコンソールプログラムを作っておくと、後からテストをしやすいことのほかに、単体で動くので、Linux に持って行きやすい、アセンブリを Windows アプリなどに取り込むことができる、という利点があります。.NET Framework場合は、実行ファイル(*.exe)を参照設定できるので、普通のクラスライブラリと同じように取り込むことが可能です。このテクニックは私もよく使います。
XML形式で取得する
路線API は、XML形式でデータを取得することもできます。データの内容はどちらも同じなので、JSON形式とXML形式の差はありません。強いていれば、JSON形式のほうがデータ量が少なくて済みます。しかし、.NET Framework の場合は XML 形式のほうが相性が良い(XDocumentで直接扱える)ので、これは開発効率などやクライアントの言語などで選択してください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
class Program { static void Main(string[] args) { var prog = new Program(); var t = prog.Go(); t.Wait(); var data = t.Result; int i = 0; foreach (var it in data.station_l) { i++; Console.WriteLine(string.Format("{0}: {1}", i, it.station_name)); } return; } /// /// XML形式でデータ取得 /// |
/// public async Task Go() { var line_cd = 11109; var url = $”http://www.ekidata.jp/api/l/{line_cd}.xml”; var hc = new HttpClient(); var res = await hc.GetAsync(url); var xml = await res.Content.ReadAsStringAsync(); // ① Console.WriteLine(xml); /* * XML形式が特殊なので、手作業で読み込む var st = new StringReader(xml); var xs = new System.Xml.Serialization.XmlSerializer(typeof(JRLine)); var data = xs.Deserialize(st) as JRLine; return data; */ var st = new StringReader(xml); var doc = XDocument.Load(st); // ② var jrline = new JRLine(); // ③ jrline.station_l = new List(); var line = doc.Root.Element(“line”); jrline.line_cd = int.Parse( line.Element(“line_cd”).Value); jrline.line_name = line.Element(“line_name”).Value; jrline.line_lon = double.Parse( line.Element(“line_lon”).Value); jrline.line_lat = double.Parse( line.Element(“line_lat”).Value); jrline.line_zoom = int.Parse(line.Element(“line_zoom”).Value); foreach ( var it in doc.Root.Elements()) { if ( it.Name == “station”) { var item = new JRStation(); item.station_cd = int.Parse(it.Element(“station_cd”).Value); item.station_g_cd = int.Parse(it.Element(“station_g_cd”).Value); item.station_name = it.Element(“station_name”).Value; item.lon = double.Parse(it.Element(“lon”).Value); item.lat = double.Parse(it.Element(“lat”).Value); jrline.station_l.Add(item); } } return jrline; } }
- ReadAsStringAsync メソッドで文字列として取り込むところまでは JSON 形式の取り込みと同じ。
- XDocument オブジェクトに変換する
- XmlSerializer が利用できれば良いのだが、XML 形式が特殊(stationタグがコレクションになっていない)ので、手作業で読み込む。
オープン化されたデータは、出力側の都合により、必ずしも取り込みやすい形になっていない場合があります。その場合は、仕方がないので手作業で取り込みます。自前で、サーバーとクライアントを同時に作るときは、ここの形式をうまく設計しておくと開発効率がアップします。
AndroidでJSON形式のデータを扱う
下準備ができたので、今度は Android から呼び出してみましょう。
画面の構造
路線一覧は、APIで取得することができないので、無料の会員登録を行って CSV 形式でダウンロードします。これを、選択式のリスト(Spinner)に表示させて選択します。
選択したときの ItemSelected イベントで、路線 API を呼び出して、ListView に表示する方式ですね。
デザイン
画面のデザインはこんな感じ
コード
MainActive.cs のコードが以下になります。
ほとんどがコンソールのものと同じで、Spinner に登録する部分と、路線 API を呼び出したあとに ListView に登録する部分だけが追加になります。
コンソールアプリと同じように、NuGet を使って「Newtonsoft.Json」をインストールしておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
[Activity(Label = "sgStationData", MainLauncher = true, Icon = "@drawable/icon")] public class MainActivity : Activity { Spinner spLine; ListView lv; List jrlines; protected override void OnCreate(Bundle bundle) { base.OnCreate(bundle); // Set our view from the "main" layout resource SetContentView(Resource.Layout.Main); spLine = FindViewById(Resource.Id.spinner1); lv = FindViewById(Resource.Id.listView1); // 路線データを登録 jrlines = new List(); // ① jrlines.Add(new JRLine() { line_cd = 11109, line_name = "JR千歳線" }); jrlines.Add(new JRLine() { line_cd = 11201, line_name = "JR東北本線(八戸~青森)" }); jrlines.Add(new JRLine() { line_cd = 11302, line_name = "JR山手線" }); jrlines.Add(new JRLine() { line_cd = 11305, line_name = "JR武蔵野線" }); jrlines.Add(new JRLine() { line_cd = 11623, line_name = "大阪環状線" }); jrlines.Add(new JRLine() { line_cd = 28001, line_name = "東京メトロ銀座線" }); jrlines.Add(new JRLine() { line_cd = 99927, line_name = "ゆいレール" }); var ad = new ArrayAdapter(this, Android.Resource.Layout.SimpleListItem1, jrlines); // ② spLine.Adapter = ad; // ③ spLine.ItemSelected += SpLine_ItemSelected; // ④ } /// /// 路線の選択時 /// |
/// /// private async void SpLine_ItemSelected(object sender, AdapterView.ItemSelectedEventArgs e) { var cd = jrlines[e.Position].line_cd; // ⑤ var jrline = await GetLineInfo(cd); // ⑥ var ad = new ArrayAdapter(this, Android.Resource.Layout.SimpleListItem1, jrline.station_l); // ⑦ lv.Adapter = ad; // ⑧ } public async Task GetLineInfo( int line_cd ) // ⑨ { var url = $”http://www.ekidata.jp/api/l/{line_cd}.json”; var hc = new HttpClient(); var res = await hc.GetAsync(url); var json = await res.Content.ReadAsStringAsync(); Console.WriteLine(json); // 余分なjavascriptを取り除く json = json.Replace(“if(typeof(xml)==’undefined’) xml = {};”, “”); json = json.Replace(“xml.data = “, “”); json = json.Replace(“if(typeof(xml.onload)==’function’) xml.onload(xml.data);”, “”); var js = new Newtonsoft.Json.JsonSerializer(); var jr = new Newtonsoft.Json.JsonTextReader(new System.IO.StringReader(json)); var data = js.Deserialize(jr); return data; } }
- 路線データは、CSV 形式から写しとる。
- ArrayAdapter でアダプタを作成する。
- Spinner#Adapter プロパティにアダプタを設定する。これでリストに表示されるようになる。
- 選択時の ItemSelected イベントを登録しておく。
- 選択したときに、路線ID を取ってくる
- 路線 API の呼び出し。9 は、コンソールで動かしたものをそのまま持ってきている。
- ListView に設定するためのアダプタを作成。コレクションは、jrline.station_l になる。
- ListView#Adapter プロパティに設定。ここは定番処理です。
- 路線APIを呼び出して JsonSerializer でパースします。
結果
JR山手線を選択して、駅の一覧を表示させたところです。
AndroidでXML形式のデータを扱う
同じように、XML 形式の場合も Android アプリを作ってみましょう。サンプルコードでは、sgStationXML プロジェクトになります。
コード
コードは、JSON形式の場合とほとんど変わりません。データを取得するための GetLineInfo メソッドが XML 形式になっています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
[Activity(Label = "sgStationDataXML", MainLauncher = true, Icon = "@drawable/icon")] public class MainActivity : Activity { Spinner spLine; ListView lv; List jrlines; protected override void OnCreate(Bundle bundle) { base.OnCreate(bundle); // Set our view from the "main" layout resource SetContentView(Resource.Layout.Main); spLine = FindViewById(Resource.Id.spinner1); lv = FindViewById(Resource.Id.listView1); // 路線データを登録 jrlines = new List(); jrlines.Add(new JRLine() { line_cd = 11109, line_name = "JR千歳線" }); jrlines.Add(new JRLine() { line_cd = 11201, line_name = "JR東北本線(八戸~青森)" }); jrlines.Add(new JRLine() { line_cd = 11302, line_name = "JR山手線" }); jrlines.Add(new JRLine() { line_cd = 11305, line_name = "JR武蔵野線" }); jrlines.Add(new JRLine() { line_cd = 11623, line_name = "大阪環状線" }); jrlines.Add(new JRLine() { line_cd = 28001, line_name = "東京メトロ銀座線" }); jrlines.Add(new JRLine() { line_cd = 99927, line_name = "ゆいレール" }); var ad = new ArrayAdapter(this, Android.Resource.Layout.SimpleListItem1, jrlines); spLine.Adapter = ad; spLine.ItemSelected += SpLine_ItemSelected; } private void BtnGet_Click(object sender, EventArgs e) { } /// /// 路線の選択時 /// |
/// /// private async void SpLine_ItemSelected(object sender, AdapterView.ItemSelectedEventArgs e) { var cd = jrlines[e.Position].line_cd; var jrline = await GetLineInfo(cd); var ad = new ArrayAdapter(this, Android.Resource.Layout.SimpleListItem1, jrline.station_l); lv.Adapter = ad; } ///
1 2 |
/// XML形式でデータ取得 /// |
/// public async Task GetLineInfo(int line_cd) // ① { var url = $”http://www.ekidata.jp/api/l/{line_cd}.xml”; var hc = new HttpClient(); var res = await hc.GetAsync(url); var xml = await res.Content.ReadAsStringAsync(); var st = new System.IO.StringReader(xml); var doc = XDocument.Load(st); var jrline = new JRLine(); jrline.station_l = new List(); var line = doc.Root.Element(“line”); jrline.line_cd = int.Parse(line.Element(“line_cd”).Value); jrline.line_name = line.Element(“line_name”).Value; jrline.line_lon = double.Parse(line.Element(“line_lon”).Value); jrline.line_lat = double.Parse(line.Element(“line_lat”).Value); jrline.line_zoom = int.Parse(line.Element(“line_zoom”).Value); foreach (var it in doc.Root.Elements()) { if (it.Name == “station”) { var item = new JRStation(); item.station_cd = int.Parse(it.Element(“station_cd”).Value); item.station_g_cd = int.Parse(it.Element(“station_g_cd”).Value); item.station_name = it.Element(“station_name”).Value; item.lon = double.Parse(it.Element(“lon”).Value); item.lat = double.Parse(it.Element(“lat”).Value); jrline.station_l.Add(item); } } return jrline; } }
- コンソールアプリで動作していたコードをそのままGetLineInfoメソッドに移します。
結果
JSON形式で取得する場合と全く同じ動作をすることを確認してみてください。
まとめ
さて、簡単ではありますが、既存の Web API を使ってデータを取り込めることができました。駅データ.jp 意外にもオープン化されたデータがあるのでいろいろと試してみるのもよいでしょう。
駅データ.jp では、単純に路線にある駅の一覧を取るだけでしたが、Web API はもっと複雑なことができます。複雑な Web API も中身は JSON 形式や XML 形式なので、手作業で作ることも可能なのですが、メジャーなものであればアクセスするためのクラスライブラリが用意されていることが多いでしょう。
次回は、Twitter アクセスをするための CoreTweet というライブラリを使って、Android から Twitter API を扱って、「いいね」の一覧を表示していきます。
次回も、お楽しみに。