Cordova Pluginの基本事項

# イントロダクション

Cordova Pluginを実装する場合の基本事項についてまとめました。

Cordovaプラグインについての記事

- [基本事項](https://qiita.com/KNaito/items/4a49ba098115085d74fb) <- _この記事_
- [JavaScript部分の実装](https://qiita.com/KNaito/items/af7b61f83888c95f0c7b)
- [Native部分の実装iOS] ... TODO
- [Native部分の実装Android] ... TODO
- [フックスクリプト](https://qiita.com/KNaito/items/65587f5d51974e8b4adf)
- [プラグインのテスト] ... TODO


# 必須ファイル

 cordova 6.5以前は、plugin.xmlだけが必須でした。
 cordova 7.0以後は、plugin.xmlのほか、package.jsonも必須となります。

# ミニマムなプラグイン構成

Cordova 7.0以後を想定して、ミニマムなプラグインを構成するplugin.xmlとpackage.jsonを考えています。


## ミニマムなplugin.xml

  ミニマムなplugin.xmlファイルは次のようになります。ここでは、プラグイン名をhello-world-pluginとしています。

```xml
<?xml version="1.0" encoding="UTF-8"?>
<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0" id="hello-world-plugin" version="0.0.1" >

  <name>HelloWorldPlugin</name>
  <description>HelloWorldPlugin</description>
  <author>Ken Naito</author>
  <license>MIT</license>
  <engines>
    <engine name="cordova" version=">=6.5.0" />
  </engines>

</plugin>
```

　この中で重要なのは、pluginディレクティブと、そのid属性、version属性です。これらは省略することが出来ません。

 idは、英数字とハイフンで区切るのが一般的です。これは、nodeのパッケージ名と同じになるようにしているからです。
 id名は、必ずしもnodeのパッケージ名と一致させる必要はありませんが、cordova 7.1以前では、nodeのパッケージ名と合っていないと正常に動作しない不具合があります。
　また、多くの公開されているプラグインのプラグインidは、「cordova-plugin-」という形になっているものが多いです。cordovaで公式に公開しているプラグインもすべてこの形式になります。

 version名は、semver形式で、メジャーバージョン、マイナーバージョン、パッチバージョンをピリオドでつないだものを指定して下さい。

`engines`は省略することも可能ですが、基本的には記述しておいた方が良いです。特に、古いcordovaだとうまく組み込めない可能性があります。現状では、6.5や7.0以上としておけば問題ないでしょう。

 `name`, `description`, `author`, `lisence`も省略することが可能ですが、一般的に何か記述しておいた方が良いでしょう。

## ミニマムなpackage.json

 package.jsonファイルは、cordova 7.0以後は必須となりました。最低限、nameとversionが記載されていれば利用可能です。

 ミニマムなpackage.jsonファイルは次のようになります。

```json
{
  "name": "hello-world-plugin",
  "version": "1.0.0"
}
```
 先にも述べた様に、package.jsonの`name`は、plugin.xmlの`widget`タグの`id`属性と同じにした方が良いです。
また、`version`は、plugin.xmlの`version`と同じにして下さい。

## ここまでのサンプルコードのプラグインを、プロジェクトに組み込む

プラグイン開発用のディレクトリを作成し(ここでは【plugin_dir】とします)、上記のplugin.xmlとpackage.jsonを配置してください。

```
【plugin_dir】
 ┣ package.json
 ┗ plugin.xml
```

　これで、ミニマムなプラグイン hello-world-pluginが出来ました。
  以下のようにして、プラグインをプロジェクトに組み込んでみましょう。

```shell
$ cordova create sampleDevelopPlugin   // サンプルプロジェクトの作成
$ cd sampleDevelopPlugin
$ cordova platform add android
$ cordova platform add ios
$ cordova plugin add 【plugin_dir】     // プラグインの組み込み
```

成功しましたでしょうか？　以下のコマンドで、正常に組み込まれたかどうかが分かります。

```shell
$ cordova plugin ls
```

また、次のコマンドでプラグインを解除することが出来ます。

```shell
$ cordova plugin rm hello-world-plugin
```

なお、このプラグインは何も機能を持っていないので、組み込んでも何もすることが出来ません。

# ネイティブコードの記述

 それでは、いよいよプラグインからネイティブコードを利用する方法へ進みましょう。

 プラグインでネイティブコードを利用したい場合、plugin.xmlを編集し、プラットフォームごとに `platform` ディレクティブで記述していく必要があります。

## Androidの場合

 `platform`ディレクティブに、`name`属性を`android`として設定します。
  
  例えば、最も簡単なプラグインの例として、ハローワールドプラグインでは、次のように記述しています。
 
```xml
  <platform name="android">
    <config-file target="res/xml/config.xml" parent="/*">
      <feature name="HelloWorldPlugin">
        <param name="android-package" value="com.example.plugin.HelloWorldPlugin"/>
      </feature>
    </config-file>
    <source-file src="src/android/HelloWorldPlugin.java" target-dir="src/com/example/plugin" />
  </platform>
```

  `source-file` ディレクティブで、組み込む必要のあるjavaコードを記述しています。src属性で、プラグイン内の相対アドレスで、ファイルを指定します。target-dir属性で、配置先をプロジェクトのplatforms/androidからの相対パスを指定します。(cordova-android のversion 7.0以後の場合は、platforms/android/app/src/main/javaからの相対パスに配置されます)
 
  `config-file` ディレクティブで、javascriptの `cordova.exec`メソッドでの呼び出し方を指定しています。`config-file`の `target` 属性と `parent` 属性は、常にこの値となります。
  `feature`ディレクティブで、呼び出し方を1つ指定します。`feature` タグのname属性は、`cordova.exec`メソッドで呼び出す場合の参照名を定義しています。

　また、`param`ディレクティブの value属性で、呼び出されるjavaクラスを指定します。この例では、`com.example.plugin.HelloWorldPlugin`クラスが指定されています。このjavaクラスは、`CordovaPlugin`クラスを継承している必要があります。`param`ディレクティブのnameは、androidの場合は、固定で`android-pacakge`とします。

　なお、`param`ディレクティブとして追加で次のように書くことも出来ます。

```xml
<param name="onload" value="true" />
```

この指定をすると、アプリ起動後にplugin （のネイティブコード）が初期化されます。この指定がないと、JavaScriptから最初の呼び出しが行われるまで、pluginの初期化が実行されません。

　なお、この例で実際に呼び出されるのは、`com.example.plugin.HelloWorldPlugin`の `execute`メソッドとなります。

　`cordova.exec`による呼び出し方は

```js
cordova.exec( success, fail, "HelloWorldPlugin", 【メソッド名】, 【パラメータ】);
```

となります。`success`は成功コールバック、`fail`は失敗コールバックです。
　第三引数の"HelloWorldPlugin"は、上述の `feature`タグのname属性で指定した文字列です。
  第四引数のメソッド名は、Androidの場合文字列であれば何でも良いですが、iOSとの整合性を考えると、なるべくキャメルケースの文字列にした方が良いでしょう。これが、ネイティブ側のexecuteメソッドの第一引数として渡されます。
　第五引数のパラメータは、かならず配列とします。一つしかパラメータがない場合、長さ1の配列とします。また、配列の要素は文字列でも数値でもJsonでも構いません。

　なお、cordova.execを直接アプリケーションから呼ぶことも出来ますが、通常はそうではなく、プラグイン内のjavascriptからcordova.execを呼び出すようにします。そして、アプリケーションから呼び出すのは、プラグイン内のjavascriptだけにする方が良いです。
　プラグインのjavascriptについては、[JavaScript部分の実装](https://qiita.com/KNaito/items/af7b61f83888c95f0c7b)を参照。

### ネイティブコードの実装

ごく簡単に、「HelloWorldPlugin」という文字列を返すだけのプラグインとした場合、Androidのネイティブ部分の実装は次のようになります。

```java:HelloWorldPlugin.java
package com.example.plugin;

import org.apache.cordova.CallbackContext;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.PluginResult;
import org.json.JSONArray;
import org.json.JSONException;

public class HelloWorldPlugin extends CordovaPlugin {

  @Override
  public boolean execute(String action, JSONArray args,
			CallbackContext callbackContext) throws JSONException {
    if ("message".equals(action)) {
      callbackContext.success("HelloWorld");
      return true;
    }
  }

}
```

ここでは詳しくは説明しませんが、メソッド名として`message`を呼び出した時に、"HelloWorld"という文字列を返す処理をしています。
パラメータは何も受け付けていません。

### サンプル：プラグインとしての構成

上記をまとめると、プラグインとしては次のようになります。

```
【plugin_dir】
 ┣ src
 ┃  ┗ android
 ┃     ┗ com/example/plugin/HelloCordovaPlugin.java
 ┣ package.json
 ┗ plugin.xml
```

### サンプル：アプリケーションからの呼び出し方

プラグイン内に、呼び出し用のjavascriptを用意していないので、ここでは
deviceready後に、次のように直接`cordova.exec`を使って呼び出します。

```javascript
var success = function (x) { alert(x); };
var fail = function () { alert("error!"); };
cordova.exec( success, fail, "HelloWorldPlugin", "message", []);
```


## iOSの場合

 androidの場合と同様に、platformディレクティブを使い、name属性として`ios`を設定します。

```xml
    <platform name="ios">
        <config-file target="config.xml" parent="/*">
            <feature name="HelloWorldPlugin">
                <param name="ios-package" value="CDVHelloWorldPlugin"/>
            </feature>
        </config-file>
        <header-file src="src/ios/CDVHelloWorldPlugin.h" />
        <source-file src="src/ios/CDVHelloWorldPlugin.m" />
    </platform>
```

config-fileディレクティブもandroidの時と同様です。target属性の指定の仕方がやや異なります。iOSでは、プラットフォームのrootにconfig.xmlファイルがあるためです。

featureタグもandroidの時とほぼ同様です。valueでは、Androidではjavaなのでパッケージ名付きでクラスを指定していましたが、iOSではObjective-Cなので、クラス名のみを記述します。パッケージ名がない分、他のライブラリとファイル名が重ならないように、クラス名にプラグイン固有のプレフィックスをつけることが普通です。ここでは、`CDV`をつけて、`CDVHelloWorldPlugin`としました。

header-fileディレクティブと、source-fileディレクティブで、Objective-Cのヘッダファイル、ソースファイルを指定します。Androidの時と異なり、target-dirを指定することは出来ません。

`cordova.exec`での呼び出し方は、androidと共通です。

```js
cordova.exec( success, fail, "HelloWorldPlugin", 【メソッド名】, 【パラメータ】);
```

このコードで呼び出されるネイティブコードは、CDVHelloWorldPluginクラスの、【メソッド名】メソッドです。すなわち、cordova.execから直接Objective-Cのメソッドが呼びだされることになります。なお、CDVHelloWorldPluginクラスは、
`CDVPlugin`クラスを継承している必要があります。

　Androidの時と同様ですが、cordova.execを直接アプリケーションから呼ぶことも出来ますが、通常はそうではなく、プラグイン内のjavascriptからcordova.execを呼び出すようにします。そして、アプリケーションから呼び出すのは、プラグイン内のjavascriptだけにする方が良いです。
　プラグインのjavascriptについては、[JavaScript部分の実装](https://qiita.com/KNaito/items/af7b61f83888c95f0c7b)を参照。


### ネイティブコードの実装

ごく簡単に、「HelloWorldPlugin」という文字列を返すだけのプラグインとした場合、Androidのネイティブ部分の実装は次のようになります。

```Objective-C:CDVHelloWorldPlugin.h
#import <Cordova/CDV.h>

@interface CDVHelloWorldPlugin : CDVPlugin

- (void) message: (CDVInvokedUrlCommand*)command;

@end
```

```Objective-C:CDVHelloWorldPlugin.m
#import "CDVHelloWorldPlugin.h"

@implementation CDVHelloWorldPlugin

- (void) say: (CDVInvokedUrlCommand*)command
{
    CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsString:@"HelloWorld"];
    [self.commandDelegate sendPluginResult:pluginResult callbackId:command.callbackId];
}

@end
```


## Windowsの場合

Windowsの場合はちょっと特殊です。そもそもWindowsの場合は、javascriptからデバイスの制御が全て行えるため、「ネイティブ」を呼び出す必要がありません。しかし、Android/iOSと呼び出し方を統一しておいた方が、アプリの実装を共通化出来るため好ましいです。そこで、Windowsの場合、デバイスを制御するようなjsコードをProxy形式（cordovaのproxyの仕様に従った形式のjavascript)で記述し、次のようにjs-moduleタグで指定するのが普通です。

```xml
    <platform name="windows">
        <js-module src="src/windows/HelloWorldPluginProxy.js" name="HelloWorldPlugin">
            <merges target="" />
        </js-module>
    </platform>
```

そして、HelloWorldPluginProxy.js内部で

```javascript
require("cordova/exec/proxy").add("HelloWorldPlugin", module.exports);
```

のように定義することで、Android/iOSと同様に `cordova.exec`メソッドで呼び出すことが出来るようになります。


# plugin.xmlファイル、package.jsonファイルの補足事項

## dependency
 プラグインが別のプラグインに依存している場合、plugin.xmlに次のように記述することで、自動的に依存先プラグインをインストールすることが出来る様になります。

```
<dependency id="【依存先プラグインのプラグインID】" version="【バージョン】" />
```

例えば、phonegap-plugin-pushの2.1.3に依存するのであれば、次の様になります。

```
<dependency id="phonegap-plugin-push" version="2.1.3" />
```

plugin.xmlにdependencyが記述してある場合、package.jsonにもdependenciesを記載しておいた方が良いでしょう。

例： `cordova-plugin-media` の場合 https://github.com/apache/cordova-plugin-media
`cordova-plugin-file`に依存しています。

```
 <dependency id="cordova-plugin-file" version="^6.0.0" />
```