RSS2.0

Ionic 2 を試してみた

あけましておめでとうございます。
年の瀬に Maven Resources Plugin の記事 を書き、お正月だからとだらだらしていたら 3 年経ってしまいました。

それはともかく、少し時間ができたのでスマホアプリの技術でも触ってみようかと思い、Ionic 2で遊んでみました。今回は Ionic 2 のインストールから空のプロジェクトを作ってブラウザから動作確認をし、独自の新ページを追加するところまでです。

スマホアプリというと Android や iPhone の実機をつないで都度ビルドしながらデバッグする、というイメージが強いですが、Ionic 2 などの最近のフレームワークは WEB ベースの技術が組み合わさっているため、実機がなくてもブラウザから動かすことができます。簡単なページは JavaScript や CSS を使って WEB アプリ的に実装し、それで実現できない部分は端末固有の実装として Java や Swift で書いたプラグインに任せよう、という設計思想だそうです。確かに、ゲーム系でないスマホアプリは、突き詰めてしまえばボタンを押しながら画面遷移していくだけの事が多いので、WEB アプリに似せた開発ができるというのは効率的なのだと思います。

Ionic 2 は Cordova というマルチプラットフォーム向けのフレームワークに覆いかぶさる形で作られています。なので Cordova 同様、Ionic 2 で書いたアプリも Android や iOS 用にビルドすることができます。また Ionic 2 の特徴として、AngularJS と切っても切れないほと密接に連携していて、その特徴を色濃く受け継いでいます。データとデータを取り扱うロジック部分をビューから切り離し、そのデータをビューに対して自動的に埋め込ませることができます。このあたりの考え方は、WEB アプリを作ったことのある人には、馴染みの深い MVC モデルの応用のように聞こえますが、MVC モデル自体がデスクトップアプリケーションに端を発しているものなので実際は逆輸入なのかもしれません。

さて、今回の目標としては、似非商品検索アプリを作ってみようと思います。トップ画面から検索画面に遷移し、そこで入力したキーワードから、キーワードを商品名に含む商品の一覧を表示する、という感じの動きです。ただし、今回は実際に DB や WEB サービスから検索するわけではなく、キーワードを商品名っぽく加工して表示するだけとします。
こんな感じの仕上がりを目指していこうと思います。

Ionic 2 をインストールする

Ionic 2 は Node.js の npm コマンドからインストールすることができます。なのでまずは Node.js をインストールしましょう。

Node.js をインストールする

普通 Node.js はパッケージのリポジトリに登録されているので、yum コマンド等でインストールできます。
が、私の開発環境である Fedora 21 では yum リポジトリに古い Node.js しかないので、自分で最新バージョンをインストールしました。

Node.js の公式サイトから推奨版の v6.9.4 LTSをダウンロードし、/usr/node/node-v6.9.4-linux-x64 に展開しました。
# mkdir /usr/node
# chown momokan.momokan /usr/node

$ cd /usr/node
$ wget https://nodejs.org/dist/v6.9.4/node-v6.9.4-linux-x64.tar.xz
$ tar xvfz node-v6.9.4-linux-x64.tar.xz
$ ln -s node-v6.9.4-linux-x64 node

展開したディレクトリの bin 配下に npm コマンドや node コマンドがあるので、ここにパスを通しておきます。今回は ~/.bash_profile を修正しました。
NODEJS_HOME=/usr/node/node

PATH=$PATH:$NODEJS_HOME/bin

~/.bash_profile を書き換えたら source コマンドで再読込みします。
$ source ~/.bash_profile

あとは実際に node コマンドを動かしてバージョンを確認してみます。
$ node -v
v6.9.4
ダウンロードした v6.9.4 で動作しています。OK ですね。

Ionic 2 をインストールする

Node.js をインストールすれば npm コマンドが使えるようになるので、npm コマンドから Ionic 2 をインストールします。冒頭のとおり Ionic 2 は Cordova に依存しているので、こちらもいっしょにインストールする必要があります。
# npm install -g cordova ionic
-g オプションはグローバル領域にインストールすることを指定するオプションです。私は面倒なのでグローバル領域にインストールしてしまいますが、プロジェクト毎にパッケージのバージョンを変えたい、というような場合にはプロジェクト毎のローカル領域にインストールすることもできます。

Ionic 2 をインストールすると ionic コマンドが使えるようになるので、こちらも動かして動作確認してみます。
$ ionic -v
2.2.1
動いていますね。

Ionic 2 で空アプリを作る

