Page tree
Skip to end of metadata
Go to start of metadata

This guide explains, how to develop Android / Java plugins for Smartface.

It is aimed for native plugin developers and if you would like to learn more about using plugins in a Smartface project as a Smartface (JavaScript) developer, you may refer to the Developing and Using Smartface Plugins document.

Plugin Development Prerequisites

AndroidPluginTemplate.zip and Filling the Template

Plugin developer will be provided with AndroidPluginTemplate.zip (You can download it from here).

This zip file contains two things:

  • io.smartface.plugin with SMFJS and SMFJSObject Template Classes inside. These classes will allow plugin developer to access the JavaScript Engine.
  • io.smartface.SmartfaceDemo contains activity with name A, this is the main activity class for Smartface. This will give you access to the main activity of Smartface ex; passing it in intent to lunch the activity.

Plugin developer needs to add these two packages to the source of Android plugin project (which is any Android project created with Android Studio or Eclipse) to be able to use them and to avoid compile errors. This will be explained in more detail and with examples later in this document.

The packages must be included in any Android plugin in the exact path with no change in package names.

SMFJS Template Content

SMFJS has evaluate function that enables the plugin developer to evaluate JavaScript code within a plugin class.

SMFJS Object Template Content

SMFJSObject represents a reference to a JavaScript object (object, method, parameter, etc). It contains constructors that can take empty or int, double, String, Boolean, JSONObject, or JSONArray as a parameter.

Important Methods :

  • hasProperty: Ex. if there is a JavaScript object that has properties and sent to the plugin class as SMFJSObject jsobj; calling jsobj.hasProperty(propertyname) on plugin side will return a Boolean depending on wither the jsobj has that property or not.
  • getProperty
  • Gets the value of the property.
  • setProperty
  • Sets the value of property.
  • deleteProperty
  • getPropertyAtIndex
  • setPropertyAtIndex
  • getPropertyNameArrayLength
  • copyPropertyNames
  • isEqual
  • isStrictEqual
  • isFunction
  • isNull
  • isUndefined
  • isBoolean
  • isNumber
  • isString
  • isObject
  • isObjectOfClass
  • toBoolean
  • toDouble
  • toInt
  • toString
  • callAsFunction

Important Note

Complete your native application before merging it with Smartface Plugin application.

Android-manifest

  • Smartface uses a multidex application which is specified in the manifest application tag as

android:name= "android.support.multidex.MultiDexApplication"
If plugin developer ever needs to implement his own Application class, he/she MUST extend MultiDexApplication.

  • If the plugin needs a change in the manifest from the plugin user, the user can make this change in Smartface project manifest. Smartface project manifest has a higher priority if same tag containing same name exists in both Smartface project manifest and the plugin manifest. Example:

<meta-data
android:name=”com.facebook.sdk.ApplicationId”
android:value=”\ 858073900973345″ />
"Facebook application ID is required and the plugin user can specify his/her own Facebook application ID in the Smartface project manifest, which will be the one used in the published app."

Input Files
If the same file exists both in plugin and Smartface project files, those files have higher priority.

For Example: Parse needs to have ParseConfig.js input file. If the plugin developer already has that file for testing the plugin and also if the plugin user puts his/her own ParseConfig.js in the project, then it will be the one working in the app when it runs.

Version of Google Play Services
Plugin developer MUST use these versions of appcompat and Google Play services if the plugin uses any service.

‘com.google.android.gms:play-services:9.4.0’
‘com.android.support:appcompat-v7:24.+’

if android.support:design needed this version must be used

'com.android.support:design:24.+'

App Theme
Smartface uses ActionBarActivity so it’s a MUST for plugin developer to use Theme.AppCompat theme (or descendant) for plugin.

Necessary Files

Main Activity
Plugin output must be in a form of .apk even if it’s a non-UI plugin. So the plugin developer needs to put the non-UI plugin inside an android project with dummy main activity, which will be ignored by Smartface plugin parser engine. Any other activity will be parsed.

PluginConfig.JSON
For an Android plugin, a JSON file is necessary with this specific name “PluginConfig.JSON ” (case sensitive) under assets folder, this file must contain an JSON array with the full path of a class i.e. PackageName.ClassName of any class that JavaScript developer will be allowed to create instance of or use it's own methods.

{"classes":["smartface.facebookplugin.controller.Facebook"]}

