CapacitorはCordovaと同じく、HTML/JavaScript/CSSでスマホアプリを開発できるフレームワークです。Ionic Frameworkを提供するIonicが主体となって開発されています。モダンなWeb技術を活用し、より高性能で安定したクロスプラットフォームアプリの開発が可能です。

そんなCapacitorではコミュニティベースのプラグインが多数ありますが、Cordovaと比べるとまだまだ少ないのが実情です。プラグインの不足は、アプリ開発の制約となる可能性があります。

そこで、自分たちのアプリで使えるように、Capacitorプラグインを作ってみましょう。今回は実用的な例として、指定された位置情報を使って地図アプリを開くシンプルなプラグインを作成していきます。

開発環境の準備

必要な開発環境

  • Node.js(LTS版推奨)
  • Xcode(iOS開発用)
  • Android Studio(Android開発用)
  • TypeScriptの基本的な知識

Capacitorプラグインのベースを作る

Capacitorプラグインを作る場合には、専用のコマンドが用意されています。これを使えばベースになるスケルトンコードを生成してくれるので便利です。

npx @capacitor/create-plugin

このコマンドを実行すると、対話式のウィザードが起動します。ウィザードに沿って、以下の情報を入力していきます:

  • プラグイン名: monaca-capacitor-plugin
  • パッケージID: io.monaca.capacitor.plugin.demo
  • クラス名: MonacaCapacitor
  • 説明: 位置情報から地図アプリを開くためのプラグイン
  • その他の情報(リポジトリURLや作者情報など)

プラグインの実装

1. Android向けの実装

Androidの実装は android/src/main/java/io/monaca/capacitor/plugin/demo/MonacaCapacitorPlugin.java を修正します。
位置情報を受け取り、デバイスの地図アプリを起動する処理を実装します。

package io.monaca.capacitor.plugin.demo;

// Capacitorプラグインとして認識させるためのアノテーション
@CapacitorPlugin(name = "MonacaCapacitor")
public class MonacaCapacitorPlugin extends Plugin {

    /**
     * 指定された緯度経度でGoogle Mapsを開くメソッド
     * Google Mapsアプリがない場合はブラウザで地図を開く
     */
    @PluginMethod()
    public void openMap(PluginCall call) {
        try {
            // プラグインに渡された緯度経度パラメータを取得
            Double latitude = call.getDouble("latitude");
            Double longitude = call.getDouble("longitude");

            // パラメータの有効性チェック
            if (latitude == null || longitude == null) {
                Log.e(TAG, "Latitude or longitude is null");
                call.reject("Latitude and longitude are required");
                return;
            }

            // Google Maps用のナビゲーションURIを生成
            Uri gmmIntentUri = Uri.parse("google.navigation:q=" + latitude + "," + longitude);
            Log.d(TAG, "Attempting to open Google Maps with URI: " + gmmIntentUri.toString());

            // Google Mapsアプリを開くためのIntentを作成
            Intent mapIntent = new Intent(Intent.ACTION_VIEW, gmmIntentUri);
            mapIntent.setPackage("com.google.android.apps.maps");

            try {
                // Google Mapsアプリを起動
                getActivity().startActivity(mapIntent);
                call.resolve();  // 成功をプラグインに通知
            } catch (ActivityNotFoundException e) {
                // Google Mapsアプリが見つからない場合の処理

                // ブラウザで開くためのGoogle Maps URLを生成
                Uri browserUri = Uri.parse("https://www.google.com/maps?q=" + latitude + "," + longitude);

                // ブラウザを開くためのIntentを作成して実行
                Intent browserIntent = new Intent(Intent.ACTION_VIEW, browserUri);
                getActivity().startActivity(browserIntent);
                call.resolve();  // 成功をプラグインに通知
            }
        } catch (Exception e) {
            // その他の例外が発生した場合の処理
            call.reject("Failed to open map: " + e.getMessage(), e);  // エラーをプラグインに通知
        }
    }
}

ここでのポイント

JavaScriptから呼び出し可能なメソッドを定義
「@PluginMethod()」アノテーションを使用すると、このメソッドをJavaScriptから利用できます。

処理結果をJavaScriptに通知
成功した場合は 「call.resolve()」 を呼び出し、処理が正常に終了したことを伝えます。
エラーが発生した場合は 「call.reject()」 を使ってエラー内容を返します。

この実装により、Androidアプリの地図機能を簡単に利用できるようになります。

2. iOS向けの実装

iOSの実装は /ios/Plugin/MonacaCapacitorPlugin.swift を修正します。
iOS向けにも同様に、Apple Mapsを開く機能を実装します。

// 必要なフレームワークをインポート
import Foundation  // 基本的なiOSの機能を提供
import Capacitor   // Capacitorプラグインのフレームワーク
import MapKit      // Apple Maps関連の機能を提供

// MonacaCapacitorPluginクラスの定義
@objc(MonacaCapacitorPlugin)
public class MonacaCapacitorPlugin: CAPPlugin, CAPBridgedPlugin{    
    public let identifier = "MonacaCapacitorPlugin"
    public let jsName = "MonacaCapacitor"
    public let pluginMethods: [CAPPluginMethod] = [
        CAPPluginMethod(name: "openMap", returnType: CAPPluginReturnPromise)
    ]

