You may want to add custom sensors or features to your application using our SDK. To do so you will have to create a custom Sting.


Define your Sting API

The Sting API, also named Dart is generated from a json description file. This enables you to get every version of a same API in various languages and a documentation file compatible with the Dashboard editor.

{
    "name":"myCustomSting",
    "type":"dart",
    "description":"Show an example of Sting creation",
    /**
     * Seeds are the values returned by the Sting,
     * You can use the following pre-defined types: 
     * string, integer, long, float, double
     */
    "seeds":[
        {
            "name": "someData",
            "description": "Value handled by the Sting",
            "type": "string"
        },
        {
            "name": "anotherData",
            "description": "Some number",
            "type": "long"
        }
    ],
    /**
     * Methods are some freely defined behavior on the given Sting
     *
     * If your method has to register some asynchronous actions you can use a
     * 'io.apisense.dart.future.JSPromise' object.
     */
    "methods":[
        {
            "name":"someOneShotMethod",
            "description":"a specific behavior for the Sting",
            "parameters":[
                {
                    "name":"oneParameter",
                    "type":"string",
                    "description": "Some parameter for the method"
                }
        ],
            "type":"io.apisense.dart.future.JSPromise",
            "returned": "Description of the returned element"
        }
    ],
    /**
     * Sprouts are the event definitions,
     * They will define a new method asking a callback to the user,
     * and waiting to be triggered.
     *
     * The event must be composed by two strings delimited by a semicolon.
     * The first string represents the event name, 
     * the second one represents the event status.
     */
    "sprouts":[
        {
            "event":"someSensor:status",
            "description":"Create an event"
        },
        {
            "event":"aRequest:completed",
            "description":"Create an event taking a single argument as configuration",
            "privateFilters": false, // Default to false, turn to true if you want to manage your filters from the Sting
            "filters":[
                {
                    "label":"filter",
                    "description":"a value changing the event configuration",
                    "type":"string"
                }
            ]
        },
        {
            "event":"someSensor:otherStatus",
            "description":"Create an event taking  a json as configuration",
            "filters":[
                {
                    "label":"aString",
                    "description":"A filter",
                    "type":"string"
                },
                {
                    "label":"aNumber",
                    "description":"another filter",
                    "type":"long"
                }
            ]
        }
    ]
}

Generate your API

One shot

You will have to use the dart-generator tarball or zip file in order to create your Dart from the description file.

Decompress the file, then start the binary as follow:

$ ./dart-generator-1.1.0/bin/dart-generator file.dart [other.dart]

This will generate every possible outputs by type and dart. You should find a generated folder containing:

  • java: contains java sources under the io.apisense.dart.$dartName package.
  • iOS: contains iOS sources.
  • html: contains markdown documentation about the _Dart_s.

If you want to generate the API documentation for the Dashboard editor, you will have to download java-api-generator tarball or zip file.

Decompress the file, then start the binary as follow:

$ ./java-api-generator-1.1.0/bin/java-api-generator -n libraryName -o output.json ./generated/java/io/apisense/dart/*/*Dart.java

You can then import the file output.json into your Dashboard settings.

Automation for Android

If you want your Dart to always be up to date, you can integrate our plugin in your project.

To do so, make sure you have the following lines in your build.gradle:

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'io.apisense:dart-gradle-plugin:1.4.3'
    }
}
apply plugin: 'dart-android'

dart {
    darts = "${projectDir}/src/main/resources/darts" // Location of your input dart files
    output = "${buildDir}/generated/source/dart" // Location of the output
    apiName = 'myLib' // Name of your custom library (used on api documentation)
    javaBasePackage = 'io.apisense.dart' // base package for the generated classes
}

You should find those folders:

  • $buildType: contains java sources under the io.apisense.dart.$dartName package.
  • resources: contains generated documentation resources:
    • api: contains json file containing API documentation to import in the Dashboard settings.
    • html: contains html documentation about the Dart.

Please note that if you want to use JSPromise in your Sting, you will need the dependency:

dependencies {
    implementation 'io.apisense:java-darts:1.4.3'
}

Implement your features

You will have to create a class extending the dart skeleton.

public class MySting extends MyCustomStingDartSkel {
    private MyCustomStingData currentData = new MyCustomStingData(seeds(), "Hello world!", 42L);
    private final APISENSEPromiseFactory<MyCustomStingData> promiseFactory;

    // Collected by the SDK to provide sensor list
    public static final Sensor SENSOR_DESCRIPTION = new Sensor(
        "MySting common name",
        NAME,
        "Describe your Sting main behavior",
        R.drawable.sting_icon
    );

    @Inject
    protected MySting(EventBus bus, APISENSEPromiseFactory promiseFactory) {
        super(bus, EnumSet.allOf(MyCustomStingSeed.class));
        this.promiseFactory = promiseFactory;
    }

    @Override
    public String someData() {
        return currentData.someData;
    }

    @Override
    public Long anotherData() {
        return currentData.anotherData;
    }

    @Override
    public JSPromise someOneShotMethod(String oneParameter) {
        APISENSEPromise promise = promiseFactory.get();
        /* Promise usage:
         * On operation update: promise.notify(currentData); // Calls JSPromise.progress(callback)
         * On operation success: promise.resolve(currentData); // Calls JSPromise.onSuccess(callback)
         * On operation failure: promise.reject(currentData); // Calls JSPromise.onFailure(callback)
         *
         * Json users can also specify a JSPromise.then(callback) to be executed on both success and failure
         * on top of the specific treatments.
         */
        promise.resolve(currentData)
        return promise;
    }
   
    @Override
    protected Tokens.TokensListener<String> initARequestCompletedListener() {
        return new Tokens.TokensListener<String>() {
            @Override
            public void init() {
                // Create everything needed to use this event.
            }

            @Override
            public String computeTopic(final EventFilter<String> eventFilter) {
                // Retrieve your filter value using EventFilter.SINGLE_VALUE, since it's your only value
                final String filter = (String) eventFilter.get(EventFilter.SINGLE_VALUE);
                new AsyncTask<String, Void, MyCustomStingData>() {
                    @Override
                    protected MyCustomStingData doInBackground(String... aString) {
                        // Do something with the filter string (e.g. a network request)
                        return new MyCustomStingData(seeds(), "AnotherValue", 12L);
                    }

                    @Override
                    protected void onPostExecute(MyCustomStingData newData) {
                        super.onPostExecute(newData);
                        currentData = newData;
                        // Publish the results when needed to execute user callback
                        publish(MyCustomStingEvent.AREQUEST_COMPLETED, currentData, eventFilter);
                    }
                }.execute(filter);
                return MyCustomStingEvent.AREQUEST_COMPLETED.toString();
            }

            @Override
            public void discardFilter(EventFilter<String> eventFilter) {
                // Remove any callback registration using current filter
            }

            @Override
            public void release() {
                // Remove every callback registration
                // And release every related objects
            }
        };
    }

    @Override
    protected Tokens.TokensListener<SomeSensorStatusFilter> initSomeSensorStatusListener() {
        return new Tokens.TokensListener<SomeSensorStatusFilter>() {
            @Override
            public void init() {
                // Create everything needed to use this event.
            }

            @Override
            public String computeTopic(EventFilter<SomeSensorStatusFilter> eventFilter) {
                // The generator will provide Enumerates for multi-values filters
                final String aString = (String) eventFilter.get(SomeSensorStatusFilter.ASTRING);
                final long aNumber = (Long) eventFilter.get(SomeSensorStatusFilter.ANUMBER);
                // Do something with the filter and call publish when done
                publish(MyCustomStingEvent.SOMESENSOR_STATUS, currentData, eventFilter);
                return MyCustomStingEvent.SOMESENSOR_STATUS.toString();
            }

            @Override
            public void discardFilter(EventFilter<SomeSensorStatusFilter> eventFilter) {
                // Remove any callback registration using current filter
            }

            @Override
            public void release() {
                // Remove every callback registration
                // And release every related objects
            }
        };
    }
}

Integrate it to the SDK

Create a class that extends InjectedStingPackage and initialize all your Stings in the method getInstances. This method will give a StingComponent as argument, containing all the available injected values.

import java.util.Arrays;
import java.util.List;

import io.apisense.sdk.core.sting.InjectedStingPackage;
import io.apisense.sdk.core.sting.StingComponent;
import io.apisense.sting.lib.Sting;

public class EnvironmentStingModule extends InjectedStingPackage {
    @Override
    public List<Sting> getInstances(StingComponent component) {
        return Arrays.<Sting>asList(
            new MySting(component.bus(), component.promiseFactory()),
            new OtherStingWithContext(component.bus(), component.context())
        );
    }
}

You will then only have to provide your module to the SDK initialization.

Until further updates, to create a custom sting you’ll just have to create a new file, you can call it whatever you want.

Example, CustomSting.swift

import Apisense
import JavaScriptCore
import CocoaLumberjack

@objc public protocol CustomStingExports: JSExport {
    func info(_ param: String)
}

class CustomSting: Sting, CustomStingExports {
    public func info(_ message: String) {
        DDLogInfo(message)
    }
}

The protocol declare functions you want to expose in your javascript. In this example, we expose a function call info with one parameter called message which going to display this message (String) in the terminal.

Then we implement this function in CustomSting, note that you’ll have to extend Sting and implement your protocol.

And that’s it, once your CustomSting is pass to APISENSE during its initialization, you’ll be able to execute crop using your own methods.