This JSON array can contain multiple classes and will be merged by the CLI tool for multiple plugins. Example: Adding both Facebook and Google Analytics plugin will result in the following:

{"classes":["smartface.googleanalyticsplugin.controller.GoogleAnalytics","smartface.facebookplugin.controller.Facebook"]}

Types

autoSupported Types

int,double,float,long,short,string,boolean and their arrays don’t need to add any extra thing. Just declare as var x = 5.5;

Objects Supporting

To support any object type other than the previous autoSupported ones, as return type, parameter or field you need to do one step extra .
In pluginConfig.JSON you will add the fullPackage.ClassName same as what we do to expose any plugin class to the JavaScript.

Let’s say we want to use Android JSONObject as parameter, return type or field in our plugin class that will be passed from the JavaScript.

{"classes":["smartface.helloplugin.Hello","org.json.JSONObject","java.lang.String"]}

After that you write in the JavaScript like;

var str = new String("this is smartface");

var jobj = new JSONObject();
jobj.put("key", 1.5);
jobj.put("name", str);

Hello.sayHello("Landroid/content/Context;", jobj);

Access of JavaScript Plugin User

Any public method in the plugin class can be accessed by the JavaScript developer. 

Provided Functionality

Smartface Main Activity
Plugin developer will not get direct access to main activity functionality of Smartface, but he/she will be passed a reference of it in any method parameter or the class constructor as parameter that is needed.

Example :

public Facebook(Activity activity) {
	this.activity = activity;
}

And the JavaScript developer will call it as :

var fb = new Facebook("Landroid/app/Activity;");

Plugin developer should explain to the JavaScript developer how to pass the activity parameter in the plugin methods signature documentation for the JavaScript developer.

Context of the Smartface Application
Plugin developer can get the context of the application as a parameter in any method. Example: Let’s say Facebook has a method called myTest that takes context:

public static String myTest(Context ctx) {
	//code
	}

This will get the application context once the JavaScript developer writes :

fb.myTest("Landroid/content/Context;");

Plugin developer should explain to the JavaScript developer, how to pass the context parameter in the plugin methods signature documentation for the JavaScript developer.

OnActivityResult
As mentioned above, plugin developer will not be provided with the main activity and its functionality, there will be automatic handling for OnActivityResult to be passed to any plugin class mentioned in the PluginConfig.JSON, if the class has the method implemented in it.

Example : If PluginConfig.JSON contains

{
	"classes" : ["smartface.facebookplugin.controller.Facebook"]
}

And inside the Facebook Class there is a method with this signature:

public static void onActivityResult(int requestCode, int resultCode, Intent data) {
	//code
}

It will be called automatically when Smartface main activity’s onActivityResult is fired.

Interacting with JavaScript from a Plugin

JavaScript Object Reference

smfJSObject
If the class has SMFJSObject variable initialized in it with this exact name smfJSObject, it will be initialized with the JavaScript object reference of this class once JavaScript developer calls the new constructor for this class.

Example :

public SMFJSObject smfJSObject = null;

It's initialized within Facebook class and in JavaScript as below:

var fb = new Facebook("Landroid/app/Activity;");

Then SMFJSObject value will be reference of fb.

smfJSClass
If the class has SMFJSObject variable initialized in it with this exact name smfJSClass, it’ll be initialized automatically with the JavaScript object reference of this class.

Example:

public static SMFJSObject smfJSClass= null; 

It's initialized within Parse class and when notification arrives the service call onParsePushReceived to trigger the call back attached to the class in the JavaScript side:

public static void onParsePushReceived() {
	try {
		SMFJSObject onParsePushReceive = smfJSClass.getProperty("onParsePushReceive");
		onParsePushReceive.callAsFunction(smfJSClass, null);

	} catch (Exception e) {
		e.printStackTrace();
	}
}

And in JavaScript as below:

Parse.onParsePushReceive = function (e) {
	alert(Parse.getData())
};

Callbacks to JavaScript
If JavaScript developer attaches a call back to fb like below :

fb.onCreated = function (e) {
	alert(e.message);
};

Then the plugin developer can trigger this call back using smfJsObject if it is in the class. One of SMFJSObject class methods mentioned before is getProperty, developer can get the SMFJSObject reference that is attached to smfJsObject by name like smfJsObject.getProperty(“onCreated”) then developer can use callAsFunction passing the object that has the callback attached to it which is smfJsObject in this case and the parameters as another SMFJSObject or null in case of no parameters like :

