2019.02.21
Capacitorとは、WebアプリをiOS・Android・Electron環境で動かすためのクロスプラットフォーム開発ライブラリ。プラットフォーム固有のAPIはiOSではSwiftで書かれた、AndroidはJavaで書かれたプラグインを使うことで限りなくWeb標準の書き方に近い形で呼び出すことができる。
2019年2月21現在、まだBeta版だが、ドキュメントも整っていたり国内で既にCapacitorとIonic 4を使った事例も生まれているようなので試してみることにした。
https://github.com/tetsushi-ito/ionic4-capacitor-sample
$ ionic start
# プロジェクト名を指定
? Project name: Ionic4CapacitorSample
# テンプレートを選択
? Starter template:
blank | A blank starter project
sidemenu | A starting project with a side menu with navigation in the content area
❯ tabs | A starting project with a simple tabbed interface
# Appflowを使うか選択(今回は使わない)
? Install the free Ionic Appflow SDK and connect your app? (Y/n) n
出来上がったら、プロジェクトのディレクトリに移動してみる。
$ cd Ionic4CapacitorSample
この時点でのディレクトリ構成は以下の通り。
$ tree -I node_modules
.
├── angular.json
├── e2e
│ ├── protractor.conf.js
│ ├── src
│ │ ├── app.e2e-spec.ts
│ │ └── app.po.ts
│ └── tsconfig.e2e.json
├── ionic.config.json
├── package-lock.json
├── package.json
├── src
│ ├── app
│ │ ├── app-routing.module.ts
│ │ ├── app.component.html
│ │ ├── app.component.spec.ts
│ │ ├── app.component.ts
│ │ ├── app.module.ts
│ │ ├── tab1
│ │ │ ├── tab1.module.ts
│ │ │ ├── tab1.page.html
│ │ │ ├── tab1.page.scss
│ │ │ ├── tab1.page.spec.ts
│ │ │ └── tab1.page.ts
│ │ ├── tab2
│ │ │ ├── tab2.module.ts
│ │ │ ├── tab2.page.html
│ │ │ ├── tab2.page.scss
│ │ │ ├── tab2.page.spec.ts
│ │ │ └── tab2.page.ts
│ │ ├── tab3
│ │ │ ├── tab3.module.ts
│ │ │ ├── tab3.page.html
│ │ │ ├── tab3.page.scss
│ │ │ ├── tab3.page.spec.ts
│ │ │ └── tab3.page.ts
│ │ └── tabs
│ │ ├── tabs.module.ts
│ │ ├── tabs.page.html
│ │ ├── tabs.page.scss
│ │ ├── tabs.page.spec.ts
│ │ ├── tabs.page.ts
│ │ └── tabs.router.module.ts
│ ├── assets
│ │ ├── icon
│ │ │ └── favicon.png
│ │ └── shapes.svg
│ ├── environments
│ │ ├── environment.prod.ts
│ │ └── environment.ts
│ ├── global.scss
│ ├── index.html
│ ├── karma.conf.js
│ ├── main.ts
│ ├── polyfills.ts
│ ├── test.ts
│ ├── theme
│ │ └── variables.scss
│ ├── tsconfig.app.json
│ └── tsconfig.spec.json
├── tsconfig.json
└── tslint.json
# 必要なパッケージをインストール
$ npm install --save @capacitor/core @capacitor/cli
# CLIで初期設定を行う
$ npx cap init
# アプリ名を指定
? App name Ionic4CapacitorSample
# アプリのパッケージIDを指定
? App Package ID (in Java package format, no dashes) net.playfulit.ionic4capacitorsample
✔ Initializing Capacitor project in /Users/tetsushi/projects/ionic/Ionic4CapacitorSample in 2.59ms
🎉 Your Capacitor project is ready to go! 🎉
Add platforms using "npx cap add":
npx cap add android
npx cap add ios
npx cap add electron
Follow the Developer Workflow guide to get building:
https://capacitor.ionicframework.com/docs/basics/workflow
これで準備完了。プロジェクトはGit管理されているので、このコマンドによって発生した差分を確認してみると、プロジェクトのディレクトリの直下にcapacitor.config.json
というファイルが生成されていた。
{
"appId": "net.playfulit.ionic4capacitorsample",
"appName": "Ionic4CapacitorSample",
"bundledWebRuntime": false,
"webDir": "www"
}
単に先ほどCLIから設定した情報が書かれているだけのようだ。
iOSやAndroidをプラットフォームとして追加するためには、www
というディレクトリが生成されている必要がある。一度アプリをビルドして、www
を生成する。
# Ionicアプリのビルドを実行
$ ionic build
これでwww
というディレクトリにWebアプリケーション一式が出来上がった。
続いて、アプリが動作するプラットフォームとしてiOSを追加してみる。
# プロジェクトのプラットフォームにiOSを追加
$ npx cap add ios
先ほどと同様にGitで変更を見てみると、プロジェクトのディレクトリの直下にios
というディレクトリが生成されていた。
ディレクトリ構造は以下の通り。ios/App/public
にはビルドしたWebアプリケーションのファイルが大量に入っているので除外した。
$ tree ios -I public
ios
├── App
│ ├── App
│ │ ├── AppDelegate.swift
│ │ ├── Assets.xcassets
│ │ │ ├── AppIcon.appiconset
│ │ │ │ ├── AppIcon-20x20@1x.png
│ │ │ │ ├── AppIcon-20x20@2x-1.png
│ │ │ │ ├── AppIcon-20x20@2x.png
│ │ │ │ ├── AppIcon-20x20@3x.png
│ │ │ │ ├── AppIcon-29x29@1x.png
│ │ │ │ ├── AppIcon-29x29@2x-1.png
│ │ │ │ ├── AppIcon-29x29@2x.png
│ │ │ │ ├── AppIcon-29x29@3x.png
│ │ │ │ ├── AppIcon-40x40@1x.png
│ │ │ │ ├── AppIcon-40x40@2x-1.png
│ │ │ │ ├── AppIcon-40x40@2x.png
│ │ │ │ ├── AppIcon-40x40@3x.png
│ │ │ │ ├── AppIcon-512@2x.png
│ │ │ │ ├── AppIcon-60x60@2x.png
│ │ │ │ ├── AppIcon-60x60@3x.png
│ │ │ │ ├── AppIcon-76x76@1x.png
│ │ │ │ ├── AppIcon-76x76@2x.png
│ │ │ │ ├── AppIcon-83.5x83.5@2x.png
│ │ │ │ └── Contents.json
│ │ │ ├── Contents.json
│ │ │ └── Splash.imageset
│ │ │ ├── Contents.json
│ │ │ ├── splash-2732x2732-1.png
│ │ │ ├── splash-2732x2732-2.png
│ │ │ └── splash-2732x2732.png
│ │ ├── Base.lproj
│ │ │ ├── LaunchScreen.storyboard
│ │ │ └── Main.storyboard
│ │ ├── Info.plist
│ │ ├── capacitor.config.json
│ │ └── config.xml
│ ├── App.xcodeproj
│ │ ├── project.pbxproj
│ │ ├── project.xcworkspace
│ │ │ └── contents.xcworkspacedata
│ │ └── xcuserdata
│ │ ├── max.xcuserdatad
│ │ │ └── xcschemes
│ │ │ └── xcschememanagement.plist
│ │ └── tetsushi.xcuserdatad
│ │ └── xcschemes
│ │ └── xcschememanagement.plist
│ ├── App.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ ├── xcshareddata
│ │ │ └── IDEWorkspaceChecks.plist
│ │ └── xcuserdata
│ │ ├── max.xcuserdatad
│ │ │ └── UserInterfaceState.xcuserstate
│ │ └── tetsushi.xcuserdatad
│ │ └── UserInterfaceState.xcuserstate
│ ├── Podfile
│ ├── Podfile.lock
│ └── Pods
│ ├── Headers
│ ├── Local\ Podspecs
│ │ ├── Capacitor.podspec.json
│ │ └── CapacitorCordova.podspec.json
│ ├── Manifest.lock
│ ├── Pods.xcodeproj
│ │ ├── project.pbxproj
│ │ └── xcuserdata
│ │ └── tetsushi.xcuserdatad
│ │ └── xcschemes
│ │ ├── Capacitor.xcscheme
│ │ ├── CapacitorCordova.xcscheme
│ │ ├── Pods-App.xcscheme
│ │ └── xcschememanagement.plist
│ └── Target\ Support\ Files
│ ├── Capacitor
│ │ ├── Capacitor-dummy.m
│ │ ├── Capacitor-prefix.pch
│ │ ├── Capacitor-umbrella.h
│ │ ├── Capacitor.modulemap
│ │ ├── Capacitor.xcconfig
│ │ └── Info.plist
│ ├── CapacitorCordova
│ │ ├── CapacitorCordova-dummy.m
│ │ ├── CapacitorCordova-prefix.pch
│ │ ├── CapacitorCordova-umbrella.h
│ │ ├── CapacitorCordova.modulemap
│ │ ├── CapacitorCordova.xcconfig
│ │ └── Info.plist
│ └── Pods-App
│ ├── Info.plist
│ ├── Pods-App-acknowledgements.markdown
│ ├── Pods-App-acknowledgements.plist
│ ├── Pods-App-dummy.m
│ ├── Pods-App-frameworks.sh
│ ├── Pods-App-resources.sh
│ ├── Pods-App-umbrella.h
│ ├── Pods-App.debug.xcconfig
│ ├── Pods-App.modulemap
│ └── Pods-App.release.xcconfig
└── capacitor-cordova-ios-plugins
├── CordovaPlugins.podspec
├── CordovaPluginsResources.podspec
├── CordovaPluginsStatic.podspec
├── resources
└── sources
これで準備完了。Xcodeを開くコマンドも用意されているので、それを使ってXcodeでプロジェクトを開いてみる。
$ npx cap open ios
Xcode上でとりあえずシミュレーターでiPhone XSを選択して実行ボタンを押してみると、あっさりシミュレーターでIonicアプリを動かすことができた。
続いて、アプリが動作するプラットフォームとしてAndroidを追加してみる。
# プロジェクトのプラットフォームにAndroidを追加
$ npx cap add android
Androidもこれで準備完了。iOSと同様にコマンドからIDEを起動できる。Androidの場合はAndroid Studioが起動する。
$ npx cap open android
自動でGradleのSyncが始まったが、以下のようなエラーが出て失敗してしまった。
ERROR: Failed to install the following Android SDK packages as some licences have not been accepted.
platforms;android-27 Android SDK Platform 27
build-tools;28.0.3 Android SDK Build-Tools 28.0.3
To build this project, accept the SDK license agreements and install the missing components using the Android Studio SDK Manager.
Alternatively, to transfer the license agreements from one workstation to another, see http://d.android.com/r/studio-ui/export-licenses.html
Using Android SDK: /Users/tetsushi/Library/Android/sdk
Install missing SDK package(s)
「Install missing SDK package(s)」の部分が押せるようになっているので押してみる。
ライセンスを確認する画面が出てきたので、確認して次へ進む。
Android Studioでは、プラグインのアップデートが利用できる場合はそれを知らせてくれる。
Android Studioの初回のSyncはかなり時間がかかるので、気長に待つ必要がある。
準備ができたら実行ボタンを押す。エミュレーターにはあらかじめセットアップしていた「Nexus 5X API 23」を選択した。
おそらくAndroidアプリとしては正常に起動できたが、Webアプリケーションの部分でエラーが発生している模様。
(intermediate value).fill is not a function
というエラーが出ているので、ES6の機能を呼び出そうとしたらCapacitorが内部的に呼び出すAndroidのWebブラウザが古くて使えなかったという状況。
新しい機能を古い環境でも動作するようにパッチを当てるPolyfillsという仕組みがあるので、それを利用して対応することもできる。Ionic(Angular)プロジェクトでは、デフォルトでsrc/polyfills.ts
というファイルが用意されていて、いくつかのPolyfillがテンプレートとして記述されている。
今回はAndroid Virtual Device Managerから「Pixel 2 API 28」を新たにセットアップしてそちらで試してみた。
API 28のAndroid端末では問題なく起動した。アプリ開発では特にWebアプリケーションを作るためのCSS・JavaScriptの対応バージョンや、Capacitor自体の対応バージョンをしっかり考えないといけない。Polyfillsを入れて対応するのか、サポートするOSバージョン・APIレベルを引き上げるのか慎重に検討したい。
Ionic 4とCapacitorを組み合わせて使う場合、Ionic 4アプリをビルドした後にそれを各プラットフォームのディレクトリにコピーする作業が必要になる。
# Ionicアプリをビルド
$ ionic build
# iOSのディレクトリにコピー
$ npx cap copy ios
# Androidのディレクトリにコピー
$ npx cap copy android
カメラ・プッシュ通知・GPS・NFCなどなど、端末の機能にアクセスするにはWebのAPIだけでは足りないので、Capacitorを通してネイティブのAPIを呼び出すことになる。ネイティブのAPIの呼び出しをJavaScriptから行えるようにするプラグインが多数公開されているので、基本的にそれを使う。
プラグインとしてまだ公開されていないような機能を使いたい場合は、自分でプラグインを書いて対応することも可能らしい。
プラグインのリストはこちら。
Cordovaのプラグインの数と比べるとまだ少ない。しかし、CapacitorではCordovaのプラグインも使えるようなので、既存のCordovaで使っていた機能を提供するようなCapacitorプラグインがまだ無くてもCordovaプラグインを使うことで対応できそう。
Ionic 4とCapacitorを組み合わせてiOS/Androidで動作するハイブリッドアプリを作ってみた。たった数コマンドで立ち上げられてエミュレーターで確認できたので非常に開発UXが高そう。まだBeta版なので、プラグインを使って機能を開発していったら何かしら困ることが出てくるかもしれないが、ドキュメントのわかりやすさも含めて期待できるのでどんどん試していきたい。