PhoneGapとSymfony2で始めるオフライン対応アプリ開発

2012年6月6日水曜日 投稿者 森怜峰

AndroidやiPhoneのブラウザではHTML5が全てサポートされているわけではないので、PhoneGapでブラウザの差を吸収しつつ、HTML5/JavaScriptで作成したものをアプリ化して、ネットワークに接続されていない状態でも使えるようにします。

HTML5単体でもApplicationCacheなどはあるものの、初回読み込みは必要なので、完全にオフラインで動作させることはできません。

Indexed DB APIはAndroid標準ブラウザでは使えませんので、オフライン状態でかつローカルのデータベースを使いたい場合はPhoneGapやTitaniumなどを使う必要があります。

今回はPhoneGapとjQuery Mobileを使用してオフラインアプリをAndroid向けに開発します。Android向けなんですが、たぶんiOSでも動作します。

ひとまずAndroidでビルドしてみます。
環境はMacですが、Windowsでもさほど変わらない(はず)


Android SDKのインストール

SDKのダウンロードはこちら http://developer.android.com/sdk/index.html


EclipseにADTプラグインを追加

Eclipseの Help > install new software から https://dl-ssl.google.com/android/eclipse/ このURLを指定する。

Android SDK ManagerでToolsとAndroidプラットフォームのSDKをインストール。


PhoneGapの導入

こちら http://phonegap.com/download からPhoneGap1.7を頂戴します。

で、このスタートガイドに従ってHello World的なものをやります。
http://docs.phonegap.com/en/1.7.0/guide_getting-started_android_index.md.html#Getting%20Started%20with%20Android

1. 適当にAndroidプロジェクトを作る
2. assets/www ディレクトリを作成する
3. libs ディレクトリを作成する
4. cordova-1.7.0.js を assets/www/ に配置
5. cordova-1.7.0.jar を libs/ に配置
6. PhoneGapの xml というフォルダを res/ に配置
7. JavaのBuild Pathのライブラリで、libs/cordova-1.7.0.jar を追加


Activityを書き換える

    package reoring.phonegap.test23;
    
    import android.os.Bundle;
    
    // PhoneGap
    import org.apache.cordova.*;
    
    public class PhoneGapTest23Activity extends DroidGap {
      @Override
      public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        super.loadUrl("file:///android_asset/www/index.html");
      }
    }

assets/www/index.html を書く

    <html>
      <head>
        <title>PhoneGap Test</title>
        <script type="text/javascript" charset="utf-8" src="cordova-1.7.0.js"></script>
      </head>

      <body>
  	    <h1>Hello PhoneGap on Android</h1>
      </body>
    </html>


jQuery Mobileを配置

assets/js/jquery.mobile に jQuery Mobileからダウンロードしてきたアーカイブを展開します。
jquery.mobile-1.1.0.css などがあればOK

HTMLを書き換え

    <!DOCTYPE HTML>
    <html>
      <head>
        <title>PhoneGap Test</title>
        
        <meta charset="UTF-8">
    
        <meta name="viewport" content="width=device-width, initial-scale=1">
        
        <link rel="stylesheet" href="js/jquery.mobile/jquery.mobile-1.1.0.min.css"/>
        <link rel="stylesheet" href="js/jquery.mobile/jquery.mobile.theme-1.1.0.min.css"/>
        
        <script type="text/javascript" src="js/jquery-1.7.2.min.js"></script>
        <script type="text/javascript" src="js/jquery.mobile/jquery.mobile-1.1.0.js"></script>
        
        <script type="text/javascript" charset="utf-8" src="cordova-1.7.0.js"></script>
      </head>
    
      <body>
    
    	<div data-role="page">
          <div data-role="header">
            <h1>タイトルエリア</h1>
          </div>
    	
          <div data-role="content">
            <p>コンテンツエリア</p>
          </div>
    	
          <div data-role="footer">
            <h4>フッターエリア</h4>
          </div>
        </div>
      </body>
    </html>

これでひとまずPhoneGap上でjQuery Mobileが動作しました。

-------------------------------------------


Behat + MinkでATDDを実施

Symfony2のBehat Bundleで受け入れテストを日本語で記述して自動化します。
ブラウザの駆動ドライバとして今回はSahiを使います。

