Android と XML を使って動的なユーザー・インターフェースを作成する

Android フォーム・エンジンを使用したデータ収集

非営利目的で投票を行ったりデータを収集したりするためのフォームを、簡単にセットアップして使用できる形で提供している Web サイトは数多くあります。このチュートリアルで紹介するのは、Android 向けにこれと同じようなアプリケーション (つまりプログラマーでなくてもモバイル・ユーザーからデータを収集できる動的なユーザー・インターフェース) を設計するための単純なアーキテクチャーです。さらにこのチュートリアルでは、サーバー・サイドとモバイル・サイドの両方でサンプル・フォーム・エンジンを作成する方法を説明します。

Frank Ableson, Entrepreneur, MSI Services, Inc.

W. Frank Ableson は、妻の Nikki と子供たちと一緒にニュージャージー州北部に住む起業家です。モバイル・ソフトウェア、組み込み設計などを専門分野としています。彼は、『Unlocking Android』(Manning Publications、2010年) の著者であり、Linux Magazine のモバイル関連のエディターでもあります。



2010年 9月 07日

はじめに

このチュートリアルを最大限に活用するには、Android SDK を使用して Android アプリケーションを作成する作業に慣れていることが前提となります。このチュートリアルを最後まで終えると、アプリケーションと Web サーバーとの間で HTTP(S) による通信を行う方法、そして DOM パーサーを使用して XML を構文解析する方法を学習することができます。チュートリアルでは、ユーザー・インターフェースの動的なカスタム・レイアウト、そしてマルチスレッド化した通信を行えるようにし、メッセージ・ハンドラー、進行状況ダイアログを作成します。また、AndroidManifest.xml とサーバー・サイドのスクリプトを作成する方法についても簡単に説明します。

このチュートリアルについて

よく使われる頭文字語

  • API: Application Programming Interface
  • DOM: Document Object Model
  • HTML: HyperText Markup Language
  • HTTP(S): Hypertext Transfer Protocol Secure
  • IDE: Integrated development environment
  • SAX: Simple API for XML
  • SDK: Software Development Kit
  • UI: User Interface
  • URL: Uniform Resource Locator
  • XML: Extensible Markup Language

このチュートリアルで紹介するのは、Android 搭載機器でのモバイル・データ収集に対応した動的フォームのアーキテクチャーです。まず始めにアーキテクチャーの概要として、このアプリケーションがデータ収集という大きな枠組みのなかでどのような部分に相当するのかを説明します。そして、すべてのソース・ファイルが含まれる完成後のプロジェクトをあらかじめ披露して、このチュートリアルが辿るロードマップを示します。手順では料理番組風に、素材となる Java クラスを 1 つひとつ慎重に組み込み、(このフォーム・エンジンのベースとなるデータ・モデルをはじめとする) アプリケーションの他の側面と関連付けながら、アプリケーションを一から構築していきます。最後に、フォームに入力されたデータをサーバーに保存し、アプリケーションのサーバー・サイドでの処理について簡単に説明してチュートリアルを締めくくります。

前提条件

表 1 に、このプロジェクトに必要なツールを記載します。

表 1. 作業で必要なツール
ツール注記
Eclipse および ADT主要なコード・エディターおよび Android Developer Tools プラグインです。
Android SDKAndroid 向けソフトウェア開発キットです。
Web サーバーPHP をサポートする任意の Web サーバー。PHP スクリプトは、別のサーバー環境に簡単にポーティングすることができます。

このチュートリアルのサンプル・コードを作成するために使用したのは、Eclipse 3.4.2 と Android SDK バージョン 8 (Android 2.2 リリースをサポート) をインストールした MacBook です。チュートリアルのコードには Android SDK バージョン 8 に固有の機能を使用していないので、Android バージョン 1.5 以降であれば、このアプリケーションを問題なく実行できるはずです。いずれのツールについても、「参考文献」にリンクが記載されています。


データ収集

まずはデータ収集についての概要、そして Android 搭載のモバイル機器を使用することで、簡単にデータを収集できるようにする方法を説明します。

Android のデータ収集フレームワーク

データの収集は、コンピューターの時代以前に遡るタスクです。コンピューターは今や日常生活の必需品となっており、情報に対する考え方、情報を検索、使用する方法を根本的に変えました。数十億ドルものマーケット・キャピタルを有する企業が存在できるのは、その企業が膨大な量の情報を効率的な方法で保存、取得、管理しているからに他なりません。今日使用されているデータベースには、メインフレームからクライアント・サーバー、Web アプリケーション、そして今ではモバイル・アプリケーションに至るまでの多種多様なアーキテクチャーのシステムからデータが提供されています。

初期の頃のモバイル・コンピューティングのデータ収集アプリケーションの実例としては、実地棚卸用アプリケーションや循環棚卸用アプリケーションが挙げられます。これらのアプリケーションはバッチ方式のデータ収集であることが多く、モバイル・コンピューティングのハードウェアにドッキング・ステーションを接続しなければ、収集された情報をアップロードすることができませんでした。

その頃と比べ、モバイル・アプリケーション市場は大きく発展しています。今や、ワイヤレス接続、ワイヤレス機器はさまざまな文化や市場のほとんど至るところに浸透しており、事実上、日常生活のすべての面に入り込んでいます。

データを収集する手段についてはモバイル化が進んでいるかもしれませんが、データ収集のコアとなる側面は大きく変わってはいません。それは、ユーザーに一連の質問を提示し、簡単な方法で応答できるようにすることです。このチュートリアルでは、XML によって可能になる動的メタデータ構造を利用して、Android 携帯電話に対応した単純なデータ収集フレームワークを作成する手順を具体的に説明します。


アプリケーションのアーキテクチャー

コードの詳細に取り掛かる前に、まずはこのアプリケーションの設定を大まかに説明しておきます。

フォーム・エンジンの概要

このセクションでは、これから作成するフォーム・エンジン・アプリケーションのすべての側面を紹介します。図 1 に、このアプリケーションと、さまざまな内容のデータ入力フォームを提供する 1 つまたは複数のサーバーとの関係を図解します。

図 1. アプリケーションのアーキテクチャー
アプリケーションのアーキテクチャーを示す図

図 1 のフォーム 1 はロボット・コンテストに登録するためのフォームで、フォーム 2 はユーザーに自動車を整備する習慣に関して質問するフォームです。これらのフォームと Android アプリケーションは、HTTP(S) による通信を介して以下の操作を行います。

  • フォーム・データをダウンロードする。
  • フォーム・データをユーザーに表示し、オプションで機器に固有のデータ (カメラで撮影した画像、録音された音声、GPS による位置情報、コンパスの読み取り値など) を収集する。
  • ユーザーが入力したデータを収集する。
  • データを適切なサーバーに送信する。

このチュートリアルのサーバー・サイドは、2 つのファイルとして実装されています。一方のファイルは、フォームを記述する XML 文書、もう一方はフォームの送信を記録する PHP 文書です。この Android アプリケーションは Android SDK を使って Java コードで作成されたネイティブ・アプリケーションであり、コードの作成には Eclipse を使用します。

表 2 に、このアプリケーションを構成するすべてのソース・ファイルを示します。このすべてのソース・ファイルが含まれる zip 形式の圧縮ファイルをダウンロードしてください (「ダウンロード」を参照)。このチュートリアルでは、これらのファイルを 1 つひとつ詳しく説明していきます。

表 2. 必要なアプリケーション・ソース・コード
ファイル名注記
XmlGui.javaAndroid アクティビティーのエントリー・ポイント
XmlGuiForm.javaフォームの上位レベルのデータ・モデルおよびメソッド
XmlGuiFormField.javaフォームのフィールドを表し、フォームの各フィールドのメタデータを格納します。
XmlGuiEditBox.javaテキスト・ボックス形式のユーザー・インターフェース要素を実装します。
XmlGuiPickOne.javaドロップダウン・リスト形式のユーザー・インターフェース要素を実装します。
RunForm.java上記のクラスを使用してフォームを処理します。
main.xmlアプリケーション・ユーザー・インターフェースのホーム・ページ
AndroidManifest.xmlAndroid アプリケーションのデプロイメント記述子
xmlgui1.xmlロボット・コンテキストの登録情報を収集するサンプル・フォーム
xmlgui1-post.phpフォーム送信を処理する PHP スクリプト
xmlgui2.xml自動車を整備する習慣を調査するためのサンプル・フォーム