SMFJSObject onCreatedSmfJsObjRef = smfJsObject.getProperty("onCreated");
onCreatedSmfJsObjRef.callAsFunction(smfJsObject, arguments);

Full example :

protected void onFBCreated(String message) {
	try {
		JSONObject js = new JSONObject();
		js.put("message", message);

		SMFJSObject[]arguments = new SMFJSObject[1];
		arguments[0] = new SMFJSObject(js);

		SMFJSObject onCreatedSmfJsObjRef = smfJsObject.getProperty("onCreated");
		onCreatedSmfJsObjRef.callAsFunction(smfJsObject, arguments);

	} catch (Exception e) {
		e.printStackTrace();
	}
}

Callback to JavaScript as Parameters
In case of parameter callbacks, plugin class will receive it as SMFJSObject and similar to callbacks; callAsFunction can be called using the SMFJSObject callback with null; if not attached to any object and SMFJSObject containing the parameters or null if there is no parameters.

Example :

Public void methodName(SMFJSObject callback) { 
	//code
	callback.callAsFunction(null, arguments);
}

Full example :

public static void openSession(Vector permissions, final SMFJSObject onSuccess, final SMFJSObject onError) {
	//code
}
public static void fireEvent(SMFJSObject callback, String args) {
	try {
		SMFJSObject[]arguments = null;
		if (args != null) {
			JSONObject js = new JSONObject(args);
			arguments = new SMFJSObject[1]; //the call back has one parameter, e
			arguments[0] = new SMFJSObject(js);
		}
		callback.callAsFunction(null, arguments);
	} catch (Exception e) {
		e.printStackTrace();
	}
}

On the JavaScript side it looks like below :

fb.openSession(
	["publish_actions", "user_friends", "user_about_me", "user_status"],
	function (e) {
		alert("login successful" + e.data);
	}, //onSuccess callback parameter
	function (e) {
		alert("error: " + e.message);
	} //onError callback parameter
);

Evaluating JavaScript Codes
Using SMFJS evaluate class plugin developer can run JavaScript code within his/her class and get SMFJSObject as a reference of the resulting JavaScript object.

Example :

SMFJSObject jsFbObject = SMFJS.evaluate("new Facebook(\"Landroid/app/Activity;\");");

This can be another way to get the object ref without having smfJsObject in the plugin class.

public static SMFJSObject createFB() {
	SMFJSObject myObj = null;
	try {
		myObj = SMFJS.evaluate("new Facebook(\"Landroid/app/Activity;\");");
	} catch (Exception e) {
		e.printStackTrace();
	}
	return myObj;
}

In this case JavaScript developer can create the object like this :

var fb = Facebook.createFB();

Layout Params for UI Plugin

For UI plugin that needs to set it’s layout params, we can use ViewGroup.LayoutParams for this.

Smartface is using AbsoluteLayout for pages and containers, and Framlayout for scrollviews.

Ignored Files

  • Files conflicting by name will be ignored except in res/values folder color.xml, attr.xml, strings.xml, styles.xml, dimens.xml and resources.xml will be merged so always choose a unique name for your files and classes.
  • The main activity inside the output apk will be ignored in all cases of a UI plugin or a non-UI plugin.
  • Any file under drawable/ with these names will be ignored :
    action_item_selected.png, arrow_down.png, arrow_up.png, buttom_bar.png, bottom_bar_highlight.9.png, close.png, clear.png, delete.png, defaultsplash.png, down.png, drawer_shadow.9.png, ic_action_search.png, icon.png, popup.png, pin.png, up.png
  • Any file under Layout/ with these names will be ignored :
    action_item_horizontal.xml, action_item_vertical.xml, activity_main.xml, bubble.xml, main.xml, popup_horizontal.xml, popup_vertical.xml, pull_to_refresh_header_horizontal.xml, pull_to_refresh_header_vertical.xml, vertical_seperator.xml
  • Any file under menu/ with these names will be ignored :
    activity_main.xml, editmenu.xml
  • Any file under xml/ with this name will be ignored :
    searchable.xml

Android Plugin Development Example

Parse Plugin

  • Complete your native application.
  • Make sure that the application class extends MultiDexApplication as its required for Smartface in case of implementing the class and not using the default.
  • Make a plugin class that Smartface developer can call and use.
  • Remember that any public method, field or their super classes can be accessed by the JavaScript developer.