それでは実際に Ionic 2 でスマホアプリを作っていきます。まずは空のプロジェクトを動かしてみて、その後に独自の似非商品検索ロジックを実装してみたいと思います。

Ionic2 プロジェクトを作る

Ionic 2 プロジェクトを作るには、以下のように ionic コマンドを実行します。ここではプロジェクト名を helloIonic とします。
$ ionic start --v2 helloIonic blank
blank オプションはプロジェクトのテンプレートの 1 つで、空プロジェクトを作ることを意味します。公式ドキュメント にプロジェクトのテンプレートの一覧がありますが、一通りの UI を確認できる tabs が推奨されているようです。そちらも別途作って動かしてみてください。

Ionic2 プロジェクトをブラウザで動かす

プロジェクトを作ったら、まずは動かしてみましょう。
手元に実機がないので実機ではなくブラウザで動かしてみます。実機で動かす場合には、Android なら Android SDK をインストールしたり、Android 用のビルドをしたりする必要があるようです。詳細はcordova の Android 用ガイドなどを読んでみてください。

ブラウザで動かすだけなら、作成した Ionic 2 プロジェクトのディレクトリ内で ionic serve コマンドを動かすだけで動作確認ができます。
$ cd helloIonic

$ ionic serve

ブラウザが起動すると思うので、http://localhost:8100 にアクセスしてみてください。
blank.png
こんな画面が表示されます。
スマホ端末にビルドした場合、通常のスマホアプリとしてこの画面が表示されることになります。

ブラウザで動かす強みとして、ロジックやテンプレートを修正した場合にその場で再読み込みができます。試しにいま表示されているホーム画面のページ内容を書き換えてみましょう。ホーム画面のテンプレートである src/pages/home/home.html を以下の様に修正します。
<ion-header>
  <ion-navbar>
    <ion-title>
      Ionic Blank
    </ion-title>
  </ion-navbar>
</ion-header>

<ion-content padding>
  Hello Ionic.
</ion-content>
ここでテンプレートファイルに通常の WEB アプリのように HTML を期待していた人は少し面食らうかもしれません。
テンプレートファイルは HTML のようにタグで構成されていますが、実際には拡張されたタグで記述されているため、HTML そのままではありません。

reloaded.png
さて、テンプレートを書き換えると、ブラウザで開いていたホーム画面も自動で再読込みされ、書き換えた内容が表示されました。

Ionic 2 がソースコードの更新を監視し、検知してくれているので、アプリを再起動することなく修正内容を確認することができます。
…とはいいつつも、実際にはさきほどのテンプレートのような Ionic 2 ベースのソースコードをビルドが走する必要があるため、それほどスピード感があるとはいかないかもしれません。ブラウザを利用できる、というのが一番のメリットなのかなと思います。

Ionic 2 プロジェクトの構造

実際にアプリを機能拡張して行く前に、Ionic 2 で作られるプロジェクトのファイル構造について簡単に説明しておきたいと思います。ソースコードが配置されている src ディレクトリ内のファイルが、主だったファイルになります。

src/index.html

アプリのエントリーポイントとなるファイルです。内部は他のテンプレート同様に Ionic 2 独自タグをふくむ HTML になっています。<body> 内にある <ion-app> タグが各ページに置換されるため、このファイル自体にページの内容を書くことはありません。このテンプレートファイルでは cordova.js や CSS の読み込みなど、アプリ全体を通しての共通処理が記述されます。

src/app/app.html

src/index.html の <ion-app> タグから参照されるテンプレートです。内部は <ion-nav> タグのみとなっています。この <ion-nav> タグの [root] 属性値には rootPage 変数が設定されています。rootPage 変数の値は src/app/app.component.ts で設定されています。

src/app/app.module.ts

テンプレートファイル src/app/app.html に対応するロジックです。.ts は TypeScript のことで、Ionic 2 は AngularJS 同様ロジックを TypeScript のコードで記述します。
src/app/app.module.ts では @NgModule アノテーションで、利用する他のページのロジックの読み込みなどを行っています。また、以下のコードで最初に表示される画面のロジックを指定しています。
  imports: [
    IonicModule.forRoot(MyApp)
  ], 
MyApp クラスは以下の import 文にあるとおり、src/app/app.component.ts ファイルから読み込まれています。
import { MyApp } from './app.component';

src/app/app.component.ts

最初に表示される画面として設定されている MyApp クラスのロジックとなる TypeScript のコードです。
ここで rootPage 変数に HomePage クラスが設定されています。HomePage クラスは以下の import 文にあるとおり、src/pages/home/home.ts で宣言されています。
import { HomePage } from '../pages/home/home';

