HTML5Experts.jp

キミはNativeScriptを知っているか?Angular2でネイティブモバイルアプリが書けるぞ!

Web技術でアプリ開発2016特集・第4弾は、JavaScript(およびTypeScript)によるクロスプラットフォーム開発が可能ながらネイティブと同様の実行速度を得られる、Telerik社のNativeScriptを取り上げたいと思います。

NativeScriptは、Angular2とも組み合わせて使うことができる、現在注目のフレームワークです。本記事では、XMLによるUI定義を行う従来の開発手法だけではなく、Angular2(ただし、RC4という少し古いバージョン)と組み合わせてモバイルアプリを開発する方法まで網羅してご紹介します。

NativeScriptの概要

NativeScript 2.0は、JavaScriptとCSSを使用し、ネイティブのiOSとAndroidのアプリを構築するためのフレームワークです。ネイティブで実行されるパフォーマンスとUXを提供し、その結果、ネイティブプラットフォームのレンダリングエンジンを使ってUIを構築します。WebViewsでのUIレンダリングはしていません。

NativeScriptにはtns-core-modulesというコアモジュールがあり、上記に述べたJavaScriptからネイティブへの変換を行っています。NativeScriptのgithubリポジトリを見ると「0.9.0」のリリースが「5 Mar 2015」なので、約1年とちょっとの歳月が経過していることになります。現時点での最新バージョンは「2.2.1」です。

NativeScriptでAngular2を利用する場合には、まず、Angular2のテンプレート(@Component.template)に、NativeScriptで提供しているディレクティブを記述します。そして、上記で説明したtns-core-modulesとnativescript-angularを利用してNativeScriptがビルドを行い、iOSやAndroidのネイティブコードを生成します。

「NativeScript with Angular2」の最初の「v0.3.0」がリリースされたのが今年8月初旬で、最新は「v0.3.1」です。

この記事では、macOS上での開発を前提に解説をしています。

インストール

インストール手順はquick-setupにも書かれている通り、あまり複雑なところはありません。しかし、もしAndroid SDKをはじめて利用する場合には少し戸惑うと思います(私もNativeScriptのあまりやさしいとはいえないエラーメッセージにちょっと苦しみました)ので、順を追って説明します。

ステップ1: Rubyのインストール

NativeScriptをビルドするとき、Rubyを利用します。Rubyのバージョンは2.2以降が必要ですが、macOSにデフォルトでインストールされているRubyは古いため、Homebrewを使って最新バージョンをインストールします。最新のRubyが入っている方は読み飛ばして下さい。

Homebrewのインストール

HomebrewはRubyを使いインストールし、インストール後環境変数を定義します。

$ ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
$ brew update
$ echo 'export PATH=/usr/local/bin:$PATH' >> .bash_profile
$ source .bash_profile

rbenvとruby-buildのインストール

brewを使ってrbenvruby-buildをインストールします。rbenvを使ってRubyのバージョン管理を行います。ruby-buildはrbenvのプラグインで、異なるバージョンのRubyをコンパイルし、インストールするためのものです。

$ brew install rbenv ruby-build
$ echo 'eval "$(rbenv init -)"' >> ~/.bash_profile
$ source ~/.bash_profile
$ rbenv --version
$ rbenv install -l
$ rbenv install 2.2.3
$ rbrnv version
$ rbenv global 2.2.3
$ ruby -v
ruby 2.2.3p173 (2015-08-18 revision 51636) [x86_64-darwin15]
$

ステップ2: Node.jsのインストール

Node.jsはv4.0.0以降を利用する必要があります。rbenvと同様にバージョン管理システムをインストールします。Node.jsのバージョン管理システムにはnodebrew, nodist, nvm, nvm-windowsなどいくつかありますが、筆者はnodebrewを好んで利用しています。

nodebrew のインストール

curlを使ってインストールを行います。インストールが完了するとパスを追加するようにメッセージが表示されますので、.bash_profile に登録してください。

$ curl -L git.io/nodebrew | perl - setup
$ echo 'export PATH=$HOME/.nodebrew/current/bin:$PATH' >> .bash_profile
$ source .bash_profile

Node.jsのインストール

続いて、Node.jsをインストールします。nodebrew ls-remoteコマンドを使ってバージョンを確認し、nodebrew install-binaryコマンドを使ってNode.jsのインストールを行います。利用するバージョンを動かす場合にはnodebrew useコマンドを実行します。

今回、Node.jsのバージョンとしてはv5.12.0を利用しました。

$ nodebrew ls-remote
$ nodebrew install-binary v5.12.0
$ nodebrew use v5.12.0
$ node -v
v5.12.0
$