このチュートリアルを最後まで終えると、Eclipse におけるアプリケーションのプロジェクトの構造は図 2 のようになっているはずです。

図 2. Eclipse におけるプロジェクトの構造
Eclipse におけるプロジェクトの構造のスクリーン・キャプチャー
Eclipse におけるプロジェクトの構造のテキスト・バージョン (図 2)
V  XMLGUI
  V  src
    V  com.msi.ibm
      > RunForm.java
      > XmlGui.java
      > XmlGuiEditBox.java
      > XmlGuiForm.java
      > XmlGuiFormField.java
      > XmlGuiPickOne.java
  >  gen [Generated Java Files]
  >  Google APIs [Android 2.2]
     assets
  V  res
    >  drawable-hdpi
    >  drawable-ldpi
    >  drawable-mdpi
    V  layout
        XmlGuiPickOne.java
       raw
    >  values
     AndroidManifest.xml
     default.properties

作業用の Android 開発環境をまだ用意していない場合は、今こそ Android をインストールする絶好の機会です。Android 開発環境のセットアップ方法について詳しくは、「参考文献」に記載されている必須ツールへのリンクと、Android を対象としたアプリケーションの開発方法に関する入門記事およびチュートリアルを参照してください。Android を熟知することが、このチュートリアルを理解する上でも役立ちます。

アプリケーションおよびアーキテクチャーの概要について理解したところで、ここからは実際の手順を開始します。


プロジェクトおよびデータ・モデル

Eclipse で Android プロジェクトを作成する準備ができたので、早速プロジェクトの作成を開始します。プロジェクトを作成したら、続いてデータ・モデルを作成し、最後にフォーム・エンジン・アプリケーションのメタデータを保存および管理するためのクラスを作成します。

プロジェクトを作成する

Android アプリケーションの作成作業は、お定まりの場所から開始します。Eclipse を開いて、「File (ファイル)」 > 「New (新規)」の順に選択してください (図 3 を参照)。

図 3. 新規 Android アプリケーションを作成する
Android アプリケーションを作成する際のスクリーン・キャプチャー

上記の操作によって Eclipse の「New Project (新規プロジェクト)」ウィザードが起動されます。このウィザードで、「Android Project (Android プロジェクト)」(Android 専用の Java 環境) を選択します。プロジェクトには必ず有効なプロジェクト名を指定してください (ここでは「XMLGUI」という名前を指定しました)。このチュートリアルで説明するソリューションに合うように、「Properties (プロパティー)」セクションでは「XML GUI」というアプリケーション名と「com.msi.ibm」というパッケージ名を入力します。また、「Create Activity (アクティビティーの作成)」チェック・ボックスを選択し、アクティビティー名として「XmlGui」と入力します (図 4 を参照)。

図 4. 新規プロジェクトをセットアップする
新規プロジェクトをセットアップする際のスクリーン・キャプチャー

作成されたプロジェクトは、図 5 のような構造になるはずです。

図 5. Eclipse で新規プロジェクト・ウィザードを完了した時点での Android プロジェクト
Eclipse で新規プロジェクト・ウィザードを完了した時点での Android プロジェクト
Eclipse で新規プロジェクト・ウィザードを完了した時点での Android プロジェクトのテキスト・バージョン (図 5)
V  XMLGUI
  V  src
    V  com.msi.ibm
      > XmlGui.java
  >  gen [Generated Java Files]
  >  Google APIs [Android 2.2]
     assets
  V  res
    >  drawable-hdpi
    >  drawable-ldpi
    >  drawable-mdpi
    >  layout
    >  values
     AndroidManifest.xml
     default.properties

これでプロジェクトが作成できたので、Android エミュレーターでアプリケーションが問題なくビルドされ、実行されることを確認してみるとよいでしょう。ここで注意する点として、Java ソース・ファイルを編集して保存するまでは、アプリケーションがビルドされない場合があります。しかしその場合には、Android SDK ツールが自動的に gen フォルダーにファイルを生成するので、Eclipse 環境の「Problems (問題)」タブにエントリーが表示されていなければ、アプリケーションをテストすることができます。

アプリケーションをテストするには、「Run Configuration (実行構成)」を作成します (図 6 を参照)。「Android Application (Android アプリケーション)」のリストから「XmlGui」を選択し、「Name (名前)」フィールドに「XmlGui」が表示されていること、「Project (プロジェクト)」フィールドに「XMLGUI」が表示されていること、「Launch (起動)」フィールドに「com.msi.ibm.XmlGui」が表示されていることを確認します。これらの値を確認したら、「Run (実行)」をクリックします。

図 6. 実行構成のセットアップ
実行構成のセットアップ

これまでの手順で、プロジェクトを作成し、構成し、そして Android エミュレーターで正常に開始しました。次は、Android に対応した XML ベースのデータ収集ツールを作成します。

データ・モデル

このアプリケーションの基本は、入力要素をユーザーに提示し、ユーザーからデータを収集し、そのデータを検証してから指定されたサーバー・ロケーションに送信することです。注意する点として、このアプリケーションは新しいレコードのみを対象にセットアップされており、既存のレコードを編集または削除目的で検索するようにはなっていません。

アプリケーションにデータ入力フォームの提示方法に関する指示が十分に伝わるようにするためには、一般にメタデータと呼ばれる一連の情報が必要です。メタデータとは、データに関するデータのことです。大まかに言うと、このアプリケーションは以下のデータ要素を理解する必要があります。

  • フォーム名—人間が読んで理解できるフォームの名前
  • フォーム ID—このメタデータ・コレクションに固有の ID
  • 送信 URL—収集したデータの送信先
  • 1 つまたは複数のフィールド—テキスト・フィールド、数値フィールド、または「リストから選択」といった類のフィールド

どのような種類の質問でも、回答するためのフィールドは、事実上この 3 つのタイプのユーザー・インターフェース要素のいずれかに当てはまります。例えば、チェック・ボックスで答える質問は、「はい」または「いいえ」のどちらかを選択するフィールドとして実装することができます。複数選択可能な質問は、複数選択可能なフィールドとして実装することができます。もちろん、このチュートリアルに記載するコードはお好きなように拡張して構いません。

このアプリケーションの使用シナリオは次のようになります。あるイベントが行われていて、そのイベント会場では 1 つ以上のアクティビティーに登録できるという設定です。アクティビティーに登録するには、用紙に記入するか、家に帰ってからこのイベントを開催している組織の Web サイトにサインインして登録するという方法がありますが、家に帰ってから登録するとなると、忘れてしまう可能性もあります。そこで、Android 機器に動的フォームを表示させることで、その場でユーザーの携帯電話から参加者の氏名、性別、年齢を単純なフォームに入力できるようにします。

リスト 1 に、xmlgui1.xml の内容を記載します。このファイルは、ロボット・クラブのイベントへの登録フォームを表しています。

リスト 1. xmlgui1.xml
<?xml version="1.0" encoding="utf-8"?>
<xmlgui>
<form id="1" name="Robotics Club Registration" 
   submitTo="http://serverurl/xmlgui1-post.php" >
<field name="fname" label="First Name" type="text" required="Y" options=""/>
<field name="lname" label="Last Name" type="text" required="Y" options=""/>
<field name="gender" label="Gender" type="choice" required="Y" options="Male|Female"/>
<field name="age" label="Age on 15 Oct. 2010" type="numeric" required="N" options=""/>
</form>
</xmlgui>

