Cordovaプラグインを開発して実際にjavascriptから呼び出すまで（アドジェネのインタースティシャル広告を例に）

## 本記事の対象者

* cordova でハイブリッドアプリを開発している、もしくは開発を検討している方
* cordova プラグインの開発フローを知りたい方

**cordovaって何？っていう方を対象にはしてません。**

## 概要

cordovaでハイブリッドアプリを開発しているといつか通らなければならない壁がcordovaプラグイン開発だ。
「基本的にサーバから情報取ってきてアプリ側は整形して表示させるだけだからプラグインの開発は不要だよ。」なんて思っているあなた！
今やアプリにサードパーティのSDKを入れるのはよくある事。
例えば以下のようなもの。

* 広告効果測定SDK
* 広告配信SDK
* クラッシュ分析SDK

世に知られているものであれば既にプラグインが公開されているが当然公開されていないものの方が多いので、その場合は作るしかない。

これらのSDKは基本ネイティブ向けなのでJavascriptからは直接アクセスできない為cordovaを介して操作する必要がある。
これを行う為に必要なのがcordovaプラグインとなる。

今回はSupershipさんが提供している「[Ad Generation（アドジェネ）](http://www.scaleout.jp/ad-generation/)の広告配信SDKを使ってインタースティシャル広告を表示するプラグインを開発しながらプラグイン開発フローを説明していこうと思う。ただしiOSのみ！

なおアドジェネのSDKはgithubで公開されている。
https://github.com/AdGeneration/sdk

プラグイン開発については公式サイトに記載があるが、最初のとっかかりとしてはわかりづらい。
[プラグイン開発ガイド](https://cordova.apache.org/docs/ja/latest/guide/hybrid/plugins/)

## 開発環境

* Mac OSX（El Capitan）
* npm が使える状態
* npm で cordova がインストールされている状態


## プラグイン開発フロー

1. plugman をインストールする
2. プラグインのスケルトンを作成
3. プラグインを実装する
4. プラグインをcordovaプロジェクトにインストール
5. javascript側でプラグイン呼び出し

### **1. plugman をインストールする**

plugman というcordovaプラグイン開発用ツールをインストールする。ただ、これを使わなくても自分でプラグインのディレクトリ構成を作れば開発はできる。

```
npm install -g plugman
```

### **2. プラグインのスケルトンを作成**

以下のコマンドでプラグインのスケルトンがサクッとできる。

```
plugman create --name [プラグイン名] --plugin_id [プラグインID] --plugin_version [プラグインバージョン]
```

今回は以下で実行

```
plugman create --name adgeneration --plugin_id cordoba-plugin-adgeneration --plugin_version 0.1.0
```

| 項目 | 説明 |
|:-----------|:------------|
|プラグイン名|プラグインの名前（cordovaプロジェクト内で呼び出す時のjavascriptのクラス名となる。）|
|プラグインID|実際にプラグインがインストールされる時に作成されるプラグインディレクトリの名前（「cordova-plugin-[プラグイン名]」が定番）npmで公開する時のIDにもなる。|
|プラグインバージョン|プラグインのバージョン|

スケルトンのディレクトリ構成はこうなる。

```text:プラグインディレクトリ構成
adgeneration
├─ plugin.xml
├─ src 
├─ www
│  └─ adgeneration.js
```

スケルトンでは作成されないが src の配下にそれぞれのプラットフォーム別のディレクトリを作成し、その中にネイティブのコードを入れるのが通例となる。
こんな感じ。

```text:srcディレクトリ構成
├─ src
│  ├─ android
│  └─ ios
│  └─ wp8
```

スケルトンで作成される adgeneration.js、plugin.xmlの2つのファイルは以下の用途となる。

| ファイル | 説明 |
|:-----------|:------------|
|adgeneration.js|これから実装するネイティブコードとcordovaプロジェクトのインタフェースみたいなもの|
|plugin.xml|基本的なプラグイン情報の他、cordovaプロジェクトのjavascriptから呼び出された時に紐づけられるネイティブ側のソースコードファイル、フレームワーク等を指定する定義ファイル。公式ドキュメントは[ここ](https://cordova.apache.org/docs/en/latest/plugin_ref/spec.html)|

生成された内容は以下。

```xml:plugin.xml
<?xml version='1.0' encoding='utf-8'?>
<plugin id="cordoba-plugin-adgeneration" version="0.1.0" xmlns="http://apache.org/cordova/ns/plugins/1.0" xmlns:android="http://schemas.android.com/apk/res/android">
    <name>adgeneration</name>
    <js-module name="adgeneration" src="www/adgeneration.js">
        <clobbers target="cordova.plugins.adgeneration" />
    </js-module>
</plugin>
```

```js:adgeneration.js
var exec = require('cordova/exec');

exports.coolMethod = function(arg0, success, error) {
    exec(success, error, "adgeneration", "coolMethod", [arg0]);
};
```

coolMethod にこれから実装していく関数名を入れる。arg0 は関数に渡す引数となる。

### **3. 実装する**

まずは関数名を変更します。今回はインターステシャル広告を表示するので coolMethod を showInterstitial に変更。また広告表示に広告枠IDを指定する必要があるのでそれを引数に渡すので arg0 を locationId に変更。

結果 adgeneration.js はこうなる。

```js:adgeneration.js
var exec = require('cordova/exec');

exports.showInterstitial = function(locationId, success, error) {
    exec(success, error, "adgeneration", "showInterstitial", [locationId]);
};
```

plugin.xmlではどのネイティブコードを利用してどのフレームワークを利用するのかを定義する。
フレームワークについては[インタースティシャル_iOSSDK導入ガイド](https://github.com/AdGeneration/sdk/wiki/インタースティシャル_iOSSDK導入ガイド)に記載されている通りに設定する。

スケルトンで生成されたjs-moduleタグの下に以下を追加。今回はiOSのみだが必要なプラットフォーム分だけplatformタグを追記していくイメージとなる。

```xml:plugin.xml
    <!-- ios -->
    <platform name="ios">
        <config-file target="config.xml" parent="/*">
            <feature name="adgeneration">
                <param name="ios-package" value="CDVAdgeneration"/>
            </feature>
        </config-file>

        <header-file src="src/ios/CDVAdgeneration.h" />
        <source-file src="src/ios/CDVAdgeneration.m" />

        <source-file src="src/ios/Adgeneration/ADG.framework" custom="true" framework="true" />

        <framework src="CoreGraphics.framework"/>
        <framework src="AdSupport.framework"/>
        <framework src="QuartzCore.framework"/>
        <framework src="Security.framework"/>
        <framework src="MediaPlayer.framework"/>
        <framework src="CoreTelephony.framework"/>
        <framework src="SystemConfiguration.framework"/>
    </platform>
```

Xcodeの「Build Phases」にて「Link Binary with Libraries」上でStatusをOptionalにする場合は `weak="true"` という属性を設定しておく。
また、リファレンス形式でframeworkを追加する場合は `custom="true" type="projectReference"` を設定する。

ネイティブコードの構成は以下の通り

```text:srcディレクトリ構成
├─ src
│  ├─ ios
│  │  ├─ ADG.framework
│  │  ├─ CDVAdgeneration.h
│  │  └─ CDVAdgeneration.m
```

ネイティブコードの内容は以下の通り。説明は省略。

```objc:CDVAdgeneration.h
#import <Cordova/CDV.h>        
#import <UIKit/UIKit.h>        
#import <ADG/ADGManagerViewController.h>
#import <ADG/ADGInterstitial.h>

@interface CDVAdgeneration: CDVPlugin <ADGManagerViewControllerDelegate, ADGInterstitialDelegate>
  
- (void)sayHello:(CDVInvokedUrlCommand*)command;
@property (nonatomic, retain) ADGManagerViewController *adg;
@property (nonatomic, retain) ADGInterstitial *interstitial;

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

@end
```

```objc:CDVAdgeneration.m
#import "CDVAdgeneration.h"

@implementation CDVAdgeneration

- (void)showInterstitial:(CDVInvokedUrlCommand *)command
{
    _interstitial = [[ADGInterstitial alloc] init];
    _interstitial.delegate = self;
    [_interstitial setLocationId:@"10723"]; // アドジェネが用意しているテスト用広告枠ID
    [_interstitial show];
}

- (void)ADGManagerViewControllerReceiveAd:(ADGManagerViewController *)adgManagerViewController
{
    NSLog(@"%@", @"ADGManagerViewControllerReceiveAd");
}

- (void)ADGManagerViewControllerFailedToReceiveAd:(ADGManagerViewController *)adgManagerViewController code:(kADGErrorCode)code {
    NSLog(@"%@", @"ADGManagerViewControllerFailedToReceiveAd");
    switch (code) {
        case kADGErrorCodeExceedLimit:
        case kADGErrorCodeNeedConnection:
        break;
        default:
            [adgManagerViewController loadRequest];
            break;
    }
}

- (void)ADGManagerViewControllerOpenUrl:(ADGManagerViewController *)adgManagerViewController{
    NSLog(@"%@", @"ADGManagerViewControllerOpenUrl");
}


- (void)ADGInterstitialClose
{
    NSLog(@"%@", @"ADGInterstitialClose");
}

@end

```


### **4. プラグインをcordovaプロジェクトにインストール**

最後に作成したプラグインをcordovaプロジェクトにインストールしてみる。
プラグインをインストールするには該当のcordovaプロジェクト直下で以下を実行する。
※cordovaプロジェクトは `cordova create [プロジェクト名]` でスケルトンプロジェクトが作成される。

`cordova plugin add [プラグインディレクトリパス]`
※ちなみにプラグインディレクトリパスでの指定以外に、プラグインを npm で公開すればプラグインIDのみで、gitリポジトリに登録しておけばリポジトリURL指定でインストールできる。

上記を実行する事でpluginsディレクトリにプラグインIDの名前でインストールされる。

```text:プラグインディレクトリ構成
plugins
│  └─ cordova-plugin-adgeneration
│  └─ fetch.json
```

同時に fetch.json というファイルが生成される。
※このファイルが何に利用されるかは未調査

```json:fetch.json
{
    "cordova-plugin-adgeneration": {
        "source": {
            "type": "local",
            "path": "[プラグインディレクトリパス]"
        },  
        "is_top_level": true,
        "variables": {}
    }   
}
```

さらに以下のコマンドでiOSプラットフォームを追加すると ios.json というファイルが生成される。
※このファイルが何に利用されるかは未調査

```
cordova platform add ios
```

```json:ios.json
{
    "prepare_queue": {
        "installed": [], 
        "uninstalled": []
    },  
    "config_munge": {
        "files": {}
    },  
    "installed_plugins": {
        "cordova-plugin-adgeneration": {
            "PACKAGE_NAME": "io.cordova.hellocordova"
        },  
        "cordova-plugin-whitelist": {
            "PACKAGE_NAME": "io.cordova.hellocordova"
        }   
    },  
    "dependent_plugins": {}
}
```
※「cordova-plugin-whitelist」は `cordova create` した時に生成されるconfig.xmlにデフォルトプラグインとして設定してあるので自動的にインストールされる。

### **5. javascript側でプラグイン呼び出し**

今回の例でいうとjavascript側では以下のように呼び出せばネイティブのコードが実行される。

```
adgeneration.showInterstitial(id)
```