技術顧問の増田です。
前回までは、Web API を使ってサーバーからデータを取得する機能を使ってアプリを作ってきました。今回は、その逆で、Web API を使ってサーバーにデータを送信する機能を使ってアプリを作ります。
Android アプリでアンケートを入力して、サーバーに送信する「簡易アンケート」アプリを作ってみましょう。
サンプルコードは https://github.com/moonmile/sg-xamarin-sample にあります。
バックナンバー
- 第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 アプリを作ろう:カメラ機能を使おう
POSTとは何か?
アンケートツールでは、サーバーにWebサーバーを使います。Webサーバーには、HTTP プロトコルでデータを送信するのですが、これは一般的にブラウザで閲覧をしているときと同じ「プロトコル」です。プロトコルというのは「約束事」ですから、世の中にはいろいろなプロトコルがあります。場合によっては自作プロトコルを作ることもできます。が、まあ、一般的な Web サーバーでは「HTTP プロトコル」が使われているので、この使い方を覚えると、大抵のWebサーバーアクセスができるというということになります。
フォーム入力を送信する
HTTP プロトコルの詳細は、別に探して貰うということで、ここではサンプルに必要なところだけ解説しましょう。
- HTTPプロトコルのPOSTメソッドを使う
- Content-Typeを「application/x-www-form-urlencoded」で送る。
- 戻り値は、HTML形式などで返ってくる。
ことだけ覚えておけばokです。
Content-Typeが「application/x-www-form-urlencoded」ってのは、ブラウザのフォーム(formタグやinputタグなど)を使ってサーバーと送受信する、いわゆるCGIな古くからある方式ですが、いまでも十分現役です。一方で、ajax など非同期で JSON や XML 形式のデータを送る方法もあります。こっちの方式のほうが、Android アプリで作るときは楽なのですが(Web APIと同じ作り方ができますからね)、一般的な Web サーバーの応答をそのまま使う場合は、Android アプリから「application/x-www-form-urlencoded」で送信できるようになったほうが、応用の幅が広がります。
図のように、
- ブラウザ
- コンソールアプリ
- Android アプリ
の3種類で同じ HTTP サーバーを相手にすることができるからです。
PHPでサーバーを作る
Web APIの解説では、駅データ.jp を使いましたが、今回は自前で Web サーバーを用意してみましょう。既に Linux などで LAMP 環境がある場合は、それを使ってもいいでしょう。
xampp を使う
Windows 上で、LAMP 環境を作るときは xampp を使うと便利です。Apache + MySQL/MariaDB + PHP の環境がさっくり作れます。
index.php
Windows 環境に /xampp/htdocs/svQuest フォルダを作って、index.php を置きます。単純なアンケートツールですね。
sgQuestion sample page
form の method 属性に「post」を指定して、送信先を「post.php」にしておきます。
post.php
もうひとつ、post.php を作ります。
sgQuestion post check
ポストされた内容を確認する
name:
join:
horoscope:
memo:
本来ならば、MySQL に保存するところですが、面倒…いや、ここでは実験なので値を返すだけにします。
ブラウザから呼び出し
ブラウザを使って動作確認をしておきましょう。
アンケート画面
確認画面
要求ヘッダ
HTTP プロトコルがどういう動きをしているのかを見るために F12 キーを押して、ネットワークの状態を確認しておきます。
要求本文
POST メソッドで送信するデータはこんな感じですね。
ブラウザから、送信するときはサーバーから送られてきた画面に対してぽちぽちやれば大丈夫ですが、これを自動化するにはどうしたらいいでしょうか?
という訳で、Android で実装するまえに、コンソールアプリを作って詳細を確認してみましょう。
コンソールから呼び出し
コンソールアプリから HTTP プロトコルを使って Web サーバーにアクセスするときは HttpClient クラスを使います。フォーム入力のような POST メソッドも手軽にできるようになっています。
コード
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
class Program { static void Main(string[] args) { var prog = new Program(); var t = prog.Go(); t.Wait(); } async Task Go() { var hc = new HttpClient(); // ① var dic = new Dictionary<string, string>(); // ② dic["name"] = "masuda"; dic["join"] = "1"; dic["horoscope"] = "Taurus"; dic["memo"] = "これはメモです"; var cont = new FormUrlEncodedContent(dic); // ③ var url = "http://localhost/svQuest/post.php"; // ④ var req = await hc.PostAsync(url, cont); // ⑤ var html = await req.Content.ReadAsStringAsync(); // ⑥ Console.WriteLine(html); } } |
- HttpClient のオブジェクトを生成
- 送信するパラメータは、Dictionary で作ります。
- キーと値のペアができたら、FormUrlEncodedContent でフォーム用にエンコードします。
- 送信先は post.php ですね。
- POST は、PostAsync メソッドを呼び出して実行します。
- 応答は ReadAsStringAsync を使って一括で取得すればokです。
結果
うまくいくと、こんな風に登録した結果が帰って来ます。
Android から呼び出し
コンソールアプリでの動作が確認できたところで、Android に実装していきましょう。
画面デザイン
デザイナを使ってぽちぽちとデザインします。面倒…じゃなくて、わかりやすいように LinearLayout でペタペタ作っていきましたが、もうちょっとデザインしてもokです。名前さえ変えなければ、どのように変えても構いません。
axml
1 2 |
<button> </button> |
コード
MainActivity.cs は 100行ちょっとで済みます。
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 43 44 45 46 47 48 49 50 |
[Activity(Label = "sgQuestionnaire", MainLauncher = true, Icon = "@drawable/icon")] public class MainActivity : Activity { // コントロール EditText editName; EditText editMemo; Spinner spJoin; RadioButton radioJoin1, radioJoin2, radioJoin3; List Horos; protected override void OnCreate(Bundle bundle) { base.OnCreate(bundle); // Set our view from the "main" layout resource SetContentView (Resource.Layout.Main); editName = FindViewById(Resource.Id.editName); // ① editMemo = FindViewById(Resource.Id.editMemo); spJoin = FindViewById(Resource.Id.spinner1); radioJoin1 = FindViewById(Resource.Id.radioJoin1); radioJoin2 = FindViewById(Resource.Id.radioJoin2); radioJoin3 = FindViewById(Resource.Id.radioJoin3); Button btnPost = FindViewById<button>(Resource.Id.buttonPost); btnPost.Click += BtnPost_Click; // 星座の一覧を登録 Horos = new List(); // ② Horos.Add(new Horo() { Id = "", Text = "選択してください" }); Horos.Add(new Horo() { Id = "Aries", Text = "おひつじ座" }); Horos.Add(new Horo() { Id = "Taurus", Text = "おうし座" }); Horos.Add(new Horo() { Id = "Gemini", Text = "ふたご座" }); Horos.Add(new Horo() { Id = "Cancer", Text = "かに座" }); Horos.Add(new Horo() { Id = "Leo", Text = "しし座" }); Horos.Add(new Horo() { Id = "Virgo", Text = "おとめ座" }); Horos.Add(new Horo() { Id = "Libra", Text = "てんびん座" }); Horos.Add(new Horo() { Id = "Scorpio", Text = "さそり座" }); Horos.Add(new Horo() { Id = "Saggitarius", Text = "いて座" }); Horos.Add(new Horo() { Id = "Capricorn", Text = "やぎ座" }); Horos.Add(new Horo() { Id = "Aquarius", Text = "みずがめ座" }); Horos.Add(new Horo() { Id = "Pisces", Text = "うお座" }); var ad = new ArrayAdapter(this, Android.Resource.Layout.SimpleListItem1, Horos); // ③ spJoin.Adapter = ad; // ④ } /// /// 投稿ボタン /// </button> |
/// /// private async void BtnPost_Click(object sender, System.EventArgs e) { // 画面からデータを取得 string name = editName.Text; // ⑤ string memo = editMemo.Text; if ( spJoin.SelectedItemPosition == 0 ) { new AlertDialog.Builder(this) // ⑥ .SetMessage(“星座を選択してください”) .Show(); return; } string horo = Horos[spJoin.SelectedItemPosition].Id; string join = “1”; if (radioJoin1.Checked) join = “1”; if (radioJoin2.Checked) join = “2”; if (radioJoin3.Checked) join = “3”; var html = await Go(name, join, horo, memo); // ⑦ // 結果を表示 new AlertDialog.Builder(this) // ⑧ .SetMessage( html ) .Show(); return; } async Task Go(string name, string join, string horo, string memo) // ⑨ { var hc = new HttpClient(); var dic = new Dictionary<string, string>(); dic[“name”] = name; dic[“join”] = join; dic[“horoscope”] = horo; dic[“memo”] = memo; var cont = new FormUrlEncodedContent(dic); // 呼び出しのIPアドレスは変更すること var url = “http://172.16.0.11/svQuest/post.php”; var req = await hc.PostAsync(url, cont); var html = await req.Content.ReadAsStringAsync(); return html; } } public class Horo // ⑩ { public string Id { get; set; } public string Text { get; set; } public override string ToString() { return this.Text; } }
簡単にコードの解説をしておきましょう。
- FindViewById メソッドでコントロールを取ってきます。
- 選択用の Spinner に設定するリストを作ります。
- ArrayAdapter を作っておいて、
- Adapter プロパティに設定します。
- 「投稿」ボタンをタップしたときに、各コントロールから値を取得します。
- 入力時のエラーメッセージは「AlertDialog.Builder」を使うと便利です。
- 投稿時のメソッドはコンソールアプリからそのままコピーしています。投稿先の URL アドレスを記述しなおしてください。
- 結果を表示します。
- 投稿時の処理をコピペします。
- Spinner に設定する Horo クラスを作っておきます。
実行
コードができたらエミュレータで実行してみましょう。
ブラウザやコンソールアプリと同じようにアンケートが投稿できるようになりましたね。
サーバーを.NET Coreで作る
さて、LAMP サーバーの場合は、PHP を使ってサーバーサイドのコードを記述しましたが、ASP.NET Core MVC を使うこともできます。
この部分は、おまけなので、ざっとコードだけ示しておきます。詳細は https://github.com/moonmile/sg-xamarin-sample/tree/master/src/sgQuestionnaire/sqQuestSvCore にあります。
Model
MVC パターンの Model クラスです。
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 |
public class Quest { [Display(Name = "名前")] public string name { get; set; } [Display(Name = "参加")] public int join { get; set; } [Display(Name = "星座")] public string horoscope { get; set; } [Display(Name = "メモ")] public string memo { get; set; } public static List horoscopes { get { var Horos = new List(); Horos.Add(new Horo() { Id = "", Text = "選択してください" }); Horos.Add(new Horo() { Id = "Aries", Text = "おひつじ座" }); Horos.Add(new Horo() { Id = "Taurus", Text = "おうし座" }); Horos.Add(new Horo() { Id = "Gemini", Text = "ふたご座" }); Horos.Add(new Horo() { Id = "Cancer", Text = "かに座" }); Horos.Add(new Horo() { Id = "Leo", Text = "しし座" }); Horos.Add(new Horo() { Id = "Virgo", Text = "おとめ座" }); Horos.Add(new Horo() { Id = "Libra", Text = "てんびん座" }); Horos.Add(new Horo() { Id = "Scorpio", Text = "さそり座" }); Horos.Add(new Horo() { Id = "Saggitarius", Text = "いて座" }); Horos.Add(new Horo() { Id = "Capricorn", Text = "やぎ座" }); Horos.Add(new Horo() { Id = "Aquarius", Text = "みずがめ座" }); Horos.Add(new Horo() { Id = "Pisces", Text = "うお座" }); return Horos; } } } public class Horo { public string Id { get; set; } public string Text { get; set; } } |
Controller
Controller クラスを作っておきます。登録時には Create メソッドが呼び出されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
public class QuestController : Controller { // GET: // public IActionResult Index() { ViewData["horoscopes"] = new SelectList( Quest.horoscopes, "Id", "Text" ); return View(); } [HttpPost] [ValidateAntiForgeryToken] public IActionResult Create([Bind("name,join,horoscope,memo")] Quest quest) { if (ModelState.IsValid) { // 実際は保存処理が入る // _context.Add(quest); // await _context.SaveChangesAsync(); return View("Result", quest); // return RedirectToAction("Post"); } return View(quest); } } |
View
ブラウザから入力するための View を作っておきます。
1 2 3 4 5 |
@model sgQuestMvcCore.Models.Quest @{ ViewData["Title"] = "Create"; } |
Create
MainActivity.cs の書き替え
投稿時の URL が異なるので、書き替えて実行します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
async Task Go(string name, string join, string horo, string memo) { var hc = new HttpClient(); var dic = new Dictionary<string, string>(); dic["name"] = name; dic["join"] = join; dic["horoscope"] = horo; dic["memo"] = memo; var cont = new FormUrlEncodedContent(dic); // 呼び出しのIPアドレスは変更すること // var url = "http://172.16.0.11/svQuest/post.php"; // .NET Core のサンプルの場合 var url = "http://172.16.0.11:5000/Quest/Create"; var req = await hc.PostAsync(url, cont); var html = await req.Content.ReadAsStringAsync(); return html; } |
実行
アンケートの入力と「投稿」ボタンまでは同じ動作になります。.net core のサーバーを「dotnet run」で動かして 5000 番ポートで待ち受けます。
戻り値が、ちょっとうまく表示できていない(HTMLデータが多すぎる)ので調節が必要ですが、ひとまず登録できていることがわかります。
まとめ
如何だったでしょうか? Android からサーバーにアクセスするときに HttpClient を使えば、結構いろいろなことができることが分かります。画面はチープですが、サーバーと組み合わせれば、サーバー側で独自で拡張することもできるので、同じアプリであっても後から機能をアップすることができます。また、逆にサーバーは同じであってもクライアントの UI を変更することで使いやすい画面を作ることも可能です。
次回は、Android 自身の機能を使ってアプリを作ります。カメラを使って、撮影の基本的な部分を作ってみます。
お楽しみに。