この XML 文書については、以下の点に注意してください。

  • この XML は要素の属性を多用しているため、とても簡単に構文解析することができます。要素の属性を多用している理由は、複数の子要素とタグを使用する場合に比べ、データの抽出を容易に行えること、ダウンロードのサイズを小さく抑えられること、そして構文解析に要する時間が短くなることなどです。
  • submitTo 属性は、アプリケーションに対し、収集したデータの送信先を指定します。
  • field 要素には、フィールド名とラベル両方の属性があります。これらの属性の値は互いに関連しますが、それぞれの name 属性の値は他の name 属性の値と区別できるように一意に決まる名前にし、受け取り側のアプリケーションが正しく構文解析して処理できるようにしなければなりません。また、label 属性には、ユーザーがそのフィールドに入力すべきデータの種類について手掛かりとなるような情報を値として提供する必要もあります。
  • この文書は、各フィールドのデフォルト値、検証用の regex 式、または特定のフィールドに関する詳細な情報を参照するためのリンクなどを組み込めるように簡単に拡張することができます。
  • options フィールドは、choice フィールド用の区切りリストとして使用されています。

以上でデータ・モデルに関する基本的なことが理解できたので、次は、この XML データを有用なアプリケーションに変身させるコードを見ていきましょう。

データを表現する

データの構文解析は、かなり機械的な作業です (構文解析については後ほど説明します)。構文解析のプロセスを検討する前に、このアプリケーションにまず必要なのは、メタデータを保存して管理するためのメモリー内の場所です。そのために、2 つの Java クラスを用意します。1 つはフォーム用、もう 1 つはフォーム・フィールドを表すためのものです。まずは、リスト 2XmlGuiForm.java を見てください。

リスト 2. XmlGuiForm.java
package com.msi.ibm;
import java.util.Vector;
import java.util.ListIterator;
import java.net.URLEncoder;

public class XmlGuiForm {

   private String formNumber;
   private String formName;
   private String submitTo;
   public Vector<XmlGuiFormField> fields;


   public XmlGuiForm()
   {
      this.fields = new Vector<XmlGuiFormField>();
      formNumber = "";
      formName = "";
      submitTo = "loopback"; // do nothing but display the results
   }
   // getters & setters
   public String getFormNumber() {
      return formNumber;
   }

   public void setFormNumber(String formNumber) {
      this.formNumber = formNumber;
   }


   public String getFormName() {
      return formName;
   }
   public void setFormName(String formName) {
      this.formName = formName;
   }

   public String getSubmitTo() {
      return submitTo;
   }

   public void setSubmitTo(String submitTo) {
      this.submitTo = submitTo;
   }

   public Vector<XmlGuiFormField> getFields() {
      return fields;
   }

   public void setFields(Vector<XmlGuiFormField> fields) {
      this.fields = fields;
   }

   public String toString()
   {
      StringBuilder sb = new StringBuilder();
      sb.append("XmlGuiForm:\n");
      sb.append("Form Number: " + this.formNumber + "\n");
      sb.append("Form Name: " + this.formName + "\n");
      sb.append("Submit To: " + this.submitTo + "\n");
      if (this.fields == null) return sb.toString();
      ListIterator<XmlGuiFormField> li = this.fields.listIterator();
      while (li.hasNext()) {
         sb.append(li.next().toString());
      }
   
      return sb.toString();
   }

   public String getFormattedResults()
   {
      StringBuilder sb = new StringBuilder();
      sb.append("Results:\n");
      if (this.fields == null) return sb.toString();
      ListIterator<XmlGuiFormField> li = this.fields.listIterator();
      while (li.hasNext()) {
         sb.append(li.next().getFormattedResult() + "\n");
      }

      return sb.toString();
   }

   public String getFormEncodedData()
   {
      try {
      int i = 0;
      StringBuilder sb = new StringBuilder();
      sb.append("Results:\n");
      if (this.fields == null) return sb.toString();
      ListIterator<XmlGuiFormField> li = this.fields.listIterator();
      while (li.hasNext()) {
         if (i != 0) sb.append("&");
         XmlGuiFormField thisField = li.next();
         sb.append(thisField.name + "=");
         String encstring = new String();
         URLEncoder.encode((String) thisField.getData(),encstring);
         sb.append(encstring);
      }

      return sb.toString();
      }
      catch (Exception e) {
         return "ErrorEncoding";
      }
   }
}

上記の XmlGuiForm クラスについて、触れておく必要がある重要な項目を以下に挙げます。

  1. 以下の 4 つのメンバー変数があります。
  2. formNumber: サーバー・サイドのフォーム配信メカニズムで使用する一意の ID です。
  3. formName: この変数はフォームのタイトルとなり、ユーザーにコンテキストを提供したり、ユーザーが確認を行ったりするために使われます。
  4. submitTo: 入力されたデータをアプリケーションが (検証後に) 送信する宛先 URL です。あるいは、この値をループバックにすることもできます。ループバックのシナリオでは、データはサーバーに送信される代わりに、ユーザーに表示されます。この方法は、テストの際に役立ちます。
  5. fields: フォームのフィールド・データを格納するようにテンプレート化された Vector クラスです。詳細については、リスト 3XmlGuiFormField.java で説明します。
  6. 上記の変数のそれぞれにゲッターとセッターがあります。
  7. toString() メソッドと getFormattedResults() メソッドは、人間が読んで理解できる XmlGuiForm クラスの要約を生成します。
  8. getFormEncodedData() メソッドは、データを submitTo 属性に指定された URL に送信する準備の際に使用されます。
  9. 厳密に連結された java.lang.String クラスを使用する代わりに、このコードではメモリー効率の良い方法で目的のデータ・ストリングを作成するため、StringBuilder を使用しています。
  10. URLEncoder クラスはサーバーに送信するデータを準備するために使用されます。このクラスによって、データは実際に従来の HTML フォームで作成されたかのように表示されます。
  11. このアプリケーションは、例えば以下のように拡張することができます。
    • メタデータをローカルに保存するか、またはキャッシュに入れることによって、繰り返されるタスクの実行時間を短縮します。
    • データを一定の期間、ローカル・ストレージに記録してから送信します。
    • GPS の記録機能によって、各レコードにロケーション・データのスタンプを付けます。

今度はリスト 3 に記載する XmlGuiFormField クラスの構成を見てください。

リスト 3. XmlGuiFormField.java
package com.msi.ibm;
// class to handle each individual form
public class XmlGuiFormField {
   String name;
   String label;
   String type;
   boolean required;
   String options;
   Object obj;   // holds the ui implementation
                   // or the EditText for example


   // getters & setters
   public String getName() {
      return name;
   }
   public void setName(String name) {
      this.name = name;
   }
   public String getLabel() {
      return label;
   }
   public void setLabel(String label) {
      this.label = label;
   }
   public String getType() {
      return type;
   }
   public void setType(String type) {
      this.type = type;
   }
   public boolean isRequired() {
      return required;
   }
   public void setRequired(boolean required) {
      this.required = required;
   }
   public String getOptions() {
      return options;
   }
   public void setOptions(String options) {
      this.options = options;
   }

   public String toString()
   {
      StringBuilder sb = new StringBuilder();
      sb.append("Field Name: " + this.name + "\n");
      sb.append("Field Label: " + this.label + "\n");
      sb.append("Field Type: " + this.type + "\n");
      sb.append("Required? : " + this.required + "\n");
      sb.append("Options : " + this.options + "\n");
      sb.append("Value : " + (String) this.getData() + "\n");

      return sb.toString();
   }
   public String getFormattedResult()
   {
      return this.name + "= [" + (String) this.getData() + "]";

   }

   public Object getData()
   {
      if (type.equals("text") || type.equals("numeric"))
      {
         if (obj != null) {
            XmlGuiEditBox b = (XmlGuiEditBox) obj;
            return b.getValue();
         }
      }
      if (type.equals("choice")) {
         if (obj != null) {
            XmlGuiPickOne po = (XmlGuiPickOne) obj;
            return po.getValue();
         }
      }

      // You could add logic for other UI elements here
      return null;
   }

}

