Cordova : Plugins

Bonjour,

Objectifs

Cordova / Ionic propose un système de plugins dont le but est double :

Lors du dernier article (ici), j’ai testé différents plugins pour la gestion de la communication Bluetooth. L’idée est donc est de comprendre comment un plugin fonctionne et comment il est possible d’en mettre un en place. On fera le bilan à la fin de la journée.

Plugin tout basique

Documentation

Personnellement, je n’ai pas trouvé la documentation officielle hyper clair. Par contre, le lien suivant présente les différentes étapes pour développer un premier plugin tout bête. Les étapes suivantes sont une adaptation/traduction du deuxième lien. Si vous le lisez, vous pouvez passer directement à au prochain “grand chapitre”. J’ajoute juste quelques notes supplémentaires mais rien de plus.

Plugman

Cordova met à disposition un outil de gestion des plugins : Plugman. Parmi les options proposées, il existe une ligne de commande qui permet la création d’un plugin :

plugman create --name UVSDPlugin --plugin_id bzh-devbieres-plugins --plugin_version 0.0.1 --path uvsdplugin

Les paramètres sont assez clairs :). La commande génère un répertoire contenant différents fichiers que nous allons être amenés à modifier pour mettre en place le plugin : plugin.xml, src/, www/UVSDPlugin.js.

plugin.xml

La documentation officielle est ici. C’est intéressant à lire. La première version de mon fichier :

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

Je n’ai changé qu’une seule chose : clobbers. Ce paramètre permet d’indiquer le point d’accès aux différents fonctions/méthodes que nous allons mettre dans le plugin.

Cible = Android

N’ayant pas de IPhone sous la main (et de mac pour compiler), je ne supporte qu’Android. Et cela il faut le dire :

<engines>
<engine name="cordova-android" version=">=4.0.0" />
</engines>

Il faut également indiquer où se trouve le code (ici Java) :

<platform name="android">
<config-file target="config.xml" parent="/*">
<feature name="UVSD">
<param name="android-package" value="bzh.devbieres.plugin.USVD"/>
</feature>
</config-file>
<source-file src="src/android/bzh/devbieres/plugin/UVSD.java" target-dir="src/bzh/devbieres/plugin" />
</platform>

Le code natif devra donc se trouver dans le fichier : src/android/bzh/devbieres/plugin/UVSD.java.

Code Javascript

Plugman nous a généré un fichier UVSDPlugin.js :

var exec = require('cordova/exec');
exports.coolMethod = function(arg0, success, error) {
    exec(success, error, "UVSD", "coolMethod", [arg0]);
};

La méthode générée est finalement un passe plat vers l’interface Java qui sera décrit plus après. La ligne “importante” est la ligne “exec”. Il s’agit de la méthode qui va permettre à Cordova de faire le routing vers la bonne méthode dans la partie Native. Ici, le plugin ne supporte qu’un OS mais au besoin la méthode va être en capacité d’appeler la bonne fonction sur la bonne plateforme. Les paramètres :

  • success : un “callback”, c’est-à-dire une fonction qui pourra être appelée suite au succès du traitement,
  • error : idem mais pour une error,
  • UVSD: il s’agit du nom du service natif qui sera appelé,
  • coolMethod : à votre avis ?
  • [arg0] : différents paramètres qui sont fournis sur la forme d’un tableau.

Dans le cadre du test, je modifie la méthode en mettant en place mon habituel “service” ping qui répond pong :

exports.ping = function(arg0, success, error) {
    exec(success, error, "UVSD", "ping", [arg0]);
};

Le code Java

La classe Java doit être crée tel que le paramétrage le décrit (sinon à quoi cela sert ?). Voici un extrait de code de ma classe pour cette première étape :

public class UVSD extends CordovaPlugin {

  @Override
  public boolean execute(
    String action, 
    JSONArray args, 
    CallbackContext callbackContext
  ) throws JSONException {
    if ("ping".equals(action)) {
      ping(callbackContext);
      return true;
    } 
    return false;
  }

  private void ping(
    CallbackContext callbackContext
  ) {
    callbackContext.success("Pong"); 
  }
}

La méthode importante est la méthode “execute”. En fait, Cordova n’appelle pas directement la méthode sur le service mais passe le nom passé en paramètre à la méthode execute qui est finalement un routeur interne pour la gestion de la méthode. Le code “fonctionnel” est bien dans la méthode interne ping.

Intégration dans un projet Cordova

Une fois le plugin terminé, il faut l’intégrer dans un projet Cordova. Je vous passe les détails de la création du projet. Juste l’appel de la fonction :

uvsd.ping(
           "inutile"
           , function(msg) { alert(msg); }
           , function(err) { alert(err); }
       );

et le résultat :

Bilan

J’ai un squelette de plugin intégré dans mon projet.

Un truc que j’ai trouvé cependant assez pénible : le fait que le déboguage d’un plugin. Les étapes sont lourdes : suppression du plugin, ré-install etc … En fait, le plus simple reste presque de modifier directement le code dans le projet Cordova …

Communication entre Natif & JS

Quelle méthode ?

J’ai sûrement mal cherché mais je n’ai pas trouvé dans la documentation d’exemples de communication entre la couche native et la couche JS. La méthode que j’utilise est celle trouvée dans un plugin de Cordova : cordova-plugin-network-information. Dans un autre plugin (NFC), il utilise une autre méthode à base de commande Javascript mais la méthode est marquée comme Deprecated.

Mise en place

Pour simuler des évènements, j’ai intégré dans mon plugin un Timer Java qui exécute une méthode toutes les secondes. Trois écouteurs sont disponibles permettant à l’application de réaliser des actions en fonction de l’écouteur activé. Un seul écouteur s’exécute à la fois.

Java

Je vous fait grace des timers et autres random pour avoir que le code intéressant :

public class UVSD extends CordovaPlugin {
// [..]
  @Override
  public boolean execute(
    String action, 
    JSONArray args, 
    CallbackContext callbackContext
  ) throws JSONException {
    if ("ping".equals(action)) {
      ping(callbackContext);
      return true;
    } 
    else if("setCallBackTimer1".equals(action)) { this._callBackContextTimer1 = callbackContext;  return true; }
    else if("setCallBackTimer2".equals(action)) { this._callBackContextTimer2 = callbackContext;  return true; }
    else if("setCallBackTimer3".equals(action)) { this._callBackContextTimer3 = callbackContext;  return true; }
    return false;
  }

  [..]
  // La méthode handleTimerInternal est appelé par le Timer. Elle passe en paramètre le callBackContext tiré au sort.  
  public void handleTimerInternal(CallbackContext callbackContext) {
      PluginResult result = new PluginResult(PluginResult.Status.OK);
      result.setKeepCallback(true);
      callbackContext.sendPluginResult(result);
  }

Un CallBackContext comme son nom l’indique, représente les informations associées au fonction que la couche JS a passée en paramètre pour “ecouter” les retours. En fonction du Status, Cordova appelle le callback success ou failed. Le setKeepCallback est important : il indique s’il faut continuer à gérer le callback ou pas.

Javascript

Au niveau du code Javascript du plugin, c’est hyper simple :

exports.setCallbackTimer1 = function(onTimer) {  
    exec(
          onTimer
          , function(err) { console.log(err); }
          , 'UVSD'
          , 'setCallBackTimer1', []); 
};

Application

Au niveau de l’application, juste des compteurs :

Et bien voilà

Objectif atteint ! J’ai un plugin avec une communication remontante. Prochaine objectif : intégrer une librairie spécifique pour communiquer avec un device externe (Bluetooth ?). Mais c’est pour un autre demain :)

Liens