中安拓也のブログ

プログラミングについて書くブログ。 Twitterやってます @l08084

自作Cordovaプラグインの戻り値をPromiseにする

はじめに

今回は、以前自作したCordovaプラグイン、cordova-plugin-cache-deleteについて、引数に成功・失敗時のコールバック関数を受け取る形式から、コールバック関数を使わないPromiseベースのプラグインに変更する改修を実施していきます。

関連記事

自作Cordovaプラグイン、cordova-plugin-cache-deleteについて書いた過去記事です。

Webviewのキャッシュを削除するCordovaプラグインを作成しました - 中安拓也のブログ

初めてのCordovaプラグイン公開 - 中安拓也のブログ

cordova.execについて

そもそも、cordova-plugin-cache-deleteで、引数に成功時・失敗時のコールバック関数を受け取る形式を採用していたのは、ネイティブプラットフォーム(今回のプラグインではAndroid)と通信するためのcordova.execメソッドが成功時・失敗時のコールバック引数を受け取る仕様になっているためです。

下記がcordova.execメソッドになります。第一引数が成功時に呼び出されるコールバック関数、第二引数が失敗時のコールバック関数になっています。以下サイトから引用してます。

Plugin Development Guide - Apache Cordova

cordova.exec(function(winParam) {},
             function(error) {},
             "service",
             "action",
             ["firstArgument", "secondArgument", 42, false]);

上記のcordova.execの呼び出し方を変えることで、コールバックを廃止した、Promiseベースのプラグインに変更することができます。そうすることで、コールバックの多重ネスト問題(コールバック地獄)が発生しなくなるなどのメリットがあります。

プラグインの改修

cordova-plugin-cache-deleteのディレクトリ構造です。

.
├── LICENSE.txt
├── README.md
├── package.json
├── plugin.xml
├── src
│   └── android
│       ├── CacheDelete.java
│       └── android.iml
└── www
    └── CacheDelete.js

CordovaプラグインのIFを、コールバック関数を使用する形式から、Promiseを返す形式に変更したいので、プラグインのIF(ネイティブプラットフォームの呼び出し方)を定義している下記のJavaScriptファイルを改修していきます。

改修前: www/CacheDelete.js

var exec = require("cordova/exec");

module.exports = {
  deleteCache: function (success, error) {
    exec(success, error, "CacheDelete", "deleteCache", []);
  },
};

上記は、改修前のJavaScriptファイルになります。success引数とerror引数でコールバック関数を受け取り、cordova.execメソッドに渡しています。

上記のJavaScriptファイル経由でプラグインを呼び出す場合、次のような形式で呼び出す必要があります。成功時・失敗時のハンドリングを引数のコールバック関数で行っており、戻り値はありません。

CacheDelete.deleteCache(successCallback, errorCallback)

なお、上記のJavaScriptファイルで呼び出されるネイティブプラットフォーム側のコードは以下のようになっています。

src/android/CacheDelete.java

package jp.l08084.plugin;

import android.util.Log;

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

import java.io.File;

public class CacheDelete extends CordovaPlugin {
    private static final String TAG = "CacheDelete";
    private static final String DELETE_CACHE_MESSAGE = "Cordova CacheDelete.deleteCache() called.";
    private static final String ERROR_MESSAGE = "Failed to delete the cache, error";

    @Override
    public boolean execute(String action, JSONArray args, final CallbackContext callbackContext)
            throws JSONException {
        Log.v(TAG, DELETE_CACHE_MESSAGE);
        if("deleteCache".equals(action)) {
            deleteCache(callbackContext);
            return true;
        }
        return false;
    }

    private void deleteCache(final CallbackContext callbackContext) {
        File cacheDir = cordova.getActivity().getApplicationContext().getCacheDir();
        clearCacheFolder(cacheDir, callbackContext);
    }

    private void clearCacheFolder (File dir, final CallbackContext callbackContext) {
        try {
            if (dir != null && dir.isDirectory()) {
                for (File child : dir.listFiles()) {
                    if (child.isDirectory()) {
                        clearCacheFolder(child, callbackContext);
                    }
                    child.delete();
                }
            }
            callbackContext.success();
        } catch (Exception ex) {
            Log.e(TAG, ERROR_MESSAGE, ex);
            callbackContext.error(ERROR_MESSAGE);
        }
    }

}

処理の成功時にcallbackContext.success();、失敗時にcallbackContext.error(ERROR_MESSAGE);を呼び出すことで、JavaScriptファイルから渡された成功時・失敗時のコールバック関数を呼び出しています。

それでは、JavaScriptファイルを改修してPromiseベースのプラグインに切り替えていきます。

改修後: www/CacheDelete.js

var exec = require("cordova/exec");

module.exports = {
  deleteCache: function () {
    return new Promise(function(resolve, reject) {
      exec(resolve, reject, "CacheDelete", "deleteCache", []);
    });
  },
};

成功(success)、失敗(error)時に渡していたコールバック関数の代わりにPromiseのresolverejectを渡すように改修しています。

動作確認

改修後のプラグインは以下のように呼び出すことができます。

import { Component } from '@angular/core';
import { Platform } from '@ionic/angular';

// アンビエント宣言
declare var CacheDelete: any;

@Component({
  selector: 'app-tab1',
  templateUrl: 'tab1.page.html',
  styleUrls: ['tab1.page.scss'],
})
export class Tab1Page {
  constructor(private platform: Platform) {}

  public ngOnInit(): void {
    this.platform.ready().then(() => {
      if (this.platform.is('android')) {
        // delete cache
        CacheDelete.deleteCache()
          .then(() => console.log('delete cache success!!'))
          .catch((error) => console.error(error));
      }
    });
  }
}

参考サイト

Cordova プラグインのコールバック地獄から脱却!Non-Angular アプリケーションでも Ionic Native を利用するという選択 - Qiita

GitHub - chemerisuk/cordova-plugin-firebase-analytics: Cordova plugin for Firebase Analytics

angular - Cordova (Ionic2) custom plugin: manage Angular2 new Promise() with cordova.exec() successHandler - Stack Overflow

Cordovaをdisる人類全員に読んでほしい「Cordovaつらいを考える」|榊原昌彦|note