以下に、XmlGuiFormField クラスの詳細を説明します。

  • クラス・レベルのメンバーには以下の 6 つがあります。
    1. name にはフィールドの名前が格納されます。これはデータ値のフィールド名で、HTML のフォーム・フィールド名、あるいはデータベースの列名のようなものです。
    2. label にはフィールドの説明、またはユーザーに表示する値が格納されます。
    3. type は、作成するユーザー・インターフェース・フィールドの種類を指定します。
      • text は、このフィールドが、英数字を入力するための EditText フィールドとして実装されることを意味します。これが最もよく使われる値です。
      • numeric も同じく EditText ですが、入力する値は数字だけに制限されます。
      • choice は、フィールドをドロップダウン・リストにします。
    4. required は、フィールドが必須フィールドであるか否かを示すブール値です。フィールドが必須フィールドの場合、値を入力しないままフォームを送信しようとすると、エラー・メッセージが表示されます。
    5. options は、選択フィールドに選択項目のリストを取り込むために使用されるストリング値です。このフィールドは、例えば検証用の regex 式として使用されるフィールドで使用することも、このフィールドの値を変更することで、デフォルト値を指定することもできます。
    6. obj は、ユーザー・インターフェース・ウィジェットを表します。例えば、この変数にはテキスト・フィールドや数値フィールド用の EditText が格納されます。選択フィールドの場合、obj メンバーに格納されるのは spinner ウィジェットです。この手法については、後ほど詳しく説明します。
  • ご想像のとおり、これらの変数にはそれぞれのゲッターとセッターがあります。
  • toString() メソッドと getFormattedResult() メソッドは両方とも、次に説明する getData() メソッドを利用します。
  • XmlGuiFormField クラスでは複数のタイプのデータを管理する必要があるため、データの保存方法およびアクセス方法が明確なコードにしなければなりません。getData() メソッドは type フィールドを調べて obj フィールドで型キャストを実行することにより、保存されているオブジェクトを適切に操作できるようにします。このフレームワークに新しいフィールド・タイプを追加する場合には、getData() メソッドを拡張することで、その新しいフィールド・タイプに対応することができます (リスト 3 の終わりの近くにあるコメントを参照)。

これで、メタデータを保存して管理する手段が用意できました。次は、アプリケーションの動作を調べ、それに続いてさまざまな要素を 1 つに組み立てます。


ユーザー・インターフェースを組み立てる

まずは、モバイル・ユーザーがデータを入力するためのフォームの作成から取り掛かります。

一から組み立てる

アプリケーションのエントリー・ポイントは XmlGui.java にあります (リスト 4 を参照)。

リスト 4. アプリケーションのエントリー・ポイント: XmlGui
package com.msi.ibm;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.Button;
import android.widget.TextView;
import android.content.Intent;
import android.util.Log;
public class XmlGui extends Activity {
        final String tag = XmlGui.class.getName();
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        Button btnRunForm = (Button) this.findViewById(R.id.btnRunForm);
        btnRunForm.setOnClickListener(new Button.OnClickListener()
        {
           public void onClick(View v)
           {
                       EditText formNumber = (EditText) findViewById(R.id.formNumber);
                       Log.i(tag,"Attempting to process Form # 
                   [" + formNumber.getText().toString() + "]");
                       Intent newFormInfo = new Intent(XmlGui.this,RunForm.class);
                       newFormInfo.putExtra("formNumber", 
                   formNumber.getText().toString());
                       startActivity(newFormInfo);
           }
        });
    }
}

メインとなる Activity のユーザー・インターフェースは極めて単純で、以下の構成要素しかありません。

  • ラベル (TextView)
  • 入力ボックス (EditText)
  • ボタン (Button)

XmlGui Activity のコードは至って標準的なもので、設計時に作成したレイアウトを拡張して、(以下で説明する) 目的の機能を実装するためのボタン・ハンドラーを定義し、作成します。

ユーザー・インターフェースを定義するファイルは main.xml です (res フォルダーの layout サブフォルダー内に置かれています)。リスト 5 に main.xml を記載します。

リスト 5. main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    >

   <TextView  
       android:layout_width="wrap_content" 
       android:layout_height="wrap_content" 
       android:text="@string/Title"
       />
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    >

   <EditText
       android:layout_width="100px"
       android:layout_height="wrap_content"
       android:text="1"
       android:id="@+id/formNumber"
   android:numeric="integer"/>
   <Button android:text="Run Form" android:id="@+id/btnRunForm" 
       android:layout_width="wrap_content" 
       android:layout_height="wrap_content">
   </Button>
</LinearLayout>

</LinearLayout>

参考までに、レイアウトは XML を直接編集するか、Android Developer Tools に含まれているレイアウト・ツール (図 7 を参照) を使用して変更することができます。

図 7. レイアウト・ツール
レイアウト・ツールのスクリーン・キャプチャー

アプリケーションを実行するには、ホーム画面アイコンをクリックします。すると、XmlGui Activity が起動します (図 8 を参照)。

図 8. 実行中のアプリケーション
実行中のアプリケーションのスクリーン・キャプチャー

ユーザーがフォームの番号を入力し、「Run Form (フォームの実行)」ボタンをタップすると、一連のイベントが発生します。ここで、この onClick() メソッドの内容を 1 行ずつ辿って行きましょう。onClick() メソッドは、リスト 4 の XmlGui クラスに含まれていることを思い出してください。

このメソッドではまず、formNumber という名前の EditText フィールドを参照します。R.id.formNumber 列挙オブジェクトは、main.xml ファイルが保存されるたびに、Android ビルド・ツールによって自動生成されます。

EditText formNumber = (EditText) findViewById(R.id.formNumber);

次に、ログに 1 行挿入します。このログの出力は、Android Developer Tools プラグインによって Eclipse に提供される「Dalvik Debug Monitor Service (DDMS)」パースペクティブで確認することができます。

Log.i(tag,"Attempting to process Form # [" + formNumber.getText().toString() + "]");

フォーム・エンジンの実際の実装が提供されるのは、Activity クラスを継承する RunForm クラスです。この Activity を起動するには、RunForm クラスを明示的に指定して Intent を作成します。

Intent newFormInfo = new Intent(XmlGui.this,RunForm.class);

RunForm Activity を起動するだけでなく、どのフォームを表示するかも指定しなければなりません。そのために、putExtra メソッドを使用してフォームの番号を Intent に追加します。

newFormInfo.putExtra("formNumber", formNumber.getText().toString());

上記の値が、後で説明する RunForm クラスによって抽出されます。

Intent はこれでセットアップできたので、startActivity を呼び出して Activity を起動します。

startActivity(newFormInfo);

このアプリケーションでは、ユーザーがフォームの番号を入力して「Run Form (フォームの実行)」ボタンをクリックします。すると、上記で説明したイベントがトリガーされて、RunForm クラスがリクエストを処理することになります。実際にはフォームの番号を入力するのは、このチュートリアルのためのテストの手段にすぎません。イベントをトリガーする方法は他にもあります。より実際的な例としては、Web ページのリンクをカスタマイズする方法、SMS (Short Message Service) によって送信されたメッセージや、目的の場所に近づいたことがわかる位置情報などを利用する方法、さらには QR コード (Quick Response コード) のスキャンを利用する方法などもあります。

フォームを実行する

RunForm クラスは、このアプリケーションのコレオグラファーです。このクラスは、処理対象のフォーム番号を指定して起動されます。今度は、リスト 6 に記載する onCreate() メソッドを見てください。

リスト 6. onCreate() メソッド
public class RunForm extends Activity {
    /** Called when the activity is first created. */
           String tag = RunForm.class.getName();
           XmlGuiForm theForm;
           ProgressDialog progressDialog;
           Handler progressHandler;

           @Override
    public void onCreate(Bundle savedInstanceState) {
           super.onCreate(savedInstanceState);
           String formNumber = "";
           Intent startingIntent = getIntent();
           if(startingIntent == null) {
              Log.e(tag,"No Intent?  We're not supposed to be here...");
              finish();
              return;
           }
              formNumber = startingIntent.getStringExtra("formNumber");
              Log.i(tag,"Running Form [" + formNumber + "]");
              if (GetFormData(formNumber)) {
                     DisplayForm();
          }
          else
          {
                  Log.e(tag,"Couldn't parse the Form.");
                  AlertDialog.Builder bd = new AlertDialog.Builder(this);
                  AlertDialog ad = bd.create();
                  ad.setTitle("Error");
                  ad.setMessage("Could not parse the Form data");
                  ad.show();

          }
    }
         // other methods omitted and shown later
}

リスト 6 のコードを見るとわかるように、最初に行っているのは、Activity をトリガーした Intent から formNumber を抽出するという操作です。処理対象のフォーム番号がなければ、この Activity は何も実行しません。

