Create a custom Cordova plugin for Ionic4

I took me a good while to find out how to create a Cordova plugin for my Ionic 4 app and make it work. So, after I made it, I decided to document the steps that made it possible, for myself in the future and maybe for somebody else that would need it too.
This is how I made it:

Assumptions

Before starting, let me define the starting point:
  • Directories: our "root directory" would be ~/Ionic projects/, or whatever directory you use to save all your ionic projects.
  • Previous knowledge: the goal of this tutorial is to show how to create the plugin and make it work. So it will be a very easy plugin. I assume you will know how to make it a useful plugin. I assume you know Java, JavaScript and so on.
  • Android: as you can develop for Android on any platform, the tutorial will be focus on this platform. You will see that you can easily extrapolate it to iOS.
  • Linux or MacOs: the console commands I run are Linux-like commands (working in Linux and MacOS). Are you a Windows user? Most commands will also work, others you will have to "translate" to Windows cmd commands yourself.

1.- Java code

At some point, you will need to have your Java code for the Android platform, so why not have it set since the very beginning. This is how our Java code will look:
package com.joangape.myfirstplugin;

import android.widget.Toast;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CallbackContext;
import org.json.JSONArray;
import org.json.JSONException;

public class MyFirstPlugin extends CordovaPlugin {
    @Override
    public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
        if(action.equals("nativeToast")){
            nativeToast();
        }
        return false;
    }

    public void nativeToast(){
        Toast.makeText(webView.getContext(), "Hello World Cordova Plugin", Toast.LENGTH_SHORT).show();
    }
}
Special attention to the package name and the class name. The naming thing is very important when creating a plugin, so don't fool around with the names or the plugin will not work. Later we will create the plugin java file (now we only have the content) and, of course, you will have to name after the class.

Tip

If you want to edit it in Android Studio and get all the helps on coding we usually have, add the Cordova API to the gradle script. So, to the dependencies section in the file build.grade (Module: app) add the following line:
implementation group: 'org.apache.cordova', name: 'framework', version: '7.0.0'

2.- Create the plugin scaffold

For this we will use the tool plugman. In this tutorial it will be installed locally in the directory where we will store all of our custom plugins: ~/Ionic projects/custom_plugins/. Let's do it:
  1. mkdir custom_plugins
  2. cd custom_plugins
  3. sudo npm install -g plugman
  4. plugman create --plugin_id com.joangape.myfirstplugin --plugin_version 0.0.1 --name MyFirstPlugin
  5. cd MyFirstPlugin
  6. plugman platform add --platform_name android
  7. sudo plugman createpackagejson ./
There is some input required for step 7, you rather just accept all defaults (you can edit the json later).
The notation.with.dots is recommended on the plugin id, as some other may cause problems on Android and would require later edit (renaming).
Again, naming is important, pay attention to it on step 4.
On step 6 we create the scaffold for Android. You can add later the scaffold for iOS too, but always do step 7 after adding a platform.

3.- Update the JavaScript bridge

At this point we already have the very basics for our plugin, but nothing will work yet. Lets start editing the JavaScript bridge, which is the file
custom_plugins/MyFirstPlugin/www/MyFirstPlugin.jsand make it look like this:
var exec = require('cordova/exec');
exports.nativeToast = function (arg0, success, error) {
    exec(success, error, 'MyFirstPlugin', 'nativeToast', [arg0]);
};
Attention to the two strings parameters of exec. They are just strings, which may lead you to think you can give any value you fancy, but no. This function will be the bridge to you native code, so the first string must be the name of the class to be called, and the second the name of the method to be called. Be careful here.

4.- Set up the Java file

Time to put our native code in the plugin. Open the file
custom_plugins/MyFirstPlugin/src/android/MyFirstPlugin.javaand substitute the code inside with the code on step one. Save it and done.
Again, careful with the cd naming of thing. Has the java file the same name as the class?

4.1 Create an Ionic project

At this point we actually have a working Cordova plugin. What's missing, which is not less, is integrate it with our Ionic project. So let's create a project for testing in ~/Ionic projects/:
  1. ionic start my-project blank
  2. cd my-project
  3. ionic cordova platform add android

5.- Create an Ionic wrapper scaffold

Summing up, at this point we already have an Ionic plugin and an Ionic project. Supposedly it would be possible to use our plugin already and install it with plugman. In my experience, this only brings problems and also is very uncomfortable to use in this stage. Good thing is to integrate it with the Ionic framework and use it like any other plugin. For this, we need an Ionic wrapper, which we create following these steps in ~/Ionic projects/:
  1. git clone https://github.com/ionic-team/ionic-native
  2. cd ionic-native 
  3. npm install gulp 
  4. npm install 
  5. gulp plugin:create -n MyFirstPlugin
What we do is: clone the official ionic-native repo (1), install the tool gulp inside of it (3 and 4) and use gulp to create a wrapper scaffold (5). Visit the repo site if you would like to learn more about it (recommended).
Now we need to change this scaffold to adapt it to our needs. First step: edit
~/Ionic projects/ionic-native/src/@ionic-native/plugins/my-first-plugin/index.ts
to make it look like this:
import { Injectable } from '@angular/core';
import { Plugin, Cordova, IonicNativePlugin } from '@ionic-native/core';

