#Ti.Airlino
This tutorial shows, how you can implement a REST interface in titanium and later how you can user a ready to use library (class) of Android to bild i.e. a Bonjour browser.
First we build a native module (without Titanium stuff). Therefore we can use this package later in noTitanium projects.
##AirlinoAdapter.java
Airlino is a hardware device for receiving streamed audio data and for playing audio URLs. Maybe some users has more then one of this black boxes and therefore we need instances. The protocol is for all function http/POST/JSON. The pdf with detailled protocol description you can find here.
There are a lot of http clients for android/java. In this solution we use AsyncHttpClient. From this website we download the newest jars and copy to lib folder:
Now we open with right click the project folder, select properties and add this both jars into classpath.
The class contains three components: constructor, a collection of methods and the client with callback impementation.
###Variables
private String ENDPOINT;
final int DEFAULTPORT = 4949;
private Context ctx;
private onResultHandler resultHandler;
private HashMap<String, String> map =new HashMap<String, String>();
###Constructor
public AirlinoAdapter(Context ctx, String host) {
this.AirlinoAdapter(ctx, host, DEFAULTPORT);
}
public AirlinoAdapter(Context ctx, String host, int port) {
this.ctx = ctx;
this.ENDPOINT = "http://" + host + ":" + port + "/api/v15/";;
}
We have only 2 instance variables: the context and the host/port. If we call the constructor without port, the default port will use.
###Methods We have a lot of methods. As example the playRadio-method:
public void playRadio(String url, String name, onResultHandler resultHandler)
throws UnsupportedEncodingException, JSONException {
map.clear();
map.put("url", url);
map.put("action", "play");
map.put("name", name);
doRequest("radio.action", map, resultHandler);
}
In case of playRadio we have three parameters: the url and name of station and the "callback". In a HashMap (similar to javascript object) we collect some parameters to realise a clean call interface.
In the end we call a generic doRequest with action, collection of paramters in map and a reference to callback.
###doRequest
private void doRequest(String action, HashMap<String, String> map, onResultHandler resultHandler) throws JSONException, UnsupportedEncodingException {
AsyncHttpClient client = new AsyncHttpClient();
JSONObject jsonParams = new JSONObject(map);
StringEntity entity = new StringEntity(jsonParams.toString());
client.post(ctx, ENDPOINT + action, entity, "application/json",
new AirlinoResponseHandler());
}
First we create a new client, convert the map to JSONObject, convert to String and in the and we call post. The async result goes to AirlinoResponseHandler.
###responseHandler
private final class AirlinoResponseHandler extends JsonHttpResponseHandler {
@Override
public void onSuccess(int statusCode, Header[] headers, JSONObject response) {
if (resultHandler != null)
resultHandler.onResult(response);
}
@Override
public void onFailure(int statusCode, Header[] headers,
String responseString, Throwable throwable) {
}
}
###Interface Inside the class we have an interface with one method:
public interface onResultHandler {
public void onResult(JSONObject result);
}
This methof must be implemented by caller of this class.
##AirlinoConnectionProxy The Bonjour browser looks to all available devices and returns a list of endpoints (with names). If I want to send the music to a device I call from javascript layer:
var device = AirlinoModule.createiArlinoConnection(endpoint);
Now I have a new instance and can send command like
device.stopRadio();
If the module only exports functionalities from system, the we don't need proxies. A singeton module is enough. Examples are Ti.Appsflyer to track an app or Ti.Ringtonmanager which saves a new ringtone. In all other cases we need a proxy. A proxy is the javascript representation of a java class (object). MODULEProxy.java is corresponding to MODULE.create().
Every proxy contains three parts:
- proxy vars
- method to import javascript vars into proxy vars
- collection of (in JS visible) methodes.
In the head we inport our AirlinoAdapter from the other package:
import de.appwerft.airlino.AirlinoAdapter;
###Proxy variables
private static final String LCAT = "Airlino 👑👑";
private String host;
final int DEFAULTPORT = 4949;
private int port = DEFAULTPORT;
Context ctx;
AirlinoAdapter adapter;
###Import of proxy variables This happens in handleCreationDict. Only one instance variable.
@Override
public void handleCreationDict(KrollDict options) {
super.handleCreationDict(options);
if (options.containsKeyAndNotNull("host")) {
endpoint = options.getString("host");
}
if (options.containsKeyAndNotNull("port")) {
endpoint += ":" options.getString("port");
}
}
###Methods This methods are visible in JS and methods of instance, like
var Module = require("ti.airlino");
var device = Module.createAirlinoDevice({
host : "192.168.0.34",
port : 8989
});
device.playRadio({
url : URL-OF-STATION,
name : NAME-OF-STATION,
onResult : function(e) {
console.log(e);
}
});
OK, we have a lot of methods. All methods are wrapper to native methodes from our AirlinoAdapter class. For example:
@Kroll.method
public void playRadio(KrollDict options)
throws UnsupportedEncodingException, JSONException {
String url = "http://";
String name = "";
adapter.playRadio(url, name, new resultHandler(getCallback(options)));
}
Or
@Kroll.method
public void stopRadio(KrollDict options)
throws UnsupportedEncodingException, JSONException {
adapter.stopRadio(new resultHandler(getCallback(options)));
}
For stopping we only need a callback to receive the play end event.
This is the helper method getCallback(options):
private KrollFunction getCallback(KrollDict options) {
if (options.containsKeyAndNotNull("onResult")) {
Object cb;
cb = options.get("onResult");
if (cb instanceof KrollFunction) {
return (KrollFunction) cb;
}
}
return null;
}
If on JS layer a property onResult is part of options object this will raw read by get. After successful testing if this untyped stuff is really a JS-function the object will casted to KrollObject and will returned.
###Callback
Every method needs an own callback. It cannot be callback for the AirlinoAdapter class. Therefore every call of native method has as last property a new resultHandler(cb) instance.
Here is the implementation as subclass (it would be possible tp move in an own file):
```java
private final class resultHandler implements AirlinoAdapter.onResultHandler {
private KrollFunction resultCallback;
private resultHandler(KrollFunction cb) {
this.resultCallback = cb;
}
@Override
public void onResult(JSONObject result) {
if (resultCallback != null) {
KrollDict dict;
try {
dict = new KrollDict(result);
resultCallback.call(getKrollObject(), dict);
} catch (JSONException e) {
e.printStackTrace();
}
}
}
}
In contructor the reference to JS-callback will saved as instance variable. Second part overrides the onResult event. KrollDict has a constructor that imports JSONObject. Therefore we can simple make a new instance of KrollDict and can send the result of http request directly to JS layer.
##Detecting of devices (Bonjour)
Bonjour is invited by Apple for detecting of printer etc. in locale network. Later microsoft and android use the same protocol. The android frame has since version 16 a NSD class for this purpose.