src/pages/home/home.ts

roopPage 変数に設定された HomePage クラスが定義されている TypeScript です。ロジックとしては何の実装もされていません。
HomePage クラスの @Component アノテーションで、templateUrl 値に設定されているファイルが、このロジックに対応するテンプレートファイルとなります。

src/pages/home/home.html

ロジック src/pages/home/home.ts に対応するテンプレートファイルです。先ほど書き換えたホーム画面の内容が記載されています。

Ionic 2 のファイル構成としては、HomePage クラスに関する TypeScript ロジックとテンプレートファイルのように、src/pages 配下に配置した .ts ファイルと .html ファイルを画面毎に追加していくことになります。AngularJS の特徴として画面自体は 1 枚とし、その内部を部分的に書き換えていくこともできますが、まずは画面を増やすほうこうで試してみたいと思います。

Ionic 2 プロジェクトで独自ページを追加する

それでは素のプロジェクトに機能拡張を行い、ページを 1 つ追加してみたいと思います。ここでは、ユーザーが入力したキーワードに関連する商品を検索する画面を追加します。ただし、Ionic 2 での開発方法を主旨とするため、検索ロジック自体はキーワードに特定の文字列を追加するだけの似非ロジックとします。


まずは以下の 3 ファイルを作成します。
import { Component } from '@angular/core';

import { NavController } from 'ionic-angular';

import { ProductService } from '../../services/ProductService';

@Component({
  selector: 'page-search',
  templateUrl: 'search.html',
  providers: [ ProductService ]
})
export class SearchPage {
  products: Array<any>;

  constructor(public navController: NavController, private productService: ProductService) {
    
  }

  searchProducts(event, key) {
    this.products = this.productService.searchProducts(event.target.value);
  }

}
src/pages/search/search.ts では商品検索画面となる SearchPage クラスを定義しています。
@Component アノテーションの templateUrl 値にはこの TypeScript ロジックに対応するテンプレートとして、search.html を指定しています。

また、searchProducts() メソッドでは ProductService クラスの searchProducts() メソッドを呼び出し、その結果を products フィールドに設定しています。商品検索ロジックの実態はこの ProductService に実装する形としています。ProductService は別ファイルで定義されているため、import 文によって参照されています。

products フィールドに設定した値をビューに反映させるのは、AngularJS の役割です。AngularJS がテンプレートに対応するロジックの変数値をテンプレート内の適切な位置にマッピングしてくれるので、ロジックとして特定の HTML タグ内部を書き換える、というような処理は必要ありません。


src/pages/search/search.htmlこのコードをダウンロードする
<ion-header>
  <ion-navbar>
    <ion-title>
      Search Page
    </ion-title>
  </ion-navbar>
</ion-header>

<ion-content padding>
  Search product.
  <ion-item>
    <ion-input type="text" placeholder="Search keyword" (input)="searchProducts($event, searchKey)"></ion-input>
  </ion-item>

  <ion-list>   
    <ion-item *ngFor="let product of products">
      <h2>{{product}}</h2>
    </ion-item>
  </ion-list>   
</ion-content>
src/pages/search/search.html は SearchPage クラスに対応するテンプレートファイルです。<ion-list> タグ内で、ngFor 属性値によってこのリスト内部が SearchPage.products 値の要素数だけ繰り返しレンダリングされるという動きになります。各要素については、ngFor 属性値によって変数 product にマッピングされ、それが <h2> タグ内に埋め込まれる形になっています。

AngularJS ではビューについては手続き型ではなく宣言型をとるべきという設計思想を持っているそうなので、実際にはニュアンスが少し異なるのですが、ここでは便宜的にこのような言い方をしたいと思います。

<ion-input> タグでは、ユーザーの入力値が SearchPage.searchProducts() メソッドにマッピングされることが記載されています。<ion-input> タグはビルドされることで HTML の <input type="text"> タグに変換されるのですが、この <input> タグへの入力イベントが発生するたびに、SearchPage.searchProducts() が呼び出されることになります。


src/services/ProductService.tsこのコードをダウンロードする
export class ProductService { 
  
  constructor() {
    
  }
  
  searchProducts(keyword) {
    let products = [ 
            'White ' + keyword,
            keyword + '\'s ink.',
            'The ' + keyword + ' at the window'
        ];

    return products;
  }
}
src/services/ProductService.ts は SearchPage クラスで利用される ProductService クラスを定義しています。
searchProducts() メソッドで実装されているのは似非商品検索ロジックなので、引数である keyword を加工した 3 種類の文字列を配列につめて返しているだけです。