ステップ3: NativeScript CLIのインストール

いよいよNativeScriptのインストールです。npmコマンドを使ってnativescriptをグローバルインストールします。nativescriptはスカッフォールディング(コード雛形の自動生成)を提供するモジュールで、tnsコマンドによって実行されます。

tnsコマンドを実行して、インストールが正しく行われているか確認しましょう。参考までにtnsコマンドで出力される内容はtns --helpと同じ内容です。tnsコマンドでは主に

を行います

$ npm install nativescript -g
$ tns

NativeScript

┌─────────┬─────────────────────────────────────────────────────────────────────┐ │ Usage │ Synopsis │ │ General │ $ tns [Command Parameters] [--command ] │ │ Alias │ $ nativescript [Command Parameters] [--command ] │ └─────────┴─────────────────────────────────────────────────────────────────────┘

・・・

$

ステップ4: iOSとAndroid開発に必要なもののインストール

JDKのインストール

JDKはバージョンが8以降のものをインストールしてください。インストールが完了したら、.bash_profileに設定を登録してください。

$ echo 'export JAVA_HOME=$(/usr/libexec/java_home)' >> .bash_profile
$ source .bash_profile

Android開発環境のインストール

Android SDKをHomebrewからインストールし、変数ANDROID_HOMEを定義します.

$ brew install android-sdk
$ echo 'export ANDROID_HOME=/usr/local/Cellar/android-sdk/24.4' >> .bash_profile
$ echo 'export PATH=$PATH:$ANDROID_HOME/platform-tools:$ANDROID_HOME/tools' >> .bash_profile
$ source .bash_profile

Androidエミュレータのインストール

android-sdkのインストールが終わったら、実際に利用するエミュレータを定義します。android-sdkがインストールされているディレクトリへ移動し、andorid avdを実行することで設定を行います。android-sdk以下のディレクトリ構成は次の通りです。

$ cd /usr/local/Cellar/android-sdk/24.4
$ tree -L 1
.
├── INSTALL_RECEIPT.json
├── README
├── add-ons -> ../../../var/lib/android-sdk/add-ons
├── bin
├── build-tools
├── etc
├── extras -> ../../../var/lib/android-sdk/extras
├── platform-tools
├── platforms -> ../../../var/lib/android-sdk/platforms
├── samples -> ../../../var/lib/android-sdk/samples
├── sources -> ../../../var/lib/android-sdk/sources
├── system-images -> ../../../var/lib/android-sdk/system-images
├── temp -> ../../../var/lib/android-sdk/temp
└── tools

Androidエミュレータを高速化するためのハードウェア支援環境「Intel Hardware Accelerated Execution Manager (Intel® HAXM) 」を事前にインストールします。次のスクリプトを実行してください。

$ cd /usr/local/Cellar/android-sdk/24.4/extras/intel/Hardware_Accelerated_Execution_Manager
$ sudo ./silent_install.sh

完了したら、android avdを起動し、事前にエミュレータ定義を登録します。

$ cd /usr/local/Cellar/android-sdk/24.4/tools
$ ./android avd

この設定を忘れると「Cannot read property ‘targetNum’ of undefined」というエラーが出ます

参考までに、今回利用しているエミュレータは次のように定義しています。

よくあるトラブルとしては、Dockerを起動しているようなら終了させておくのが無難です。Dockerが利用しているポートとエミュレータが利用するポートがぶつかり、実行できないことがあります。

iOS開発環境のインストール

macOSを利用されていれば、iOSのエミュレータを利用するのは容易だと思います。

Command Line Tools for Xcode

$ sudo gem install xcodeproj
$ sudo gem install cocoapods
$ brew install xcproj

だいぶ長くなりましたが、これで開発環境の設定は完了です。尚、tnsコマンドにはtns doctorというコマンドがあります。環境設定などうまくできているかのチェックができますので、一通り終わった後に実行し、漏れがないかを確認します。

$ tns doctor
Verifying CocoaPods. This may take more than a minute, please be patient.
  ◡ Installing iOS runtime.tns-ios@2.2.1 ../../../../../../var/folders/ts/n268b_qx2j7g3ykzkqlj3jt00000gn/T/nativescript-check-cocoapods11686-1391-akcqo6/node_modules/tns-ios
  ◝ Verifying CocoaPods. This may take some time, please be patient..
Your components are up-to-date.

No issues were detected. $

NativeScriptで作るHello World

ここではNativeScriptを使ってHello World的なアプリケーションを作成します。手順は

  1. プロジェクトの作成
  2. プラットフォームのインストール
  3. エミュレータの実行