値を抽出した後に必要となるのは、サーバーに接続してフォームの仕様をダウンロードすることです (データをフェッチする前に、このフォームのメタデータをローカル・キャッシュで検索する処理を追加することも考えられます)。リスト 7 に、GetFormData() メソッドを記載します。

リスト 7. GetFormData() メソッド
   private boolean GetFormData(String formNumber) {
   try {
      Log.i(tag,"ProcessForm");
      URL url = new URL("http://www.example.com/xmlgui" + formNumber + ".xml");
      Log.i(tag,url.toString());
      InputStream is = url.openConnection().getInputStream();
      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
      DocumentBuilder db = factory.newDocumentBuilder();
      Document dom = db.parse(is);
      Element root = dom.getDocumentElement();
      NodeList forms = root.getElementsByTagName("form");
      if (forms.getLength() < 1) {
         // nothing here??
         Log.e(tag,"No form, let's bail");
         return false;
      }
      Node form = forms.item(0);
      theForm = new XmlGuiForm();

      // process form level
      NamedNodeMap map = form.getAttributes();
      theForm.setFormNumber(map.getNamedItem("id").getNodeValue());
      theForm.setFormName(map.getNamedItem("name").getNodeValue());
      if (map.getNamedItem("submitTo") != null)
         theForm.setSubmitTo(map.getNamedItem("submitTo").getNodeValue());
      else
         theForm.setSubmitTo("loopback");

      // now process the fields
      NodeList fields = root.getElementsByTagName("field");
      for (int i=0;i<fields.getLength();i++) {
         Node fieldNode = fields.item(i);
         NamedNodeMap attr = fieldNode.getAttributes();
         XmlGuiFormField tempField =  new XmlGuiFormField();
         tempField.setName(attr.getNamedItem("name").getNodeValue());
         tempField.setLabel(attr.getNamedItem("label").getNodeValue());
         tempField.setType(attr.getNamedItem("type").getNodeValue());
         if (attr.getNamedItem("required").getNodeValue().equals("Y"))
            tempField.setRequired(true);
         else
            tempField.setRequired(false);
         tempField.setOptions(attr.getNamedItem("options").getNodeValue());
         theForm.getFields().add(tempField);
      }

      Log.i(tag,theForm.toString());
      return true;
   } catch (Exception e) {
      Log.e(tag,"Error occurred in ProcessForm:" + e.getMessage());
      e.printStackTrace();
      return false;
   }
}

このコードの役割は、メタデータ・リポジトリーからデータを取得することです。以下の例では、Web サーバーから XML ファイルをダウンロードすることによってデータを取得します。

URL url = new URL("http://10.211.55.2/~fableson/xmlgui" + formNumber + ".xml");

XML データを DOM パーサーで操作して、フォーム要素とフィールド要素を属性と併せて抽出し、XmlGuiForm クラスと XmlGuiFormField クラスのインスタンスにそれぞれの要素を保存します。このメソッドの大部分は、もっぱら構文解析とデータ取り込みのタスクに費やされています。

XML を構文解析する主な方法としては、DOM と SAX の 2 つがあります。DOM パーサーの場合、文書を構文解析してメモリーに展開し、それからアプリケーションが DOM (Document Object Model) ツリーをウォークして、XML に含まれるデータの各種要素にアクセスします。この例では、2 つのクラスにデータを取り込むことによって独自の文書表現を作成するため、SAX パーサー・モデルを使用することもできます。

このアプリケーションにとって DOM の手法を使用するメリットは、DOM にはある程度手続き型の側面があるため、コードを理解しやすいからです。一方、SAX の手法では、目的のデータだけを保存するためのコールバック関数が必要になります。開発者が SAX のコールバック関数を実装するために作成するコードは、場合によっては DOM の手法に比べて大幅に複雑になることがあります。DOM の手法では XML データが完全に構文解析されるため、多少メモリーを多く使います。それでもメタデータのフォームはかなり小さいことから、このアプリケーションの目的を考えると、メモリーの管理よりも、その単純さと理解しやすさが DOM を使用する大きな動機となりました。

Android で XML パーサーをコーディングする方法については、「参考文献」に記載されている優れた資料を参照してください。

ここまでの手順で、XML メタデータのフォームを Java クラスのインスタンスに変換しました。次はいよいよ、このフォームを表示してユーザーからデータを収集します。


ユーザー・データの収集

メインの Activity の画面レイアウトは作成できたので、次はデータを収集するためのユーザー・インターフェース・フォームの作成に移ります。作成するのは、ロボット・クラブ登録フォームと自動車整備調査フォームです。

メタデータを使用する

このアプリケーションは、Android プログラマーが動的にユーザー・インターフェースを扱う手腕にかかっています。先ほど詳しく説明した main.xml ファイルは、XmlGui クラス (メインの Activity) の画面レイアウトを定義するファイルですが、現在のままでは、設計時またはコンパイル時に必ずユーザー・インターフェースの要素を定義しなければならない場合には、このアプリケーションを動作させることは事実上不可能です。

幸い、この方法に束縛されることはありません。DisplayForm() メソッドが、メタデータをデータ収集用のユーザー・インターフェース要素に変換してくれるからです。このメソッドのコードは基本的に 2 つの主要な機能に分けられます。1 つはユーザー・インターフェース要素のレイアウト、もう 1 つは送信ボタンの処理です。

まず、レイアウト・ロジックについて詳しく見てみましょう。レイアウト・ロジックとは、XmlGuiForm オブジェクトを実際の画面上のフォームに変換するコードです。リスト 8 にこのコードを記載します。

リスト 8. レイアウト・ロジック
    private boolean DisplayForm()
    {

        try
        {
            ScrollView sv = new ScrollView(this);

        final LinearLayout ll = new LinearLayout(this);
        sv.addView(ll);
        ll.setOrientation(android.widget.LinearLayout.VERTICAL);

        // walk through the form elements and dynamically create them, 
        // leveraging the mini library of tools.
        int i;
        for (i=0;i<theForm.fields.size();i++) {
            if (theForm.fields.elementAt(i).getType().equals("text")) {
                    theForm.fields.elementAt(i).obj = new
                    XmlGuiEditBox(this,(theForm.fields.elementAt(i).isRequired()
                    ? "*" : "") + theForm.fields.elementAt(i).getLabel(),"");
                    ll.addView((View) theForm.fields.elementAt(i).obj);
            }
            if (theForm.fields.elementAt(i).getType().equals("numeric")) {
                    theForm.fields.elementAt(i).obj = new
                    XmlGuiEditBox(this,(theForm.fields.elementAt(i).isRequired() 
                    ? "*" : "") + theForm.fields.elementAt(i).getLabel(),"");
                    ((XmlGuiEditBox)theForm.fields.elementAt(i).obj).makeNumeric();
                    ll.addView((View) theForm.fields.elementAt(i).obj);
            }
            if (theForm.fields.elementAt(i).getType().equals("choice")) {
                    theForm.fields.elementAt(i).obj = new
                    XmlGuiPickOne(this,(theForm.fields.elementAt(i).isRequired()
                    ? "*" : "") + theForm.fields.elementAt(i).getLabel(), 
                    theForm.fields.elementAt(i).getOptions());
                    ll.addView((View) theForm.fields.elementAt(i).obj);
            }
        }


        Button btn = new Button(this);
        btn.setLayoutParams(new LayoutParams
        (ViewGroup.LayoutParams.WRAP_CONTENT,ViewGroup.LayoutParams.
        WRAP_CONTENT));

        ll.addView(btn);

        btn.setText("Submit");
        btn.setOnClickListener(new Button.OnClickListener() {
            public void onClick(View v) {
                // check if this form is Valid
                if (!CheckForm())
                {
                    AlertDialog.Builder bd = new AlertDialog.Builder(ll.getContext());
                AlertDialog ad = bd.create();
                ad.setTitle("Error");
                ad.setMessage("Please enter all required (*) fields");
                ad.show();
                return;

                }
                if (theForm.getSubmitTo().equals("loopback")) {
                    // just display the results to the screen
                    String formResults = theForm.getFormattedResults();
                    Log.i(tag,formResults);
                    AlertDialog.Builder bd = new AlertDialog.Builder(ll.getContext());
                AlertDialog ad = bd.create();
                ad.setTitle("Results");
                ad.setMessage(formResults);
                ad.show();
                return;

                } else {
                    if (!SubmitForm()) {
                        AlertDialog.Builder bd = new AlertDialog.Builder(ll.getContext());
                    AlertDialog ad = bd.create();
                    ad.setTitle("Error");
                    ad.setMessage("Error submitting form");
                    ad.show();
                    return;
                    }
                }

            }
        } );

        setContentView(sv);
        setTitle(theForm.getFormName());

        return true;

        } catch (Exception e) {
            Log.e(tag,"Error Displaying Form");
            return false;
        }
    }