新規で追加するソースコードは以上の 3 点になりますが、既存のソースコードもいくつか修正する必要があります。

import { NgModule, ErrorHandler } from '@angular/core';
import { IonicApp, IonicModule, IonicErrorHandler } from 'ionic-angular';
import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';
import { SearchPage } from '../pages/search/search';

@NgModule({
  declarations: [
    MyApp,
    HomePage,
    SearchPage
  ],  
  imports: [
    IonicModule.forRoot(MyApp)
  ],  
  bootstrap: [IonicApp],
  entryComponents: [
    MyApp,
    HomePage,
    SearchPage
  ],  
  providers: [{provide: ErrorHandler, useClass: IonicErrorHandler}]
})
export class AppModule {}
AppModule クラスの @NgModule アノテーションで、declarations 値と entryComponents 値となる配列にそれぞれ SearchPage クラスを追加しています。これにより、SearchPage クラスが読み込まれるようになります。
SearchPage クラスが定義されている src/pages/search/search.ts ファイルも、新たな import 文によって参照されています。

<ion-header>
  <ion-navbar>
    <ion-title>
      Ionic Blank
    </ion-title>
  </ion-navbar>
</ion-header>

<ion-content padding>
  Hello Ionic!!

  <div>
    <button myitem (click)='goSearchPage();'>Search Product</button>
  </div>
</ion-content>
最初に表示されるホーム画面から追加した似非商品検索画面へ遷移できるように、<button> タグを追加しています。この <button> タグをクリックした際の動作として、HomePage クラスの goSearchPage() メソッドをマッピングしています。

import { Component } from '@angular/core';

import { NavController } from 'ionic-angular';

import { SearchPage } from '../search/search';

@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {

  constructor(public navController: NavController) {
    
  }

  goSearchPage() {
    this.navController.push(SearchPage);
  }

}
HomePage クラスには先ほどテンプレート内に追記した goSearchPage() メソッドを追加しています。
Ionic 2 では画面遷移にスタックのしくみを用いていて、新たな画面へ遷移する場合には NavController クラスの push() メソッドを利用します。引数には遷移先の画面のロジックとなる SearchPage クラスを指定しています。
SearchPage クラスを利用するため、新たな import 文で src/pages/search/search.ts を読み込むことも忘れないでください。

追加した Ionic 2 独自ページを動かしてみる

それでは ionic serve コマンドで動かしてみます。さきほどからずっと起動しっぱなしの場合には、自動でビルドされ、ブラウザの内容が更新されていると思います。

custom_home.png
ホーム画面には Search Product ボタンが追加されています。
このボタンを押すと似非商品検索画面へ遷移します。
custom_search.png
似非商品検索画面です。
デフォルトでは検索窓があるだけですが、ここに文字列を入力することで、商品を検索することができます。
custom_search_result.png

ローカルで処理が完結しているだけあって、画面内の表示変更はかなり速いです。
今回は入力したキーワードを適当に加工して返しているだけですが、WEB API 経由で商品を検索したり、DB に検索をかけたりする場合は応答が遅くなっていきます。このあたりは、UI からのイベント処理を非同期で行う等の対応が必要になってきます。

Ionic 2 を使ってみて

スマホのネイティブアプリを実装するにあたっては、やはり端末毎の UI 実装というのはネックになると思います。私自身は Andorid のネイティブアプリしか作ったことがないのですが、Swing のレイアウトに似たアプローチでネイティブというだけあるな、という印象でした。おそらく iOS も同時開発するには、iOS 特有の UI 実装を別途する必要があるでしょうから、このあたりを吸収して WEB アプリのように開発できるというのは大きなメリットな気がします。
逃げ道として、ネイティブで実装したコードをプラグインとして利用する機能もあるそうなので、インタラクティブ性の薄いアプリの開発であれば、十分有用なフレームワークだと思います。

Node.js や AngularJS、SASS などが普通に使われているあたりは、伝統的な WEB アプリ開発者にとっては学習コストになるかもしれませんが、そこを踏まえて採用するか検討してみるのがいいのではないでしょうか。既存の技術がからみ合ってできているので、どこからどこまでがどの技術かということまで知りたければ、別途個々の技術要素を調べていく必要がありそうです。



  WEB 技術Ionic 2  コメント (0)  2017/01/24 19:19:22


公開範囲:
プロフィール HN: ももかん
ゲーム作ったり雑談書いたり・・・していた時期が私にもありました。
カレンダー
<<2018, 4>>
1234567
891011121314
15161718192021
22232425262728
293012345