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のインストール
- ステップ2: Node.jsのインストール
- ステップ3: NativeScript CLIのインストール
- ステップ4: iOSとAndroid開発に必要なもののインストール
ステップ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を使ってrbenvとruby-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
コマンドでは主に
- テンプレートからプロジェクトの生成
- プロジェクトへの生成プラットフォーム(iOS, Android)の追加
- JavaScriptからネイティブへのビルド
- エミュレータの実行
を行います
$ npm install nativescript -g $ tnsNativeScript
┌─────────┬─────────────────────────────────────────────────────────────────────┐ │ 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的なアプリケーションを作成します。手順は
- プロジェクトの作成
- プラットフォームのインストール
- エミュレータの実行
です。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.xml
- main-page.js
- main-view-model.js
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" /><ListView items="{{ items }}" itemTap="listViewItemTap" row="1"> <ListView.itemTemplate> <Label text="{{ title }}" class="listItem" /> </ListView.itemTemplate> </ListView>
</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 編集部が付けた以前のタイトルは、一部の方を不快にさせる可能性があるとの判断のもと、記事タイトルを変更いたしました。
執筆者の佐川さん、及び以前のタイトルを不快に感じた多くの方々に、深くお詫び申し上げます。