皆さんは、1 つの画面に収まる以上のフィールドが使用可能になることをご期待のことでしょう。そこで、ScrollView を親ビューまたはコンテナーとして使用します。ScrollView の中で縦方向の LinearLayout を使用して、各種のユーザー・インターフェース・ウィジェットを縦方向に連なる 1 列の中に編成します。

この手法は以下のように、至って簡単です。

  • XmlGuiForm インスタンスの fields メンバーの中に含まれる一連の XmlGuiFormField オブジェクトを列挙します。
  • リクエストされたフィールドのタイプに応じて、異なるユーザー・インターフェース要素をインスタンス化し、LinearLayout に追加します。各種の UI ウィジェットについては、この後すぐに説明します。

UI 要素が作成されてリニア・レイアウトに追加されたら、ScrollView インスタンス全体をこの画面のコンテンツに割り当て、フォーム名を画面のタイトルに割り当てます。図 9 に、ユーザーが入力できる状態のロボット・クラブ登録画面を示します。このフォームは、リスト 1 に記載した XML データを処理した結果、表示されるフォームです。

図 9. 画面に表示されたロボット・クラブ登録フォーム
ロボット・クラブ登録フォームのスクリーン・キャプチャー

ここからは、このアプリケーション用に作成した各種のカスタム・ユーザー・インターフェース・ウィジェットを見ていきましょう。

このアプリケーション用に定義したデータ入力フィールドには、テキスト・フィールド、数値フィールド、選択フィールドの 3 つのタイプがあったことを思い出してください。この 3 つのタイプは、XmlGuiEditBoxXmlGuiPickOne という 2 つの異なるカスタム・ウィジェットによって実装されます。

テキストと数値は似ているため、同じ EditView ウィジェットを利用することができますが、英数字の入力と数字のみの入力とで切り替えるため、それぞれに異なる入力フィルターを適用します。リスト 9 に、XmlGuiEditBox クラスのコードを記載します。

リスト 9. XmlGuiEditBox クラス
package com.msi.ibm;

import android.content.Context;
import android.util.AttributeSet;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.EditText;
import android.text.method.DigitsKeyListener;

public class XmlGuiEditBox extends LinearLayout {
   TextView label;
   EditText txtBox;

   public XmlGuiEditBox(Context context,String labelText,String initialText) {
      super(context);
      label = new TextView(context);
      label.setText(labelText);
      txtBox = new EditText(context);
      txtBox.setText(initialText);
      txtBox.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams
                   .FILL_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT));
      this.addView(label);
      this.addView(txtBox);
   }

   public XmlGuiEditBox(Context context, AttributeSet attrs) {
      super(context, attrs);
      // TODO Auto-generated constructor stub
   }

   public void makeNumeric()
   {
      DigitsKeyListener dkl = new DigitsKeyListener(true,true);
      txtBox.setKeyListener(dkl);
   }
   public String getValue()
   {
      return txtBox.getText().toString();
   }
   public void setValue(String v)
   {
      txtBox.setText(v);
   }
}

XmlGuiEditBox クラスは LinearLayout クラスを継承し、必要な入力を説明するためのテキスト・ラベルと、入力されたデータを実際に収集するための EditText の両方を含めます。オブジェクトの初期化はすべて、コンストラクター内で行われます。優れたフォームではないと思われるかもしれませんが、この方法に満足でない場合、フォームの改善は読者の皆さんの演習として残しておきます。

他に説明しておくべき内容として、3 つのメソッドがあります。そのうちの 2 つ、getValue() および setValue() メソッドはご想像のとおり、EditText フィールドを操作するためのゲッターとセッターです。

3 つ目の makeNumeric() メソッドは、数値用のフォーム・フィールドをセットアップするときにだけ呼び出され、DigitsKeyListener のインスタンスを使用して数字以外のキーをフィルタリングして除外します。もう 1 つ、このクラスの良いところは、使用されている XmlGuiEditBox のタイプ (数値設定の有無) に応じて適切なキーボードが表示されることです。

図 10 に、英字キーボードを表示したフォームを示します。名前 (Last Name) 用のフィールドは英字入力用に設定 (つまり、text に設定) されているため、このキーボードが表示されます。

図 10. 英数字キー入力
英数字キー入力のスクリーン・キャプチャー

年齢用のフィールドに設定されているデータ型は numeric に設定されているため、図 11 に示す数字キーボードが表示されます。

図 11. 数字キーボード
数字キーボードのスクリーン・キャプチャー

XmlGuiPickOne クラスによってユーザー・インターフェースに実装される選択フィールドは、他の 2 つとは多少異なります。選択フィールドは、Android では Spinner ウィジェットとして実装されます。このユーザー・インターフェース要素は他のプログラミング環境でのドロップダウン・リスト・ボックスと同様で、ユーザーが既存の選択項目のなかから 1 つを選択しなければなりません。図 12 には、XmlGuiPickOne ウィジェットの 3 つのインスタンスが表示されています。

図 12. 3 つの XmlGuiPickOne インスタンスによる自動車整備調査
3 つの XmlGuiPickOne インスタンスによる自動車整備調査のスクリーン・キャプチャー

この例で収集されるデータは統計に使用されるため、選択可能なエントリーを標準化することで、テキスト入力フィールドを扱う場合よりも簡潔にデータを処理することができます。言うまでもなく、「State (州)」フィールドも選択フィールドとして定義すれば、調査を特定の地域に絞ることができます。

リスト 10 に、XmlGuiPickOne クラスのコードを記載します。

リスト 10. XmlGuiPickOne クラス
package com.msi.ibm;

import android.content.Context;
import android.util.AttributeSet;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Spinner;
import android.widget.ArrayAdapter;

public class XmlGuiPickOne extends LinearLayout {
   String tag = XmlGuiPickOne.class.getName();
   TextView label;
   ArrayAdapter<String> aa;
   Spinner spinner;

   public XmlGuiPickOne(Context context,String labelText,String options) {
      super(context);
      label = new TextView(context);
      label.setText(labelText);
      spinner = new Spinner(context);
      String []opts = options.split("\\|");
      aa = new ArrayAdapter<String>( context,
           android.R.layout.simple_spinner_item,opts);
      spinner.setAdapter(aa);
      this.addView(label);
      this.addView(spinner);
   }

   public XmlGuiPickOne(Context context, AttributeSet attrs) {
      super(context, attrs);
      // TODO Auto-generated constructor stub
   }


   public String getValue()
   {
      return (String) spinner.getSelectedItem().toString();
   }

}

このクラスは XmlGuiEditBox クラスとかなり似ていますが、大きく異なるのは EditText コントロールではなく、Spinner コントロールが使用されている点です。また、このクラスが実装しているメソッドは唯一、getValue() だけであることにも注目してください。このクラスに手を加えるとしたら、それは明らかに、ユーザーがデフォルト値を指定できるようにすることです。

もう 1 つの注目すべき点は、選択項目のリストを取り込むために options メンバーが使用されていることです。このコードでは、選択可能な項目が含まれる String が regex 式を使用した配列に分割されてから、ArrayAdapter のインスタンスに渡されます。android.R.layout.simple_spinner_item は Android に組み込まれている定数です。この定数はチュートリアルのアプリケーション・コードには含まれていませんが、アダプターのセットアップが完了した時点で Spinner に割り当てます。図 13 に、画面に表示された選択項目のリストを示します。ユーザーはこのリストから、オイル交換の目安とする走行距離 (マイル数) を選択します。