です。NativeScriptでのアプリケーション作成にはtnsコマンドを使います。

今、プロジェクトをMyFirstNativeScriptAppとしますので、iOSとAndroidアプリケションの両方を作成する場合にはiOSとAndroidのプラットフォーム用ライブラリをインストールしエミュレートを起動します。

$ cd ~
$ tns create MyFirstNativeScriptApp
$ cd MyFirstNativeScriptApp
$ tns platform add android
$ tns platform add ios
$ tns run android --emulator
$ tns run ios --emulator

Androidエミュレータでの実行結果

iOSエミュレータでの実行結果

tns create MyFirstNativeScriptAppで作成されたファイルを確認します。

$ tree -L 2
.
├── app
│   ├── App_Resources
│   ├── app.css
│   ├── app.js
│   ├── main-page.js
│   ├── main-page.xml
│   ├── main-view-model.js
│   ├── package.json
│   └── references.d.ts
├── node_modules
│   ├── babel-traverse
│   ├── babel-types
│   ├── babylon
│   ├── lazy
│   ├── tns-core-modules
│   └── tns-core-modules-widgets
├── package.json
└── platforms
    ├── android
    └── ios

このアプリケーションで特に重要なファイルがありますので、簡単に説明します。

app.jsはアプリケーションが一番最初にロードするエントリーポイントで、このファイルからアプリケーションmain-page.jsの呼び出しを行っています。

// app.js
var application = require("application");
application.start({ moduleName: "main-page" });

// main-page.js
var createViewModel = require("./main-view-model").createViewModel;

function onNavigatingTo(args) { var page = args.object; page.bindingContext = createViewModel(); } exports.onNavigatingTo = onNavigatingTo;

main-page.xmlは画面をXMLで定義し、関連する処理をmain-view-model.jsに定義してます。

<!-- main-page.xml -->
<Page xmlns="http://schemas.nativescript.org/tns.xsd" navigatingTo="onNavigatingTo">
  <StackLayout>
    <Label text="Tap the button" class="title"/>
    <Button text="TAP" tap="{{ onTap }}" />
    <Label text="{{ message }}" class="message" textWrap="true"/>
  </StackLayout>
</Page>

// main-view-model.js
var Observable = require("data/observable").Observable;

function getMessage(counter) { if (counter <= 0) { return "Hoorraaay! You unlocked the NativeScript clicker achievement!"; } else { return counter + " taps left"; } }

function createViewModel() { var viewModel = new Observable(); viewModel.counter = 42; viewModel.message = getMessage(viewModel.counter);

viewModel.onTap = function() { this.counter--; this.set("message", getMessage(this.counter)); }

return viewModel; }

exports.createViewModel = createViewModel;

テンプレートを使って簡単なアプリケーションを作成する

NativeScriptには、テンプレートを使ってプロジェクトを生成する機能があります。

$ tns create [Project Name] --template [Template Name]

繰り返しになりますが、テンプレートを作成した後「プラットフォームのインストール」「エミュレータの実行」を行うとエミュレートで実行確認ができます。

このテンプレートはnpmに登録されています。が、まだあまり多くのテンプレートは定義されていないようです。その中から開発時に利用できそうなものを幾つかピックアップしました。具体的なコードは各テンプレートを使ってコードを生成し確認していただけると幸いです。

タブを使ったアプリケーション

$ tns create MyNextGreatApp --template tns-template-tab-navigation

タブ切り替えのテンプレート部分はTabViewタグを使い、各タブで表示する内容はTabViewItemで定義しています。

<!-- main-page.xml -->
<Page xmlns="http://schemas.nativescript.org/tns.xsd" loaded="pageLoaded">
  <TabView>
    <TabView.items>
      <TabViewItem title="First">
        <TabViewItem.view>
          <StackLayout class="tab-content">
            <Label text="First View" class="title"/>
            <Label text="This is the content of the first tab." textWrap="true"/>
          </StackLayout>
        </TabViewItem.view>
      </TabViewItem>
      <TabViewItem title="Second">
        <TabViewItem.view>
          <StackLayout class="tab-content">
            <Label text="Second View" class="title"/>
            <Label text="This is the content of the second tab." textWrap="true"/>
          </StackLayout>
        </TabViewItem.view>
      </TabViewItem>
    </TabView.items>
  </TabView>
</Page>

Androidエミュレータでの実行結果

iOSエミュレータでの実行結果

一覧と詳細を表示するアプリケーション

$ tns create MyMasterDetailApp --template tns-template-master-detail

一覧と明細はGridLayoutを使って表示する場所を定義し、一覧部分を表示するテンプレートはListViewを使っています。

