Cordova based plugin with Swift & Kotlin for Ionic

Apache Cordova Log

When you want to add a new Cordova plugin to your Ionic project, most likely you will find examples how to do it in Java and Objective-C. However those days are thankfully way behind us and we can use much more modern languages.

The other newcomers problem in Cordova world is that most tutorials show you how to develop a plugin right into your github account, but yet I find this very unfriendly especially for a people coming from a web development who just want to do some playground work first. Because of that I decided to write a post to show how to create, test and develop a simple plugin in your local environment.

tl;dr
You can download the project from here and just reuse the contents from folder private as a start for your plugin.

Project setup

Firstly, since the versions may change over time here is Ionic and Cordova version printout that were used when writing this post:

ionic -v
6.20.8
cordova -v
11.1.0

Then let’s set up a new project:

ionic start cordova-plugin-swift-and-kotlin blank --type=angular

I used Angular, but you may as well use React or Vue. We won’t be utilizing it much.

At the moment of writing, the current version of Ionic uses by default Capacitor, so we need to switch it off and add iOS and Android platforms for Cordova:

ionic integrations disable capacitor
ionic cordova platform add ios android

Our project should look like that:

At the moment of writing Ionic doesn’t include build schematics for Cordova in Angular project. You can easily add it by running

ng add @ionic/cordova-builders

Create a local plugin

Now let’s create template for our plugin. Let’s call our plugin CalculatorPlugin that will do some tricky computing using native code. We will put the advanced logic in a single method sum that will accept two parameters and returns the sum of those.

First we create a folder local-plugins/calculator-plugin. This is when we will keep our clean version of plugin. First we need to add a package.json of plugin:

// package.json
{
  "name": "calculator-plugin",
  "version": "1.0.0",
  "description": "My first Cordova plugin",
  "cordova": {
    "id": "calculator-plugin",
    "platforms": [
      "ios",
      "android"
    ]
  },
  "keywords": [
    "cordova",
    "cordova-android",
    "cordova-ios",
    "swift",
    "kotlin"
  ],
  "author": "Dariusz Zabrzeński",
  "license": "MIT"
}

You can actually put anything in keywords here as well as description. If you decide to put your plugin at some point to github, then make sure the keywords make sense. Most important part here is the id which will be used later.

Now let’s add an Android plugin under local-plugins/calculator-plugin/android/io/ionic/starter:

//CalculatorPlugin.kt
package io.ionic.starter

import org.apache.cordova.CallbackContext
import org.apache.cordova.CordovaPlugin
import org.json.JSONArray
import org.json.JSONObject

class CalculatorPlugin : CordovaPlugin() {

  override fun execute(action: String, args: JSONArray, callbackContext: CallbackContext): Boolean {
    if (action == "sum") {
      val a: Double = args.getDouble(0)
      val b: Double = args.getDouble(1)

      val result = sum(a, b)

      callbackContext.success(result)
      return true
    }
    callbackContext.error("Unknown method called")
    return false
  }

  fun sum(a: Double, b: Double): JSONObject {
    return JSONObject("{" + "\"sum\" : " + (a + b) + "}")
  }
}

We use reverse-domain syntax as default in config.xml. When developing your own plugin, make sure that it corresponds with your config.xml.

Our code simply takes arguments as a Double and returns a JSON with a property sum. You can also use a primitive types or a JSON Array as a result. You can also skip the result and just rely on status.

Now we add a plugin for iOS under local-plugins/calculator-plugin/ios/

//CalculatorPlugin.swift
@objc(CalculatorPlugin)
class CalculatorPlugin : CDVPlugin {

  @objc(sum:)
  func sum(command: CDVInvokedUrlCommand) {
    let a = command.arguments[0] as? Double ?? 0
    let b = command.arguments[1] as? Double ?? 0

    let pluginResult = CDVPluginResult(
      status: CDVCommandStatus_OK,
      messageAs: [
          "sum": a+b
      ]
    )

    self.commandDelegate!.send(
      pluginResult,
      callbackId: command.callbackId
    )
  }
}

Similar to Android version, we do a heavy computing using the device native environment and return a JSON.

The last thing we need to do is to provide a plugin xml with configuration. This should land under local-plugins/calculator-plugin

<!-- plugin.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0"
    xmlns:android="http://schemas.android.com/apk/res/android"
    id="calculator-plugin"
    version="1.0.0">
    <engines>
      <engine name="cordova" version=">=10.0.0"/>
      <engine name="cordova-android" version=">=9.0.0"/>
    </engines>

    <name>Calculator Plugin</name>

    <description>My first plugin with Swift and Kotlin.</description>

    <author>Dariusz Zabrzeński</author>

    <keywords>ionic, cordova, swift, kotlin</keywords>

    <license>MIT</license>

    <!-- iOS -->
    <platform name="ios">
      <config-file target="config.xml" parent="/*">
          <feature name="CalculatorPlugin">
              <param name="ios-package" value="CalculatorPlugin" />
          </feature>
      </config-file>
    <source-file src="src/ios/CalculatorPlugin.swift" />
    </platform>
    <!-- Android -->
    <platform name="android">
      <config-file target="res/xml/config.xml" parent="/*">
        <preference name="GradlePluginKotlinEnabled" value="true" />
        <feature name="CalculatorPlugin">
            <param name="android-package" value="io.ionic.starter.CalculatorPlugin" />
        </feature>
      </config-file>
      <source-file src="src/android/io/ionic/starter/CalculatorPlugin.kt" target-dir="src/android/io/ionic/starter"/>
    </platform>