図 13. オイル交換に関して質問する XmlGuiPickOne
オイル交換の間隔とするマイル数を選択するよう求める XmlGuiPickOne のスクリーン・キャプチャー

以上の手順で、ユーザーがフォームにデータを入力できるようになりました。次は、データを検証して送信します。


データの保存および送信

次に必要な作業は、データを検証してサーバーに送信することによって、ユーザーがデータを保存できるようにすることです。

データを保存する

ここで、RunForm クラスの DisplayForm() メソッドに再び戻ります。このメソッドの前半は、前にも説明したようにフォームを描画する役割を担っています。今度は後半の、送信ボタンの onClick() ハンドラーについて見ていきます (リスト 11 を参照)。

リスト 11. onClick() ハンドラー
btn.setOnClickListener(new Button.OnClickListener() {
   public void onClick(View v) {
       // check if this form is Valid
       if (!CheckForm())
       {
           AlertDialog.Builder bd = new AlertDialog.Builder(ll.getContext());
               AlertDialog ad = bd.create();
               ad.setTitle("Error");
               ad.setMessage("Please enter all required (*) fields");
               ad.show();
               return;
       }
       if (theForm.getSubmitTo().equals("loopback")) {
           // just display the results to the screen
           String formResults = theForm.getFormattedResults();
           Log.i(tag,formResults);
           AlertDialog.Builder bd = new AlertDialog.Builder(ll.getContext());
           AlertDialog ad = bd.create();
               ad.setTitle("Results");
               ad.setMessage(formResults);
               ad.show();
               return;
       } else {
           if (!SubmitForm()) {
               AlertDialog.Builder bd = new AlertDialog.Builder(ll.getContext());
           AlertDialog ad = bd.create();
           ad.setTitle("Error");
           ad.setMessage("Error submitting form");
           ad.show();
           return;
           }
       }
   }
} );

ユーザーが送信ボタンを選択すると、必須フィールドがすべて入力されていることを確実にするために、フォームへの入力内容がチェックされます。入力されていない必須フィールドがあると、AlertDialog がすべてのフィールドを入力するようにユーザーに注意を促します。適切にデータが入力されているという前提で、次はデータを送信します。

このチュートリアルのアプリケーションでは、データの送信プロセスは 2 つのシナリオのいずれかに分かれます。フォームの submitTo フィールドがループバックの値に設定されるシナリオでは、値が単純に画面にエコー出力されるだけです。この方法は、テストの際に役立ちます。フォーム・ツールが適切にデータを収集していると納得した時点で、今度は入力内容を記録するためのサーバー・ページを指すようにフィールドを設定します。

リスト 12 に、CheckForm() メソッドを記載します。このコードは比較的単純で、各フィールドをチェックして、それが必須フィールドであるかどうかを調べます。そしてフィールドが必須フィールドであるのに、ユーザーが情報を入力していなければ、フラグをセットするというだけです。このコードには、より具体的なフィードバックをユーザーに提供するように処理を追加することもできます。

リスト 12. CheckForm() メソッド
private boolean CheckForm()
{
    try {
       int i;
       boolean good = true;


       for (i=0;i<theForm.fields.size();i++) {
                   String fieldValue = (String)
               theForm.fields.elementAt(i).getData();
                   Log.i(tag,theForm.fields.elementAt(i)
               .getName() + " is [" + fieldValue + "]");
                   if (theForm.fields.elementAt(i).isRequired()) {
                       if (fieldValue == null) {
                           good = false;
                       } else {
                           if (fieldValue.trim().length() == 0) {
                               good = false;
                           }
                       }

                   }
       }
       return good;
    } catch(Exception e) {
       Log.e(tag,"Error in CheckForm()::" + e.getMessage());
       e.printStackTrace();
       return false;
    }
}

次に必要なのは、収集したデータをサーバーに送信することです。リスト 13 に記載する SubmitForm() メソッドを見てください。

リスト 13. SubmitForm() メソッド
private boolean SubmitForm()
{
       try {
                   boolean ok = true;
       this.progressDialog = ProgressDialog.show(this, 
         theForm.getFormName(), "Saving Form Data", true,false);
       this.progressHandler = new Handler() {

               @Override
               public void handleMessage(Message msg) {
                   // process incoming messages here
                   switch (msg.what) {
                       case 0:
                           // update progress bar
                           progressDialog.setMessage("" + (String) msg.obj);
                           break;
                       case 1:
                           progressDialog.cancel();
                           finish();
                           break;
                       case 2:
                               progressDialog.cancel();
                               break;
                   }
                   super.handleMessage(msg);
               }

       };

       Thread workthread = new Thread(new TransmitFormData(theForm));

       workthread.start();

               return ok;
       } catch (Exception e) {
               Log.e(tag,"Error in SubmitForm()::" + e.getMessage());
               e.printStackTrace();
       // tell user that the submission failed....
       Message msg = new Message();
       msg.what = 1;
       this.progressHandler.sendMessage(msg);

               return false;
       }

}

まず、android.os.Handler クラスのインスタンスをセットアップします。Handler クラスは、アプリケーションが他の異なるスレッドとデータを共有しなければならない場合に役立ちます。SubmitForm() メソッドでもう 1 つ重要な点は、ProgressDialog を使用していることです。リスト 14 では、ProgressDialogHandler がクラス・レベルの変数として定義されていることに注意してください。

リスト 14. ProgressDialog および Handler
public class RunForm extends Activity {
    /** Called when the activity is first created. */
      String tag = RunForm.class.getName();
      XmlGuiForm theForm;
      ProgressDialog progressDialog;
      Handler progressHandler;
     ...
}

サーバーと通信している間、アプリケーションを不必要にブロックすることはしたくないはずです。そこで、通信にはバックグラウンド・スレッドを使用しますが、通信スレッドからの通知の受信に関しては、ユーザーにフィードバックを提供できるようにするために Handler を利用します。ここではメイン・スレッドだけがユーザー・インターフェースとインターフェースを取ることになりますが、別個のスレッドを使用する代わりに、android.os パッケージに含まれる AsyncTask クラスを使用するという方法もあります。

アプリケーションがデータを転送するためにサーバーに接続する際には、この処理の状況をユーザーに通知することができます。もちろんこれは、望ましいことです。図 14 に、動作中の ProgressDialog を示します。

図 14. ProgressDialog
動作中の ProgressDialog のスクリーン・キャプチャー

実際にサーバーと通信するためのコードは、Runnable インターフェースを実装する TransmitFormData() クラスに含まれています (リスト 15 を参照)。

リスト 15. TransmitFormData クラス
   private class TransmitFormData implements Runnable
   {
   XmlGuiForm _form;
   Message msg;
   TransmitFormData(XmlGuiForm form) {
       this._form = form;
   }

   public void run() {

       try { 
                msg = new Message();
                msg.what = 0;
                msg.obj = ("Connecting to Server");
                progressHandler.sendMessage(msg);

                URL url = new URL(_form.getSubmitTo());
                URLConnection conn = url.openConnection();
                conn.setDoOutput(true);
                BufferedOutputStream wr = new BufferedOutputStream
                       (conn.getOutputStream());
                String data = _form.getFormEncodedData();
                wr.write(data.getBytes());
                wr.flush();
                wr.close();
 
                msg = new Message();
                msg.what = 0;
                msg.obj = ("Data Sent");
                progressHandler.sendMessage(msg);

                // Get the response
                BufferedReader rd = new BufferedReader(new
                 InputStreamReader(conn.getInputStream()));
                String line = "";
                Boolean bSuccess = false;
                while ((line = rd.readLine()) != null) {
                       if (line.indexOf("SUCCESS") != -1) {
                           bSuccess = true;
                       }
                       // Process line...
                       Log.v(tag, line);
                }
                wr.close();
                rd.close();

                if (bSuccess) {
                       msg = new Message();
                       msg.what = 0;
                       msg.obj = ("Form Submitted Successfully");
                       progressHandler.sendMessage(msg);

                       msg = new Message();
                       msg.what = 1;
                       progressHandler.sendMessage(msg);
                       return;

                }
       } catch (Exception e) {
                Log.d(tag, "Failed to send form data: " + e.getMessage());
                msg = new Message();
                msg.what = 0;
                msg.obj = ("Error Sending Form Data");
                progressHandler.sendMessage(msg);
       }
       msg = new Message();
       msg.what = 2;
       progressHandler.sendMessage(msg);
   }

   }