package smf.parse.controller;

import android.app.Activity;
import io.smartface.plugin.SMFJSObject;
import smf.parse.Application;

public class Parse {
	public static SMFJSObject smfJSClass = null;
	//called from application class if parse registration fail to trigger javascript call back
	public static void onParseRegistrationFail() {
		try {
			SMFJSObject onRegistrationFail = smfJSObject.getProperty("onRegistrationFail");
			onRegistrationFail.callAsFunction(smfJSClass, null);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	public static boolean openedByParseNotification() {
		return !notificationReceiveFired;
	}
	//triggered from Receiver
	public static void onParsePushReceived() {
		try {
			SMFJSObject onParsePushReceive = smfJSOClass.getProperty("onParsePushReceive");
			onParsePushReceive.callAsFunction(smfJSClass, null);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	//javascript developer call this to get parse’s token
	public static String getToken(Activity currentActivity) {
		return ((Application)currentActivity.getApplication()).getParseToken();
	}
}
  • From your extended ParsePushBroadcastReceiver you can trigger parse events as:
protected void onPushReceive(Context context, Intent intent) {
	super.onPushReceive(context, intent);
	String data = intent.getExtras().get("com.parse.Data").toString();

	if (data != null && !data.equals("{}") && !data.isEmpty() && isForeground(context)) {
		Parse.setData(data);
		Parse.notificationReceiveFired = true;
		Parse.onParsePushReceived();
	}
}
public void onPushOpen(Context context, Intent intent) {
	Intent mIntent = new Intent(context, MainActivity.class);
	mIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
	mIntent.putExtras(intent.getExtras());
	String data = intent.getExtras().get("com.parse.Data").toString();
	Parse.setData(data);
	mIntent.putExtra("notificationData", data);
	Parse.notificationReceiveFired = false;
	context.startActivity(mIntent);
}

Filling the Template

Filling the template has some common parts and distinct parts for iOS and Android, which are described below.

Common Part of Template
Like an npm package, plugins use a “package.json” file to identify its contents. It is recommended to use following command to create a new “package.json” file after Changing Directory (CD) into workspace:

npm init


This will use command line interface to create a new “package.json” file or modify the existing one. Based on the approach, you may need to modify the “package.json” file later manually.

In the file, add following fields to the JSON :

  • os {string} – Identifies if it is an iOS or an Android plugin. Possible values are : “Android” or “iOS”.
  • cpu {array.string} – Identifies architecture types supported by the plugin. Possible values are: “x86”, “ARM”, “ARM64”. For Android, it can be “x86” or “ARM” or both. For iOS, it is required to have “ARM” and “ARM64” together.

Sample Android Package.json file:

{
	"name" : "helloPlugin",
	"version" : "1.0.0",
	"description" : "Smartface Hello Plugin",
	"keywords" : [
		"Smartface",
		"Hello",
		"Plugin"
	],
	"author" : "Smartface Inc.",
	"license" : "ISC",
	"OS" : "Android",
	"cpu" : ["arm", "x86"]
}

Custom plugin post integrator
During the publishing process, the plugin is integrated with published project. To implement custom actions on the post processor, use the “main” property of the “package.json” file. This “main” property points to a JavaScript file. If the file is present during post integration process, it is used. If there is no specific need to use the post integrator, please delete “main” field from “package.json”.

Refer to the Developing Custom Plugin Post Integrator section for more information.

Next steps

  • Create PluginConfig.JSON and add you call full path to it. Example:
    {“classes”:[“smf.parse.controller.Parse”]}
  • Use template project to integrate your plugin project
  • Add the developed plugin
  • Get the signed apk for it
  • Generate Plugin.zip file by using the CLI
  • Put the plugin zip file in the plugin folder present in your Smartface Workspace path. ( ex. plugins/Android )
  • Activate your plugin in project.json by code as "true" :

    Activating Plugin
    					"Plugin name": {
    						"url": "if you will get the plugin data from url please write here",
    						"path": "plugins/Android/pluginname.zip",
    						"active": true
  • Now the JavaScript developer can call the plugin classes.
Parse.onRegistrationFail = function () {
	alert("parse Registration failed");
};
alert(Parse.getToken("Landroid/app/Activity;"));