ATDD(Acceptance Test Driven Development)はその名の通り受け入れテストを自動化します。
受け入れテストをソースを書く前に記述して最終的な振る舞いを考慮しながら開発して手戻りを最小限にします。

受け入れテストは日本語で記述することによって振る舞いの確認をお客さんにして頂けるように考慮します。


Sahiのインストール

Tyto Softwareさんのページからダウンロードします。http://sahi.co.in/w/sahi-os-vs-sahi-pro

ダウンロードしたらインストールして、インストールディレクトリにあるsahi.shを起動します。
Macの環境だとbinに移動してからsahi.shを実行しないとなぜかエラーになるので注意してね。

正常に実行できるとコンソールに下記のようなメッセージがでます。

--------
SAHI_HOME: ..
SAHI_USERDATA_DIR: ../userdata
SAHI_EXT_CLASS_PATH:
--------
Sahi properties file = /Users/morireo/sahi/config/sahi.properties
Sahi user properties file = /Users/morireo/sahi/userdata/config/userdata.properties
Added shutdown hook.
>>>> Sahi started. Listening on port: 9999
>>>> Configure your browser to use this server and port as its proxy
>>>> Browse any page and CTRL-ALT-DblClick on the page to bring up the Sahi Controller
-----
Reading browser types from: /Users/morireo/sahi/userdata/config/browser_types.xml
-----

Sahiプロキシがポート9999で立ち上がります。
この状態で http://localhost:9999/ などどやるとStart URL:という画面になるので、これが出ていれば正常に動作しています。

 

Symfony2でSahiとBehat+minkの設定

test.phonegap.localをVirtualHostの設定でweb/phonegap/に向けます。

behat+minkの設定

app/config/config_test.yml を作ります。

behat: ~

mink:
    base_url:   http://test.phonegap.local/

    sahi:
      host:     %sahi_host%

    browser_name: firefox
    show_cmd:   open %s
    default_session: sahi
    # default_session: symfony
    goutte: ~

FeatureContextを少し書き換える

BehatのFeatureContextを少し書き換えます。extends BehatContextの部分を、MinkContextにします。

namespace Phonegap\TestBundle\Features\Context;

use Behat\BehatBundle\Context\BehatContext,
    Behat\BehatBundle\Context\MinkContext;

use Behat\Behat\Context\ClosuredContextInterface,
    Behat\Behat\Context\TranslatedContextInterface,
    Behat\Behat\Exception\PendingException;

use Behat\Gherkin\Node\PyStringNode,
    Behat\Gherkin\Node\TableNode;

/**
 * Feature context.
 *
 * BehatContext or MinkContext
 */
class FeatureContext extends MinkContext
{
  public function __construct($parameters)
  {
    parent::__construct($parameters);
  }
}

フィーチャの記述

ここからはSymfony2で行います。Symfony2で予めBehatとMinkのバンドルをインストールする必要があります。インストール方法は割愛しますが、https://github.com/reoring/phonegap_bdd_sample に動作するサンプルを置いてあります

下記のようなフィーチャファイルをsrc/Phonegap/TestBundle/Features/phonegap.feature として記述します。

先頭行の# language: ja とすることでフィーチャの記述が日本語で記述できます。

このフィーチャではMinkの組み込みステップを使っています。Appendixに組み込みステップの一覧を記述しています。

# language: ja
フィーチャ: PhoneGapのコンテンツをテスト
  背景:

  シナリオ: トップページからメニューへの遷移
    前提 ユーザーは "/index.html" を表示している
    ならば レスポンスに "コンテンツエリア" が含まれていること
    もし ユーザーが "メニュー" のリンク先へ移動する
    ならば 画面に "メニュー表示" と表示されていること

これを書いたら実行します。

./app/console behat -e=test src/Phonegap/TestBundle/Features/phonegap.feature
フィーチャ: PhoneGapのコンテンツをテスト

  背景: # src/Phonegap/TestBundle/Features/phonegap.feature:3

  シナリオ: トップページからメニューへの遷移                 # src/Phonegap/TestBundle/Features/phonegap.feature:5
    前提 ユーザーは "/index.html" を表示している           # Phonegap\TestBundle\Features\Context\FeatureContext::visit()
    ならば レスポンスに "コンテンツエリア" が含まれていること # Phonegap\TestBundle\Features\Context\FeatureContext::assertResponseContains()
    もし ユーザーが "メニュー" のリンク先へ移動する          # Phonegap\TestBundle\Features\Context\FeatureContext::clickLink()
    ならば 画面に "メニュー表示" と表示されていること        # Phonegap\TestBundle\Features\Context\FeatureContext::assertPageContainsText()