<!-- main-page.xml -->
<Page xmlns="http://schemas.nativescript.org/tns.xsd" loaded="pageLoaded">
  <GridLayout rows="auto, *">
    <Label text="Items Page" class="title" />

&lt;ListView items="{{ items }}" itemTap="listViewItemTap" row="1"&gt;
  &lt;ListView.itemTemplate&gt;
    &lt;Label text="{{ title }}" class="listItem" /&gt;
  &lt;/ListView.itemTemplate&gt;
&lt;/ListView&gt;

</GridLayout> </Page>

明細部分はdetails-page.xmlでレイアウトを定義していて、具体的な内容の表示にはdetails-view.xmlを使っています。

<!-- details-page.xml -->
<Page xmlns="http://schemas.nativescript.org/tns.xsd"
      xmlns:app="."
      navigatedTo="pageNavigatedTo">
  <app:details-view />
</Page>

<!-- details-view.xml -->
<StackLayout  xmlns="http://schemas.nativescript.org/tns.xsd">
  <Label text="{{ title }}" class="detail-title"/>
  <Label text="{{ info }}" class="info" textWrap="true"/>
</StackLayout>

Androidエミュレータでの実行結果

iOSエミュレータでの実行結果

サイドメニュー付きアプリケーション

$ tns create MyDrawerApp --template nativescript-template-drawer

サイドメニュー付きアプリケーションを作成する場合には、サイドメニューdrawer-content.xmlを定義し、メニューをクリックしたときの画面をそれぞれ定義しています。

<!-- drawer-content.xml -->
<grid-layout class="drawer-content">
  <stack-layout>
    <label text="Home" tap="navigate" class="{{ selectedPage == 'home' ? 'selected' : '' }}" />
    <label text="About" tap="navigate" class="{{ selectedPage == 'about' ? 'selected' : '' }}" />
    <label text="Settings" tap="navigate" class="{{ selectedPage == 'settings' ? 'selected' : '' }}" />
  </stack-layout>
</grid-layout>

Androidエミュレータでの実行結果

iOSエミュレータでの実行結果

NativeScriptとAngular2を組み合わせる

ここからが本記事の一番のポイント、「Angular2を使ったNativeScriptアプリケーション」に入ります。準備が長くて申し訳ありません!

npmにはいくつかのAngular2を利用したNativeScriptのテンプレートがありますが、今回はNativeScriptのチュートリアルを参考に、githubリポジトリーからテンプレートをcloneしたものを利用します。

NativeScript with Angular2で作るHello World

まずはリポジトリhttps://github.com/NativeScript/sample-Groceriesをcloneします。clone後、angular-startをcheckoutすると、NativeScript with Angular2で開発するための最も基本的な構成を得ることができます。

$ git clone https://github.com/NativeScript/sample-Groceries.git
$ cd sample-Groceries
$ git checkout angular-start
$ tree -L 2
├── app
│   ├── App_Resources
│   ├── app.component.ts
│   ├── app.css
│   ├── app.routes.ts
│   ├── main.ts
│   ├── package.json
│   ├── pages
│   ├── platform.android.css
│   ├── platform.ios.css
│   ├── shared
│   └── utils
├── package.json
├── platforms
│   └── ios
├── references.d.ts
└── tsconfig.json
$ tns platform add ios
$ tns platform add android
$ tns run ios --emulator
$ tns run android --emulator

Androidエミュレータでの実行結果

iOSエミュレータでの実行結果

NativeScriptではBootstrapに特別なメソッドnativeScriptBootstrapを利用します。具体的にエントリーポイントmain.tsは次のように記述されます。

// main.ts
import {nativeScriptBootstrap} from "nativescript-angular/application";
import {AppComponent} from "./app.component";

nativeScriptBootstrap(AppComponent);

Angular2では、NativeScriptのXMLを@Component.templateにディレクティブとして定義します。ただ@Component.templateではXMLをそのまま利用できないので、少し書き換える必要があります。具体的には<foo />を<foo></foo>に書き換えるといった作業です。

具体的にこのサンプルの場合、XML定義では

<Label text='hello world' />

ですが、Angular2ではコンポーネントとして定義します。

// app.component.ts
import {Component} from "@angular/core";

@Component({ selector: "my-app", template: "<Label text='hello world'></Label>" }) export class AppComponent {}

LabelはHTMLタグではなくNativeScriptで定義されたディレクティブです。NativeScriptでは幾つかのUIコンポーネントを定義していますので一覧にまとめます。

UIコンポーネントのマッピング