@Plugin({
    pluginName: 'MyFirstPlugin',
    plugin: 'com.joangape.myfirstplugin',
    pluginRef: 'cordova.plugins.MyFirstPlugin',
    platforms: ['Android']
})

@Injectable()
export class MyFirstPlugin extends IonicNativePlugin {

    @Cordova()
    nativeToast(): Promise<any> {
        return;
    }
}
Again: naming, naming, naming. Don't change any string unless you know what you're doing.
Want a deeper understanding on what you're doing? Visit the repo site I mentioned above.

Let's compile the wrapper we just made and copy the result to our ionic project: go to
~/Ionic projects/ionic-native/
and run:
  1. npm run build
  2. cd ..
  3. cp -r ionic-native/dist/@ionic-native/plugins/my-first-plugin my-project/node_modules/@ionic-native
We first generate the compiled version of the plugin wrapper (1) and then manually copy it inside our Ionic project (3).
(Sorry for the step 2, I just want to be very clear on what directory we are on any time).

6.- Add and integrate the plugin and wrapper

So the plugin is done, the wrapper is done and copied into our project. The only thing missing is something you may also have already done if you added before a plugin to an Ionic project (which I assume you have done if you are reading this). That is: add the plugin to the project and register the provider.

6.1 Add the plugin from its source code

  1. cd ~/Ionic projects/my-project/
  2. ionic cordova plugin add ../custom_plugins/MyFirstPlugin
Note: At this point, npm deletes automatically the folder we copied to my-project/node_modules. This is something that has to be solved (see Important notice below). So the cp command has to be run again:
  1. cp -r ~/Ionic\ projects/ionic-native/dist/@ionic-native/plugins/my-first-plugin ~/Ionic\ projects/my-project/node_modules/@ionic-native

6.2.- Register the provider at app.module.ts

Now you need to import the plugin and add the provider into app.module.ts, so add the import line and the provider in @NgModule:

import {MyFirstPlugin} from '@ionic-native/my-first-plugin/ngx';

@NgModule({
    providers: [
        MyFirstPlugin
    ]
})
As you added the wrapper to node_modules directory, you import from @ionic-native, which is pretty cool. Don't forget the ngx at the end.
About @NgModule, you don't have to delete anything, just add a provider to the providers array.

6.3.- Use the plugin in home.page.ts

Now you can use the plugin just as any other plugin. Whenever you need it, import it, inject it and use it. In our example, we use it in home.page.ts like this:
import {MyFirstPlugin} from '@ionic-native/my-first-plugin/ngx';

constructor(private myFirstPlugin: MyFirstPlugin) {}

public test() {
  this.myFirstPlugin.nativeToast().then();
}

So we're done!! ðŸ˜ƒ

Important notice

Whenever you add or remove any plugin into your project or run npm install (and probably with other similar commands), the directories in node_modules will be automatically deleted by npm. I still could not find the way to prevent it. I am sure is about adding some lines to my-project/package.json, but still haven't found exactly what. If you know, please leave it in the comments.
When this happens, just copy them again like in step 5:
  1. cp -r ionic-native/dist/@ionic-native/plugin-name my-project/node_modules/@ionic-native/

Learn more

Bibliography

First thing first: this tutorial was inspired by the following documents, which are the bibliography of it:
  1. Cordova custom plugin easy than expected with Ionic wrapper
  2. Ionic Native Developer Guide
  3. Ionic Tutorial - Chapter - 22 ( How to create plugin - PART 1) ðŸ“º
None of these three worked for me completely, which is why I wrote this tutorial.

Make something useful

Maybe all you wanted is to show a toast with a fixed text in Android 😜, but probably you want to make some awesome plugin with lots of functionalities. Then you need to read further to extend your plugin. The place to start is Official Apache Cordova Plugin Development Guide. Also the 3rd point of the bibliography shows how do plugins work internally. There you will also learn how to make it work on iOS.


I hope you enjoyed this tutorial and it was useful to you. Please leave your comment if you feel like it!

Comments

  1. After doing following the tutorial step by step. I'm getting this error on my console. "ERROR Error: Uncaught (in promise): plugin_not_installed".

    I tried running both on browser on a device, Buh i got same error.

    ReplyDelete
    Replies
    1. Plugins do not work in the browser. They also don't work if you're using Ionic DevApp. You need to run the project using 'ionic cordova run android --device'.
      I cannot know why it does not work to you as I have not enough data. Maybe npm erased it (as warned in the tutorial)? Try to copy the folder again (see step 5)

      Delete
  2. This comment has been removed by a blog administrator.

    ReplyDelete
  3. hello, i tried and when i runn my app get error plugin not installed and i runing in my smartphone

    ReplyDelete
  4. Thanks a lot, worked flawlessly

    ReplyDelete

Post a Comment