1 scenario (1 passed)
4 steps (4 passed)
0m9.213s

実行するとブラウザが自動的に起動してテストが実行されます。

Appendix.

Minkで使える組み込みステップ


Given /^(?:|ユーザーは )"(?P<page>[^\s]+)" を表示している$/
When /^(?:|ユーザーが )"(?P<page>[^\s]+)" へ移動する$/
When /^(?:|ユーザーが )ページをリロードする$/
When /^(?:|ユーザーが )履歴の前のページに戻る$/
When /^(?:|ユーザーが )履歴の次のページヘ進む$/
When /^(?:|ユーザーが )"(?P<button>(?:[^"]|\\")*)" ボタンをクリックする$/
When /^(?:|ユーザーが )"(?P<link>(?:[^"]|\\")*)" のリンク先へ移動する$/
When /^(?:|ユーザーが )"(?P<field>(?:[^"]|\\")*)" フィールドに "(?P<value>(?:[^"]|\\")*)" と入力する$/
When /^(?:|ユーザーが )"(?P<value>(?:[^"]|\\")*)" という値を "(?P<field>(?:[^"]|\\")*)" に入力する$/
When /^(?:|ユーザーが)次のように入力する:$/
When /^(?:|ユーザーが )"(?P<option>(?:[^"]|\\")*)" という値を "(?P<select>(?:[^"]|\\")*)" から選択する$/
When /^(?:|I )additionally select "(?P<option>(?:[^"]|\\")*)" from "(?P<select>(?:[^"]|\\")*)"$/
When /^(?:|ユーザーが )"(?P<option>(?:[^"]|\\")*)" にチェックをつける$/
When /^(?:|ユーザーが )"(?P<option>(?:[^"]|\\")*)" のチェックをはずす$/
When /^(?:|ユーザーが)パス "(?P<path>[^"]*)" にあるファイルを "(?P<field>(?:[^"]|\\")*)" に添付する$/
Then /^(?:|ユーザーが )(?P<page>[^\s]+) を表示していること$/
Then /^the (?i)url(?-i) should match "(?P<pattern>(?:[^"]|\\")*)"$/
Then /レスポンスコードが (?P&gt;code&lt;\d+) であること/
Then /^the response status code should not be (?P\d+)$/
Then /^(?:|画面に )"(?P<text>(?:[^"]|\\")*)" と表示されていること$/
Then /^(?:|画面に )"(?P<text>(?:[^"]|\\")*)" と表示されていないこと$/
Then /^レスポンスに "(?P<text>(?:[^"]|\\")*)" が含まれていること$/
Then /^レスポンスに "(?P<text>(?:[^"]|\\")*)" が含まれていないこと$/
Then /^"(?P<element>[^"]*)" エレメントに "(?P<text>(?:[^"]|\\")*)" と表示されていること$/
Then /^"(?P<element>[^"]*)" エレメントに "(?P<value>(?:[^"]|\\")*)" という値が含まれていること$/
Then /^(?:|画面に )"(?P<element>[^"]*)" エレメントが表示されていること$/
Then /^(?:|画面に )"(?P<element>[^"]*)" エレメントが表示されていないこと$/
Then /^"(?P<field>(?:[^"]|\\")*)" フィールドに "(?P<value>(?:[^"]|\\")*)" が含まれていること$/
Then /^"(?P<field>(?:[^"]|\\")*)" フィールドに "(?P<value>(?:[^"]|\\")*)" が含まれていないこと$/
Then /^チェックボックス "(?P<checkbox>(?:[^"]|\\")*)" のチェックがついていること$/
Then /^チェックボックス "(?P<checkbox>(?:[^"]|\\")*)" のチェックがはずれていること$/
Then /^(?:|I )should see (?P<num>\d+) "(?P<element>[^"]*)" elements?$/
Then /^最後のレスポンスを表示$/
Then /^最後のレスポンスをブラウザで表示$/

 

ラベル: , , , , ,

コメントを投稿