NativeScriptのUIコンポーネントは、iOSやAndroidのウィジェットにマッピングさせています。ビルドするとNativeScript with Angular2で定義したコンポーネントがマッピングされたiOSやAndroidのウィジェット変換されます。具体的な利用方法に関しては、Native Script UI Componentsを見ていただきたいと思います。

NativeScript with Angular2 Android iOS
Button android.widget.Button UIButton
Label android.widget.TextView UILabel
TextField android.widget.EditText UITextField
TextView android.widget.EditText UITextView
SearchBar android.widget.SearchView UISearchBar
Switch android.widget.Switch UISwitch
Slider android.widget.SeekBar UISlider
Progress android.widget.ProgressBar UIProgressView
ActivityIndicator android.widget.ProgressBar UIActivityIndicatorView
Image android.widget.ImageView UIImageView
ListView android.widget.ListView UITableView
HtmlView android.widget.TextView UILabel
WebView android.webkit.WebView UIWebView
TabView android.support.v4.view.ViewPager UITabBarController
SegmentedBar android.widget.TabHost UISegmentedControl
DatePicker android.widget.DatePicker UIDatePicker
TimePicker android.widget.TimePicker UIDatePicker
ListPicker android.widget.NumberPicker UIPickerView

NativeScript + Angular2でHello World

はじめに作成した「NativeScriptで作るHello World」のAngular2版のテンプレートがあります。アプリケーションの動きはまったく同じですがAngular2で書かれています。

$ tns create MyNgNativeScriptApp --template tns-template-hello-world-ng 
$ cd MyNgNativeScriptApp
$ tns platform add android
$ tns platform add ios
$ tns run android --emulator
$ tns run ios --emulator

テンプレート部分を見比べると、XMLで書かれていたテンプレートコードがAngular2スタイルで記載されていることが理解でき、結果見通しのよいコードになっています。

<!-- app.component.html -->
<StackLayout>
  <Label text="Tap the button" class="title"></Label>
  <Button text="TAP" (tap)="onTap()"></Button>
  <Label [text]="message" class="message" textWrap="true"></Label>
</StackLayout>

// app.component.ts
import {Component} from "@angular/core";

@Component({ selector: "my-app", templateUrl: "app.component.html", }) export class AppComponent { public counter: number = 16;

public get message(): string { if (this.counter > 0) { return this.counter + " taps left"; } else { return "Hoorraaay! \nYou are ready to start building!"; } }

public onTap() { this.counter--; } }

Todosアプリケーション

NativeScriptのサイトには、より多く機能が実装されたチュートリアルBuilding Apps with NativeScript and Angular 2を提供しています。完成したアプリケーションは先ほどと同じリポジトリhttps://github.com/NativeScript/sample-Groceriesにありますので、是非見て下さい。

$ git clone https://github.com/NativeScript/sample-Groceries.git NgTodosNative
$ cd NgTodosNative
$ tns platform add ios
$ tns platform add android
$ tns run ios --emulator
$ tns run android --emulator

Androidエミュレータでの実行結果

iOSエミュレータでの実行結果

サンプルコードを読むと、NativeScriptで利用するnativeScriptBootstrapの使い方やNativeScript用のルーティング設定が記載されていたりとAngular2の特徴を活かした実装になっています。

今回は、環境設定からはじまり、NativeScriptを使ったもの、NativeScript wit Angular2へと進め、Hello World的なアプリケーションを動かすところまでをテンプレートを使いながら簡単に説明させていただきました。

最後に

NativeScriptの事例がないかと探してみましたがNativeScript Showcasesにいくつかありました。このアプリケーションがAngular2をベースにしたものかはわかりませんが、NativeScriptを使ったものです。

NativeScriptのXMLで定義したUIコンポーネントが、コンパイル時にiOS、Androidのウィジェットにそれぞれマッピングされネイティブアプリにコンパイルされます(ちょっと古いですがXSLTを思い出しました)。Angular2の@Component.templateを利用することでNativeScriptがUIコンポーネントの定義を解釈し、ネイティブコードの生成を行っています。

ただし、NativeScript with Angular2用に作成したコンポーネントとNativeScriptを使わない通常のAngular2のコンポーネントとの相互再利用性は低いです。ですが、慣れたAngular2の構造を利用しTypeScript(もしくはJavaScript)で書けるというのは非常に興味が湧くところです。

編集部よりお詫び
2016/9/9 18:30 編集部が付けた以前のタイトルは、一部の方を不快にさせる可能性があるとの判断のもと、記事タイトルを変更いたしました。 執筆者の佐川さん、及び以前のタイトルを不快に感じた多くの方々に、深くお詫び申し上げます。