この記事はIonic Advent Calendar 2017の12/10のエントリーです。
2016年6月に『最新のテクノロジーを使って、新しい音楽の体験を生み出し、「音楽を演奏する人」「音楽を聴く人」にとって、より幸せな世界をつくる』というミッションを胸に会社を2人で起業しました。
今は、弦楽器の練習をサポートするシステムBowingVisionを自社開発していて、世にリリースするべく開発に励んでいます。
最近、ionicを使ってモバイルアプリを作ることがよくあります。ionicには色々なプラグインが用意されていてスマートホンの様々な機能を使うことができるようになっています。また、Cordovaのプラグインもそのまま使うことができるので、大抵のことは既存のプラグインで実現できます。
しかし、それでも、独自の機能を入れたプラグインがあれば、さらに便利になることもあります。そこで、ionic3でNative pluginを作って見ました。
ionicは進化が激しいので、pluginの作り方も少しずつ変わっています。今回の作成方法も現時点での作り方ですので、将来変更になる可能性はあります。今回使用したバージョンは3.19.0です。
また、開発環境はMac OSX 10.13.2を使っています。
Native pluginとは
Ionicで使えるプラグインには、Ionic native pluginとCordovaプラグインの2つがあります。この2つの主な違いは呼び出し方にあります。
Cordova plugin
Cordovaのプラグインは、グローバルオブジェクトとしてインストールされるので、使用するときにはプラグインのオブジェクトを使います。ただし、TypeScriptでは未定義のオフジェクトを使おうとするとコンパイルエラーになりますので、定義が必要となります。
例えば、ExamplePluginという名前のプラグインを使用する場合には、以下のようにdeclareなどで宣言する必要があります。
import { Component } from '@angular/core'; import { NavController, Platform } from 'ionic-angular'; declare var ExamplePlugin: any; @Component({ selector: 'page-home', templateUrl: 'home.html' }) export class HomePage { constructor(public navCtrl: NavController, public platform: Platform) { this.platform.ready().then(() => { ExamplePlugin.func('abc'); }); } }
Ionic native plugin
Ionic native pluginはCordova pluginのラッパーです。プラグインの実体はCordova pluginですが、ブラグイン用のオブジェクトを無理やり?定義することなく、使用できるようにするためのものです。ExamplePluginがNative pluginになっていれば以下のように使うことができます。
import { Component } from '@angular/core'; import { NavController, Platform } from 'ionic-angular'; import { ExamplePlugin } from '@ionic-native/example-plugin'; @Component({ selector: 'page-home', templateUrl: 'home.html' }) export class HomePage { constructor(public navCtrl: NavController, public platform: Platform, private examplePugin: ExamplePlugin) { this.platform.ready().then(() => { this.examplePlugin.func('abc'); }); } }
Cordova Pluginの作成
Ionic Native PluginはCordova Pluginをラップしたものなので、まず、Cordova Pluginを作成します。
プロジェクトの作成
プラグイン開発のためのプロジェクトを作ります。プロジェクト名を”my-plugin”とします。
$ ionic start my-plugin blank $ cd my-plugin
Pluginのテンプレートのダウンロード
ionic-teamがプラグインのテンプレートを公開していますので、今回はこれを利用します。プラグイン開発用のディレクトリとしてplugins-devを作成し、その中にmy-cordova-pluginというパッケージ名でテンプレートをクローンします。
$ mkdir plugins-dev $ cd plugins-dev $ git clone https://github.com/ionic-team/cordova-plugin-template.git my-cordova-plugin
テンプレートの修正
今日の時点でのテンプレートにはいくつか問題があるので、最低限それらを直す必要があります。
package.jsonの修正
以前のpluginには必要ありませんでしたが、現在はこのファイルがないとプラグインとして追加することができません。package.jsonの内容は以下の通りです。
{ "name": "cordova-plugin-template", "version": "1.0.0", "cordova": { "id": "cordova-plugin-template", "platforms": [ "android", "ios", ] }, "description": "A starting point for a Cordova Plugin" }
{ "name": "my-cordova-plugin", "version": "1.0.0", "cordova": { "id": "my-cordova-plugin", "platforms": [ "android", "ios" ] }, "description": "A starting point for a Cordova Plugin" }
plugin.xmlの修正
最初のplugin.xmlは以下のようになっています。
<?xml version="1.0" encoding="UTF-8"?> <plugin xmlns="http://apache.org/cordova/ns/plugins/1.0" id="my-cordova-plugin" version="1.0.0"> <name>Cordova Plugin Template</name> <description></description> <license>MIT</license> <keywords></keywords> <repo>https://github.com/driftyco/cordova-plugin-template.git</repo> <issue>https://github.com/driftyco/cordova-plugin-template/issues</issue> <!-- android --> <platform name="android"> <js-module src="www/plugin.js" name="plugin"> <runs/> <!-- This is the window variable name you want, like window.MyCordovaPlugin --> <clobbers target="MyCordovaPlugin" /> </js-module> <config-file target="res/xml/config.xml" parent="/*"> <feature name="MyCordovaPlugin"> <param name="android-package" value="com.example.MyCordovaPlugin" /> <param name="onload" value="true" /> </feature> </config-file> <source-file src="src/android/com/example/MyCordovaPlugin.java" target-dir="src/com/example/" /> </platform> <!-- ios --> <platform name="ios"> <js-module src="www/plugin.js" name="plugin"> <runs/> <clobbers target="MyCordovaPlugin" /> </js-module> <config-file target="config.xml" parent="/*"> <feature name="MyCordovaPlugin"> <param name="ios-package" value="MyCordovaPlugin" onload="true" /> </feature> </config-file> <header-file src="src/ios/MyCordovaPlugin.h" /> <source-file src="src/ios/MyCordovaPlugin.m" /> </platform> </plugin>
今回はテンプレートで使っているパッケージ名”my-cordova-pugin”やプラグイン名”MyCordovaPlugin”と同じにしているので、修正するところはほとんどありません。別の名前を使う場合はそれに合わせてプラグイン名を全部修正します。
今回修正したplugin.xmlは以下の通りです。
<?xml version="1.0" encoding="UTF-8"?> <plugin xmlns="http://apache.org/cordova/ns/plugins/1.0" id="my-cordova-plugin" version="1.0.0"> <name>My Cordova Plugin</name> <description></description> <license>MIT</license> <keywords></keywords> <!-- android --> <platform name="android"> <js-module src="www/plugin.js" name="plugin"> <runs/> <!-- This is the window variable name you want, like window.MyCordovaPlugin --> <clobbers target="MyCordovaPlugin" /> </js-module> <config-file target="res/xml/config.xml" parent="/*"> <feature name="MyCordovaPlugin"> <param name="android-package" value="com.example.MyCordovaPlugin" /> <param name="onload" value="true" /> </feature> </config-file> <source-file src="src/android/com/example/MyCordovaPlugin.java" target-dir="src/com/example/" /> </platform> <!-- ios --> <platform name="ios"> <js-module src="www/plugin.js" name="plugin"> <runs/> <clobbers target="MyCordovaPlugin" /> </js-module> <config-file target="config.xml" parent="/*"> <feature name="MyCordovaPlugin"> <param name="ios-package" value="MyCordovaPlugin" onload="true" /> </feature> </config-file> <header-file src="src/ios/MyCordovaPlugin.h" /> <source-file src="src/ios/MyCordovaPlugin.m" /> </platform> </plugin>
plugin.jsの修正
wwwディレクトリの下にあるplugin.jsもプラグイン名に合わせて編集します。今回はプラグイン名を変更しませんでしたので、そのまま使います。plugin.jsは次のようになっています。
var exec = require('cordova/exec'); var PLUGIN_NAME = 'MyCordovaPlugin'; var MyCordovaPlugin = { echo: function(phrase, cb) { exec(cb, null, PLUGIN_NAME, 'echo', [phrase]); }, getDate: function(cb) { exec(cb, null, PLUGIN_NAME, 'getDate', []); } }; module.exports = MyCordovaPlugin;
MyCordovaPluginを使ってみる
ここまでできればテンプレートのプラグインを動かすことができます。
Pluginの追加
最初にプラグインをプロジェクトに追加します。
$ ionic cordova plugin add plugins-dev/my-cordova-plugin
以下のようにしてプラグインが追加されたことを確かめることができます。
$ ionic cordova plugin ls > cordova plugin ls my-cordova-plugin 1.0.0 "My Cordova Plugin"
Pluginの更新
プラグインを修正するなどして更新したい場合は、一旦削除して再度インストールします。
$ ionic cordova plugin rm my-cordova-plugin $ ionic cordova plugin add plugins-dev/my-cordova-plugin
削除するときはプラグインのパッケージ名を指定します。パッケージ名は上記のlsコマンドで確認できます。
もし、万一、プラグインのpackage.jsonのnameを直し忘れ、パッケージ名と事なってしまった場合は、削除した後の追加がエラーになります。その場合は、アプリの方のpackage.jsonを見て、間違ったパッケージ名をnpm コマンドでアンインストールします。
$ npm uninstall --save <間違ったパッケージ名>
そうすれば、再度プラグインを追加できるようになります。
アプリの作成
このプラグインは2つのメソッドを持っています。
// 引数の文字列をログとして出力する MyCordovaPlugin.echo("Hello!"); // 現在の時間をコールバックで返す MyCordovaPlugin.getDate(date => { console.log(date); });
確認するアプリを作るためにsrc/pages/homeの中のファイルを修正してきます。まず、home.htmlを以下のように変更します。
<ion-header> <ion-navbar> <ion-title> My Cordova Plugin </ion-title> </ion-navbar> </ion-header> <ion-content padding> <ion-item> Now: {{now}} </ion-item> </ion-content>
次にhome.tsを以下のようにします。今回のプラグインは”MyCordovaPlug”という名前でグローバルオブジェクトとして登録されます。したがって、window.MyCordovaPluginまたはMyCordovaPlugin という名前で呼び出すことができます。以下のコードでは、MyCordovaPluginを宣言して、呼び出せるようにしています。
また、constructorが呼ばれたたいみんぐでプラグインが読み込み終わっているとは限らないので、platform.ready() を使ってプラグインが読み終わったタイミングで呼び出すようにしてます。
import { Component } from '@angular/core'; import { NavController, Platform } from 'ionic-angular'; declare var MyCordovaPlugin: any; @Component({ selector: 'page-home', templateUrl: 'home.html' }) export class HomePage { public now: string; constructor(public navCtrl: NavController, public platform: Platform) { this.platform.ready().then(() => { MyCordovaPlugin.echo('Hello MyCordova Plugin'); MyCordovaPlugin.getDate(date => { this.now = date; }); }); } }
アプリを動かす
それではiPhoneでアプリを動かしてみます。ionicにiosのプラットフォームを追加して、Xcodeを起動します。
$ ionic cordova platform add ios $ ionic cordova prepare $ open platforms/ios/MyApp.xcworkspace/
Xcodeが起動したら、singingの設定を行い、”Build and Run”します。問題なく起動すれば、Xcodeのコンソールにechoした文字列が表示されます。
2017-12-12 11:49:01.054526+0900 MyApp[11733:844796] DiskCookieStorage changing policy from 2 to 0, cookie file: file:///Users/tetsuo/Library/Developer/CoreSimulator/Devices/714515F2-3CB2-4A6C-A0A0-F07B468E1E92/data/Containers/Data/Application/077502F7-0DE1-4B89-8748-BB097DD0E802/Library/Cookies/io.ionic.starter.binarycookies 2017-12-12 11:49:01.186879+0900 MyApp[11733:844796] Apache Cordova native platform version 4.4.0 is starting. 2017-12-12 11:49:01.187687+0900 MyApp[11733:844796] Multi-tasking -> Device: YES, App: YES 2017-12-12 11:49:01.243561+0900 MyApp[11733:844796] CDVWKWebViewEngine: trying to inject XHR polyfill 2017-12-12 11:49:01.577821+0900 MyApp[11733:844796] [MC] Lazy loading NSBundle MobileCoreServices.framework 2017-12-12 11:49:01.579284+0900 MyApp[11733:844796] [MC] Loaded MobileCoreServices.framework 2017-12-12 11:49:01.599056+0900 MyApp[11733:844796] CDVWKWebViewEngine will reload WKWebView if required on resume 2017-12-12 11:49:01.599216+0900 MyApp[11733:844796] Using Ionic WKWebView 2017-12-12 11:49:01.599794+0900 MyApp[11733:844796] [CDVTimer][handleopenurl] 0.128984ms 2017-12-12 11:49:01.602356+0900 MyApp[11733:844796] [CDVTimer][intentandnavigationfilter] 2.314031ms 2017-12-12 11:49:01.602999+0900 MyApp[11733:844796] [CDVTimer][gesturehandler] 0.096977ms 2017-12-12 11:49:01.603323+0900 MyApp[11733:844796] [CDVTimer][mycordovaplugin] 0.060022ms 2017-12-12 11:49:01.658638+0900 MyApp[11733:844796] [CDVTimer][splashscreen] 55.198014ms 2017-12-12 11:49:01.660286+0900 MyApp[11733:844796] [CDVTimer][keyboard] 1.402974ms 2017-12-12 11:49:01.660524+0900 MyApp[11733:844796] [CDVTimer][TotalPluginStartup] 60.863972ms 2017-12-12 11:49:06.491890+0900 MyApp[11733:844796] Hello MyCordova Plugin
また、画面上にも現在の時間が表示されます。
プラグインのコード
今回はテンプレートのコードをそのまま利用しましたが、独自の機能を実装するには、以下の”*”印がついたファイルを修正します。
my-cordova-plugin/ ├── LICENSE ├── README.md ├── package.json ├── plugin.xml ├── src │ ├── android │ │ └── com │ │ └── example │ │ └── MyCordovaPlugin.java (*) │ └── ios │ ├── MyCordovaPlugin.h (*) │ └── MyCordovaPlugin.m (*) └── www └── plugin.js (*)
Ionic native pluginの作成
準備
先ほど作成したCordova pluginをIonic native pluginとして使えるようにします。まず、最初にnative pluginに必要なコードをhttps://github.com/ionic-team/ionic-nativeからクローンします。ディレクトリはどこでも良いのすが、plugins-devの下に置くことにします。
$ cd plugin-devs $ git clone https://github.com/ionic-team/ionic-native.git
ionic-native/src/@ionic-native/pluginsの下には既存のIonic native pluginのソースコードが置いてあります。ビルドするときに邪魔になるので一旦pluginsの下を全て削除します。
$ rm -rf ionic-native/src/@ionic-native/plugins/*
Native Plugin用のファイルの生成
今回のNative Pluginを作るためのファイルを生成します。ionic-nativeのディレクトリの下で以下のコマンドを実行します。
$ cd ionic-native $ gulp plugin:create -n MyCordovaPlugin
正常に終了すると以下のファイルが生成されます。
src/@ionic-native/plugins/my-cordova-plugin/index.ts
ファイルの編集
上で生成したファイルは以下のようになっています。
/** * This is a template for new plugin wrappers * * TODO: * - Add/Change information below * - Document usage (importing, executing main functionality) * - Remove any imports that you are not using * - Add this file to /src/index.ts (follow style of other plugins) * - Remove all the comments included in this template, EXCEPT the @Plugin wrapper docs and any other docs you added * - Remove this note * */ import { Injectable } from '@angular/core'; import { Plugin, Cordova, CordovaProperty, CordovaInstance, InstanceProperty, IonicNativePlugin } from '@ionic-native/core'; import { Observable } from 'rxjs/Observable'; /** * @name My Cordova Plugin * @description * This plugin does something * * @usage * ```typescript * import { MyCordovaPlugin } from '@ionic-native/my-cordova-plugin'; * * * constructor(private myCordovaPlugin: MyCordovaPlugin) { } * * ... * * * this.myCordovaPlugin.functionName('Hello', 123) * .then((res: any) => console.log(res)) * .catch((error: any) => console.error(error)); * * ``` */ @Plugin({ pluginName: 'MyCordovaPlugin', plugin: '', // npm package name, example: cordova-plugin-camera pluginRef: '', // the variable reference to call the plugin, example: navigator.geolocation repo: '', // the github repository URL for the plugin install: '', // OPTIONAL install command, in case the plugin requires variables installVariables: [], // OPTIONAL the plugin requires variables platforms: [] // Array of platforms supported, example: ['Android', 'iOS'] }) @Injectable() export class MyCordovaPlugin extends IonicNativePlugin { /** * This function does something * @param arg1 {string} Some param to configure something * @param arg2 {number} Another param to configure something * @return {Promise<any>} Returns a promise that resolves when something happens */ @Cordova() functionName(arg1: string, arg2: number): Promise<any> { return; // We add return; here to avoid any IDE / Compiler errors } }
まず、プラグインの定義部分を以下のように変更します。
@Plugin({ pluginName: 'MyCordovaPlugin', plugin: 'my-cordova-plugin', // npm package name, example: cordova-plugin-camera pluginRef: 'MyCordovaPlugin', // the variable reference to call the plugin, example: navigator.geolocation repo: '', // the github repository URL for the plugin install: '', // OPTIONAL install command, in case the plugin requires variables installVariables: [], // OPTIONAL the plugin requires variables platforms: ['Android', 'iOS'] // Array of platforms supported, example: ['Android', 'iOS'] })
pluginNameはその下のクラス名と一致していなければなりません。pluginはパッケージ名、pluginRefはプラグイン名にします。最後にサポートするプラットフォームを記述します。
次にクラス内に必要なメソッドを追加します。今回のプラグインは2つのメソッドを持っているのでそれを記述します。
@Injectable() export class MyCordovaPlugin extends IonicNativePlugin { /** * This function does something * @param arg1 {string} Some param to configure something * @param arg2 {number} Another param to configure something * @return {Promise<any>} Returns a promise that resolves when something happens */ @Cordova() echo(arg1: string): void { } @Cordova() getDate(): Promise<any> { return; } }
最後に使用していないモジュールを削除して、import文は以下のようになります。
import { Injectable } from '@angular/core'; import { Plugin, Cordova, IonicNativePlugin } from '@ionic-native/core';
Native Pluginのビルド
作成したNative Pluginラッパーをビルドして利用できるファイルを生成します。ビルドはNative Pluginのトップディレクトリ(プロジェクト/plugins-dev/ionic-native)で行います。
$ npm run build
ビルドが終了すると以下のディレクトリが作られ、その中に必要なファイルが生成されます。
dist/@ionic-native/my-cordova-plugin
Native Pluginのインストール
作成したNative Pluginをインストールします。単純にプロジェクトのnode_modules/@ionic-nativeの下にコピーしても使えますが、アプリのビルドなどをしていると消えることがあるので、インストールした方が良いようです。インストールはプロジェクトのトップで以下のコマンドを実行します。
$ cd ../.. $ npm install --save plugins-dev/ionic-native/dist/@ionic-native/my-cordova-plugin
これでNative Pluginもインストールされました。
ls -l node_modules/@ionic-native/ total 0 drwxr-xr-x 34 tetsuo staff 1088 12 12 14:32 core drwxr-xr-x 8 tetsuo staff 256 12 12 14:42 http lrwxr-xr-x 1 tetsuo staff 67 12 12 17:45 my-cordova-plugin -> ../../plugins-dev/ionic-native/dist/@ionic-native/my-cordova-plugin drwxr-xr-x 10 tetsuo staff 320 12 12 14:32 splash-screen drwxr-xr-x 10 tetsuo staff 320 12 12 14:32 status-bar
Native Pluginの更新
インストールしたNative Pluginを更新するには一旦削除して、再度インストールします。
$ npm uninstall @ionic-native/my-cordova-plugin $ npm install --save plugins-dev/ionic-native/dist/@ionic-native/my-cordova-plugin
Native Pluginを使う
次に今作成したNative Pluginを使用します。まず、アプリのファイルsrc/pages/home/home.tsを以下のように修正します。
import { Component } from '@angular/core'; import { NavController, Platform } from 'ionic-angular'; import { MyCordovaPlugin } from '@ionic-native/my-cordova-plugin'; @Component({ selector: 'page-home', templateUrl: 'home.html' }) export class HomePage { public now: string; constructor(public navCtrl: NavController, public platform: Platform, public myCordovaPlugin: MyCordovaPlugin) { this.platform.ready().then(() => { this.myCordovaPlugin.echo('Hello MyCordova Plugin>>>>'); this.myCordovaPlugin.getDate().then(date => { this.now = date; }); }); } }
また、src/app/app.modue.tsにもプラグインを使用するための設定を追加します。
import { BrowserModule } from '@angular/platform-browser'; import { ErrorHandler, NgModule } from '@angular/core'; import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular'; import { SplashScreen } from '@ionic-native/splash-screen'; import { StatusBar } from '@ionic-native/status-bar'; import { MyCordovaPlugin } from '@ionic-native/my-cordova-plugin'; import { MyApp } from './app.component'; import { HomePage } from '../pages/home/home'; @NgModule({ declarations: [ MyApp, HomePage ], imports: [ BrowserModule, IonicModule.forRoot(MyApp) ], bootstrap: [IonicApp], entryComponents: [ MyApp, HomePage ], providers: [ StatusBar, SplashScreen, {provide: ErrorHandler, useClass: IonicErrorHandler}, MyCordovaPlugin ] }) export class AppModule {}
これで準備はできました。Cordova Pluginの時と同様にビルドすればアプリが動作するが確認できます。
結果は、先ほどと全く同じです。
まとめ
- Cordovaのプラグインは、手順さえ分かってしまえば、作るのはそれほど難しいものではありません。
- プラグインの構造が分かれば、既存のプラグインを改造することも簡単にできます。
- Ionic Native Pluginは、Cordovaのプラグインのラッパーなので、かなり簡単に作成できます。