    @objc func openMap(_ call: CAPPluginCall) {
        // 必須パラメータ(緯度・経度)の存在確認
        guard let latitude = call.getDouble("latitude"),
              let longitude = call.getDouble("longitude") else {
            call.reject("Must provide latitude and longitude")
            return
        }

        // CLLocationCoordinate2Dオブジェクトを作成
        let coordinate = CLLocationCoordinate2D(
            latitude: latitude,
            longitude: longitude
        )

        // 指定された座標にPlacemarkを作成
        let placemark = MKPlacemark(coordinate: coordinate)
        let mapItem = MKMapItem(placemark: placemark)

        // Maps表示のオプションを設定
        // - 経路案内モード:車での移動
        // - 交通情報:表示する
        let launchOptions = [
            MKLaunchOptionsDirectionsModeKey: MKLaunchOptionsDirectionsModeDriving,
            MKLaunchOptionsShowsTrafficKey: true
        ] as [String : Any]

        // Apple Mapsアプリで地図を開く
        if mapItem.openInMaps(launchOptions: launchOptions) {
            // 成功した場合、Apple Mapsで開いたことを通知
            call.resolve([
                "opened": true,
                "using": "apple-maps"
            ])
        } else {
            // Apple Mapsでの表示が失敗した場合のフォールバック処理
            // Google MapsのWeb版URLを作成
            let googleMapsURL = URL(string: "https://www.google.com/maps?q=\(latitude),\(longitude)")

            // URLが有効で、かつ開くことが可能か確認
            if let url = googleMapsURL, UIApplication.shared.canOpenURL(url) {
                // ブラウザでGoogle Mapsを開く
                UIApplication.shared.open(url, options: [:]) { success in
                    if success {
                        // ブラウザでの表示が成功した場合
                        call.resolve([
                            "opened": true,
                            "using": "browser"
                        ])
                    } else {
                        // ブラウザでの表示が失敗した場合
                        call.reject("Failed to open map in browser")
                    }
                }
            } else {
                // どの方法でも地図を開くことができなかった場合
                call.reject("Failed to open map with any available method")
            }
        }
    }
}

iOS用のプラグインコードでは次の3つが重要となります。

1. JavaScript側からの参照のための情報(jsName)
プラグインをJavaScriptで呼び出すときに利用するための名称を決めるのが「jsName」です。

  • 役割:JavaScriptからプラグインを呼び出す際の名前を定義
  • 定義:public let jsName = "MonacaCapacitor"
  • 使用例:
    const { MonacaCapacitor } = Plugins;  
    MonacaCapacitor.openMap({ ... });

2. プラグインの内部識別子(identifier)

  • 役割:ネイティブ側でプラグインを一意に識別
  • 定義:
    public let identifier = "MonacaCapacitorPlugin"

3. プラグインメソッドの定義(pluginMethods)
プラグインをJavaScript側から呼び出すことができるメソッドを登録します。

  • 役割:利用可能なメソッドの登録と型定義
  • 定義:
    public let pluginMethods: [CAPPluginMethod] = [
      CAPPluginMethod(name: "openMap", returnType: CAPPluginReturnPromise)
    ]
  • 設定項目:
    • メソッド名(name):JavaScript側から呼び出す際の名前
    • 戻り値の型(returnType):
    • none:戻り値なし
    • callback:コールバック関数
    • promise:Promise形式

3. Web向けの実装

Capacitorの特徴として、Webプラットフォームもサポートしています。Web向けの実装も作成しましょう。

まず、src/definitions.tsでインターフェースを定義します:

export interface MonacaCapacitorPlugin {
  openMap(options: OpenMapOptions): Promise<void>;
}

export interface OpenMapOptions {
  latitude: number;
  longitude: number;
}

次に、src/web.tsで実際の実装を行います:

import { WebPlugin } from "@capacitor/core";
import type { MonacaCapacitorPlugin, OpenMapOptions } from "./definitions";

export class MonacaCapacitorWeb
  extends WebPlugin
  implements MonacaCapacitorPlugin
{
  async openMap(location: OpenMapOptions): Promise<void> {
    console.log("Opening map in browser", location);

    // Google Mapsを新しいタブで開く
    const url = https://www.google.com/maps?q=${location.latitude},${location.longitude};
    window.open(url, '_blank');

    return;
  }
}

プラグインの使用方法

1. Capacitorアプリのセットアップ

一番手軽なのはIonic CLIを利用する方法です。まずIonic CLIをインストールします:

npm install -g @ionic/cli

そして、Ionic CLIを使ってCapacitorアプリを作成します。typeオプションではreact/angular/vueから選択できます:

ionic start myApp tabs --capacitor --type=react

2. プラグインのインストール

必要なプラットフォームを追加します:

ionic capacitor add

作成したプラグインをnpmを使ってインストールします。ローカルのプラグインディレクトリを指定します:

npm install  [ローカルのプラグインディレクトリを指定]

その後、Capacitorの設定を更新し、プラグインを反映させます:

npm run build
npx cap sync

3. プラグインの使用

アプリケーションコードでプラグインを使用する例:

import { MonacaCapacitor } from "capacitor-plugin-demo";

async function showLocationOnMap() {
  try {
    // 東京タワーの位置を開く例
    await MonacaCapacitor.openMap({
      latitude: 35.6585805,
      longitude: 139.7454329
    });
  } catch (error) {
    console.error("地図アプリの起動に失敗しました", error);
  }
}

まとめ

Capacitorプラグインの開発は、以下の特徴があります:

  • TypeScriptを使用することで、型安全なインターフェースを定義できる
  • iOS、Android、Webの3プラットフォームで一貫した動作を実現
  • 必要な機能を独自に実装可能

今回作成したような位置情報プラグインは、多くのアプリで必要となる基本的な機能です。このような共通機能をプラグイン化することで、再利用性が高まり、開発効率が向上します。

参考リンク