</plugin>

If you have ever developed a plugin for Cordova, then you will notice here two things:

  1. I skipped js-module – you can add it if you decide to publish your plugin as npm package
  2. There’s a new preference for android GradlePluginKotlinEnabled that will automatically let Android build to allow Kotlin files to work

The plugin folder should look as below:

Add plugin to project

We are almost done with the development of our plugin. Now we need to add it to platforms and add a call to them:

ionic cordova plugins add ./local-plugins/calculator-plugin

> cordova plugin add ./local-plugins/calculator-plugin
Installing "calculator-plugin" for android
Installing "calculator-plugin" for ios
Adding calculator-plugin to package.json

Then we need to find a way to call the plugin from our app. Let’s add a simple service:

// calculator-plugin.service.ts
import { Injectable } from '@angular/core';
import { Platform } from '@ionic/angular';

declare var cordova: any;

@Injectable({
  providedIn: 'root',
})
export class CalculatorPluginService {
  private readonly _pluginName: string = 'CalculatorPlugin';

  constructor(private platform: Platform) {}

  async sum(a: number, b: number): Promise<number> {
    await this.platform.ready();
    return new Promise((resolve, error) => {
      cordova.exec(
        (result: { sum: number }) => {
          resolve(result.sum);
        },
        (err: any) => {
          error(err);
        },
        this._pluginName,
        'sum',
        [a, b]
      );
    });
  }
}

Now let’s add a simple button to home component in our project:

<!-- home.page.html -->
<ion-header [translucent]="true">
  <ion-toolbar>
    <ion-title>
      Blank
    </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content [fullscreen]="true">
  <ion-header collapse="condense">
    <ion-toolbar>
      <ion-title size="large">Blank</ion-title>
    </ion-toolbar>
  </ion-header>

  <ion-button (click)="testPlugin()">Test plugin</ion-button>

</ion-content>

And a code behind for it:

// home.page.ts
import { Component } from '@angular/core';
import { CalculatorPluginService } from './calculator-plugin.service';

@Component({
  selector: 'app-home',
  templateUrl: 'home.page.html'
})
export class HomePage {
  constructor(private calculatorPlugin: CalculatorPluginService) {}

  async testPlugin() {
    const result = await this.calculatorPlugin.sum(1,3);
    alert(result);
  }
}
Note for non-angular developers: You can just focus on the cordova.exec method. The rest is DI / click handling for Angular.

Our app should display a button and when tapped it should show alert with a result of sum 1+3.

Run & Test

We have everything in place now. Let’s build the app and see if it shows the result.

Android

First build angular/react/vue solution and then build the android version:

ionic cordova build android

Open platforms/android in your Android Studio. Plugin code should land in android/app/src/main/java/io/ionic/starter/CalculatorPlugin.kt. Let’s run the app and see if the result is 4:

iOS

Time to check out how the plugin works in iOS:

ionic cordova build ios

If you receive an error around SWIFT_VERSION

error: Value for SWIFT_VERSION cannot be empty. (in target 'MyApp’ from project 'MyApp’)

Don’t worry. Open platforms/ios/MyApp.xcworkspace in Xcode. First time you will receive an error about unsupported Swift version. Go to your App build settings and add a Swift Language Version:

The second problem is that our Swift code won’t recognize Cordova libraries since they were built using Objective-C. We need to create a bridging header in which we will define which Objective-C files should be visible inside Swift code. Note that this needs to be done only once. If you add more plugins, you can keep original header:

// Briding-Header.h
#import <Cordova/CDV.h>

Yes, that’s the whole code.

Now you should get the following result:

Summary

As shown adding a new plugin for Ionic using Cordova is quite simply. Although Cordova has many legacy points, you can still use the latest features and modern languages like Swift and Kotlin. In next post I will show you how easy it is to add a Capacitor plugin.

You can find the whole code for this project here.

Extra tip: when developing a plugin for Android or iOS don’t build the Ionic app all time. Use the local Android Studio / Xcode debugger and update the code within the project. When it’s done you can copy the code to the plugin folder / repository.


Opublikowano

w

, , ,

przez

Komentarze

Jedna odpowiedź do „Cordova based plugin with Swift & Kotlin for Ionic”

  1. […] you are interested in Cordova plugin development, you can check my previous post when I wrote how to create a Cordova plugin using Swift and […]