TransmitFormData クラスの役割は、(メタデータから取得された) XmlGuiForm インスタンスの submitTo メンバーに含まれているサーバーに接続することです。このクラスは、sendMessage() メソッドを介して Message クラスのインスタンスをハンドラーに送信することによって、定期的にメインのアプリケーション・スレッドを更新します。Message クラスでは、以下の 2 つのメンバーに値が入力されます。

  • what の値は、メッセージに対してどのような処理を行うべきかを Handler に指示するための上位レベルでのスイッチの役割を果たします。
  • obj の値は、オプションの java.lang.Object を指定します。この例の場合、java.lang.String インスタンスが渡されて、進行状況を示すダイアログを表示するために使用されます。

どのアプリケーションでも、使用するスキーマは任意です。このアプリケーションでは表 3 の値を使用します。

表 3. whatに許容されるアプリケーションの値
注記
0ユーザーに表示するテキスト・ストリングが obj に含まれます。
1正常に送信されました。これで登録は完了です。
2エラーが発生しました。ユーザーにエラーが発生したことを通知し、データを破棄しないように指示する必要があります。

図 15 は、フォーム・データの送信が正常に完了すると表示される ProgressDialog のメッセージです。

図 15. フォームの送信完了
フォームの送信完了メッセージのスクリーン・キャプチャー

フォームが正常に送信されると、アプリケーションはメイン・ページに戻ります。本番に即したアプリケーションの場合、次に行われる内容は、組織がデータを収集する動機に大きく依存します。例えば、実地棚卸用アプリケーションでの場合と同じように、別のエントリーを収集するために画面をリセットするだけにすることも、あるいはユーザーに他の画面を表示することもできます。

アプリケーションが正常に実行されるためには、AndroidManifest.xml ファイルに、使用するすべての Activity クラスへの参照が含まれていること、そしてインターネット・アクセスの使用権限が組み込まれていることが必須です。リスト 16 に、このチュートリアルのアプリケーションの場合の AndroidManifest.xml ファイルを記載します。

リスト 16. AndroidManifest.xml ファイル
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="com.msi.ibm"
      android:versionCode="1"
      android:versionName="1.0">
      <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name=".XmlGui"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".RunForm">
        </activity>
    </application>
<uses-permission android:name="android.permission.INTERNET"></uses-permission>

</manifest>

このチュートリアルを締めくくる前に、サーバー・サイドのスクリプトについて簡単に説明しておきます。


サーバー・サイド・スクリプトの提供

このチュートリアルでは、PHP スクリプトを使用して必要なデータを収集し、そのデータをテキスト・ファイルに追加します。

サーバー・サイドでの処理

サーバー上で行われる処理の内容は、データを収集する組織が何を必要としているかによって異なります。一般的なデータ収集手法は、フォーム・データを DB2®、MySQL、SQL Server、Oracle などのリレーショナル・データベースに保存することです。データベースに保存した後は、データを細分化して分析できます。

このチュートリアルでは、データを PHP スクリプトによって収集し、テキスト・ファイルに追加します。リスト 17 に、ロボット登録フォームに関連する PHP フォームを記載します。

リスト 17. ロボット登録フォームに関連する PHP フォーム
<?php
// xmlgui form # 1
// this page is expecting
// fname
// lname
// gender
// age


$filename = "/pathtowritablefile/datafile.txt";


$f = fopen($filename,"a");
fprintf($f,"Data received @ ".date(DATE_RFC822));
fprintf($f,"\n");
fprintf($f,'First Name:['.$_POST['fname'].']');
fprintf($f,"\n");
fprintf($f,'Last Name:['.$_POST['lname'].']');
fprintf($f,"\n");
fprintf($f,'Gender:['.$_POST['gender'].']');
fprintf($f,"\n");
fprintf($f,'Age:['.$_POST['age'].']');
fprintf($f,"\n");
fclose($f);
print "SUCCESS";
?>

このスクリプトが SUCCESS というストリングを返すと、RunForm クラスがリセットされます。それ以外の値が返されると、エラー・メッセージが表示されるので、ユーザーは入力内容を修正するか、あるいはフォームの送信に関するヘルプを見ることができます。


まとめ

このチュートリアルでは、Android SDK を利用したネイティブ・アプリケーションの手法をベースに、動的な質問を Android ユーザーに提示するためのフレームワークを取り上げました。そして、動的にフォームを表示、検証、処理する手法について、さらにはアプリケーションと Web サーバーとの間の通信について説明しました。


ダウンロード

内容ファイル名サイズ
Forms engine source codeformsengine.zip78KB

参考文献

学ぶために

  • Develop Android applications with Eclipse」(Frank Ableson 著、developerWorks、2008年2月): この developerWorks のチュートリアルでは、Android アプリケーションを開発するのに最も簡単な方法として、Eclipse を使用した開発方法を説明しています。
  • Android によるネットワーキング」(Frank Ableson 著、developerWorks、2009年6月): Android のネットワーキング機能について学んでください。
  • Android で XML を扱う」(Michael Galpin 著、developerWorks、2009年6月): Android で XML を扱う際のさまざまなオプション、そしてこれらのオプションを使って独自の Android アプリケーションを構築する方法を説明しています。
  • Unlocking Android, 2nd Edition』(Frank Ableson、Robi Sen、Chris King 共著、Manning Publications、2010年12月): Android 開発のすべての側面を取り上げているこの本では、Android オペレーティング・システムおよび開発ツールの使用方法を簡潔かつ具体的に説明しています。
  • Mobile Design and Development』(Brian Fling 著、O'Reilly Media、2009年): モバイル製品を作成する際の実用的ガイドライン、標準、手法、そしてベスト・プラクティスについて説明しているこの本を読んでください。
  • Android SDK の資料: Android API リファレンスで最新情報を入手してください。
  • Open Handset Alliance: Android のスポンサーのサイトにアクセスしてください。
  • Android 開発入門」(Frank Ableson 著、developerWorks、2009年5月): Android プラットフォームの概要を知り、基本的な Android アプリケーションのコードを作成する方法を学んでください。
  • Under the Hood of Native Web Apps for Android」: Android でのハイブリッド・アプリケーションについて学んでください。
  • この著者による他の記事とチュートリアル (Frank Ableson 著、developerWorks、2002年4月から現在まで): Android、XML、Eclipse、GPS、BlackBerry のアプリケーション、そしてその他の技術に関する記事を読んでください。
  • developerWorks の XML エリア: XML 分野でのスキルを磨くための資料を入手してください。
  • My developerWorks: developerWorks のエクスペリエンスを自分流にカスタマイズしてください。
  • IBM XML 認定: XML や関連技術の IBM 認定技術者になる方法について調べてください。
  • XML technical library: 広範な技術に関する記事とヒント、チュートリアル、標準、そして IBM Redbooks については、developerWorks XML ゾーンを参照してください。また、XML に関するヒントを読んでください。
  • developerWorks の Technical events and webcasts: これらのセッションで最新情報を入手してください。
  • Twitter での developerWorks: 今すぐ登録して developerWorks のツイートをフォローしてください。
  • developerWorks podcasts: ソフトウェア開発者向けの興味深いインタビューとディスカッションを聞いてください。

製品や技術を入手するために

  • Android SDK: Android SDK をダウンロードしてください。この公式 Android 開発者向けサイトでは、API リファレンスや Android に関する最新ニュースも入手できます。この記事の演習には、バージョン 1.5 以降を使用することができます。
  • Eclipse: 最新の Eclipse IDE を入手してください。
  • Android Open Source Project: Android はオープンソースなので、ここからソース・コードを入手できます。
  • IBM 製品の評価版: DB2®、Lotus®、Rational®、Tivoli®、および WebSphere® のアプリケーション開発ツールとミドルウェア製品を体験するには、評価版をダウンロードするか、IBM SOA Sandbox のオンライン試用版を試してみてください。

議論するために

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=XML, Web development
ArticleID=588097
ArticleTitle=Android と XML を使って動的なユーザー・インターフェースを作成する
publish-date=09072010