Callbacks in Appcelerator Titanium modules

how-to
May 29, 201319 mins

I recently found myself implementing both an Android and iOS Appcelerator module for App47’s respective Agent libraries. Like PhoneGap plugins, Appcelerator modules are a way to bridge an Appcelerator app with native code running on a device; in this case, the native code happens to be App47’s Android and IOS Agents, which capture usage analytics and facilitate a few security features. Naturally, these Agent libraries are coded in Java and Objective-C.

In the end, what I wanted to implement was a JavaScript-ish callback associated with a native App47 Agent call. Alas, it took me a lot of digging to achieve this goal.

For example, for a timed event (which, as you’ve probably guessed, captures how long an event took), rather than the more traditional call which is inline:

Straightforward method invocation
<span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<code class='javascript'><span class='line'><span class="kd">var</span> <span class="nx">id</span> <span class="o">=</span> <span class="nx">agent</span><span class="p">.</span><span class="nx">startTimedEvent</span><span class="p">(</span><span class="s2">"openCrust 2.0.27"</span><span class="p">);</span>
</span><span class='line'><span class="nx">openCrust</span><span class="p">({});</span>
</span><span class='line'><span class="nx">agent</span><span class="p">.</span><span class="nx">endTimedEvent</span><span class="p">(</span><span class="nx">id</span><span class="p">);</span>
</span>

I wanted a more JavaScript friendly call that wraps the timed code like so:

Callback invocation
<span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<code class='javascript'><span class='line'><span class="nx">agent</span><span class="p">.</span><span class="nx">timedEvent</span><span class="p">(</span><span class="s2">"openCrust 2.0.27"</span><span class="p">,</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'>  <span class="nx">openCrust</span><span class="p">({});</span>
</span><span class='line'><span class="p">});</span>
</span>

This has the benefit of wrapping a desired event – there is no explicit need for anyone to code the ending – it is automatically done via the timedEvent call after invoking the passed in function.

The Titanium module documentation is a bit hard to find (that is, finding up-to-date valid documentation is challenging); your best bet to see how to do something interesting is to look at the various code repositories on Github followed by studying the API docs (i.e. JavaDocs and .h/.m files for iOS).

It turns out, invoking a JavaScript callback in either Android or iOS is fairly straightforward. In the case of Android, you need to use the KrollFunction type like so:

Wrapped Timed Event
<span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<code class='java'><span class='line'><span class="nd">@Kroll.method</span>
</span><span class='line'><span class="kd">public</span> <span class="kt">void</span> <span class="nf">timedEvent</span><span class="o">(</span><span class="n">String</span> <span class="n">name</span><span class="o">,</span> <span class="n">KrollFunction</span> <span class="n">callback</span><span class="o">)</span> <span class="o">{</span>
</span><span class='line'>  <span class="n">String</span> <span class="n">id</span> <span class="o">=</span> <span class="n">EmbeddedAgent</span><span class="o">.</span><span class="na">startTimedEvent</span><span class="o">(</span><span class="n">name</span><span class="o">);</span>
</span><span class='line'>  <span class="n">callback</span><span class="o">.</span><span class="na">call</span><span class="o">(</span><span class="n">getKrollObject</span><span class="o">(),</span> <span class="k">new</span> <span class="n">HashMap</span><span class="o"><</span><span class="n">String</span><span class="o">,</span> <span class="n">String</span><span class="o">>());</span>
</span><span class='line'>  <span class="n">EmbeddedAgent</span><span class="o">.</span><span class="na">endTimedEvent</span><span class="o">(</span><span class="n">id</span><span class="o">);</span>
</span><span class='line'><span class="o">}</span>
</span>

As you can see in the above code, I’m not doing anything special like passing in any arguments to the KrollFunction instance. If you want to do that, say in the case of passing in some special value that the corresponding callback will use, then you can either pass in a Map or an Object[].

For example, you can implement this style of callback where a custom value is passed in for a timed event like so:

Callback with Java
<span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<code class='java'><span class='line'><span class="nd">@Kroll.method</span>
</span><span class='line'><span class="kd">public</span> <span class="kt">void</span> <span class="nf">startTimedEvent</span><span class="o">(</span><span class="n">String</span> <span class="n">name</span><span class="o">,</span> <span class="n">KrollFunction</span> <span class="n">callback</span><span class="o">)</span> <span class="o">{</span>
</span><span class='line'>  <span class="n">String</span> <span class="n">id</span> <span class="o">=</span> <span class="n">EmbeddedAgent</span><span class="o">.</span><span class="na">startTimedEvent</span><span class="o">(</span><span class="n">name</span><span class="o">);</span>
</span><span class='line'>  <span class="n">HashMap</span><span class="o"><</span><span class="n">String</span><span class="o">,</span> <span class="n">String</span><span class="o">></span> <span class="n">map</span> <span class="o">=</span> <span class="k">new</span> <span class="n">HashMap</span><span class="o"><</span><span class="n">String</span><span class="o">,</span> <span class="n">String</span><span class="o">>();</span>
</span><span class='line'>  <span class="n">map</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"id"</span><span class="o">,</span> <span class="n">id</span><span class="o">);</span>
</span><span class='line'>  <span class="n">callback</span><span class="o">.</span><span class="na">call</span><span class="o">(</span><span class="n">getKrollObject</span><span class="o">(),</span> <span class="n">map</span><span class="o">);</span>
</span><span class='line'><span class="o">}</span>
</span>

This results in a JavaScript call like so:

Using a callback but not wrapping the event
<span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<code class='javascript'><span class='line'><span class="nx">agent</span><span class="p">.</span><span class="nx">startTimedEvent</span><span class="p">(</span><span class="s2">"openCrust 2.0.27"</span><span class="p">,</span> <span class="kd">function</span><span class="p">(</span><span class="nx">result</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>  <span class="kd">var</span> <span class="nx">id</span> <span class="o">=</span> <span class="nx">result</span><span class="p">[</span><span class="s1">'id'</span><span class="p">];</span>
</span><span class='line'>  <span class="nx">openCrust</span><span class="p">({});</span>
</span><span class='line'>  <span class="nx">agent</span><span class="p">.</span><span class="nx">endTimedEvent</span><span class="p">(</span><span class="nx">id</span><span class="p">);</span>
</span><span class='line'><span class="p">});</span>
</span>

In iOS land, invoking a callback is a bit different, but certainly as easy. The same timedEvent JavaScript method that takes a callback to be wrapped by the timed event can be implemented as follows:

Wrapped event by callback
<span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<code class='objective-c'><span class='line'><span class="k">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">timedEvent:</span><span class="p">(</span><span class="kt">id</span><span class="p">)</span><span class="nv">args</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>  <span class="n">NSString</span><span class="o">*</span> <span class="n">eventName</span> <span class="o">=</span> <span class="p">[</span><span class="n">args</span> <span class="nl">objectAtIndex:</span><span class="mi">0</span><span class="p">];</span>
</span><span class='line'>  <span class="n">NSString</span><span class="o">*</span> <span class="n">eventID</span> <span class="o">=</span> <span class="p">[</span><span class="n">EmbeddedAgent</span> <span class="nl">startTimedEvent:</span><span class="n">eventName</span><span class="p">];</span>
</span><span class='line'>
</span><span class='line'>  <span class="n">KrollCallback</span><span class="o">*</span> <span class="n">callback</span> <span class="o">=</span> <span class="p">[</span><span class="n">args</span> <span class="nl">objectAtIndex:</span><span class="mi">1</span><span class="p">];</span>
</span><span class='line'>  <span class="k">if</span><span class="p">(</span><span class="n">callback</span><span class="p">){</span>
</span><span class='line'>    <span class="p">[</span><span class="n">callback</span> <span class="nl">call:</span><span class="nb">nil</span> <span class="nl">thisObject:</span><span class="nb">nil</span><span class="p">];</span>
</span><span class='line'>  <span class="p">}</span>
</span><span class='line'>
</span><span class='line'>  <span class="p">[</span><span class="n">EmbeddedAgent</span> <span class="nl">endTimedEvent:</span><span class="n">eventID</span><span class="p">];</span>
</span><span class='line'><span class="p">}</span>
</span>

In this case, I’m using the KrollCallback type, which appears to be analogous to Appcelerator’s Android KrollFunction.

If you need to pass values to a corresponding JavaScript function, they need to be in NSArray form, thus, you can do something like this to pass in parameters to the underlying JavaScript function:

With callback parameter
<span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<code class='objective-c'><span class='line'><span class="k">-</span> <span class="p">(</span><span class="kt">void</span><span class="p">)</span><span class="nf">startTimedEvent:</span><span class="p">(</span><span class="kt">id</span><span class="p">)</span><span class="nv">args</span>
</span><span class='line'><span class="p">{</span>
</span><span class='line'>  <span class="n">NSString</span><span class="o">*</span> <span class="n">eventName</span> <span class="o">=</span> <span class="p">[</span><span class="n">args</span> <span class="nl">objectAtIndex:</span><span class="mi">0</span><span class="p">];</span>
</span><span class='line'>  <span class="n">NSString</span><span class="o">*</span> <span class="n">eventID</span> <span class="o">=</span> <span class="p">[</span><span class="n">EmbeddedAgent</span> <span class="nl">startTimedEvent:</span><span class="n">eventName</span><span class="p">];</span>
</span><span class='line'>
</span><span class='line'>  <span class="n">KrollCallback</span><span class="o">*</span> <span class="n">callback</span> <span class="o">=</span> <span class="p">[</span><span class="n">args</span> <span class="nl">objectAtIndex:</span><span class="mi">1</span><span class="p">];</span>
</span><span class='line'>  <span class="k">if</span><span class="p">(</span><span class="n">callback</span><span class="p">){</span>
</span><span class='line'>    <span class="n">NSDictionary</span><span class="o">*</span> <span class="n">dict</span> <span class="o">=</span> <span class="p">[[[</span><span class="n">NSDictionary</span> <span class="n">alloc</span><span class="p">]</span> <span class="nl">initWithObjectsAndKeys:</span><span class="s">@"id"</span><span class="p">,</span> <span class="n">eventID</span><span class="p">,</span> <span class="nb">nil</span><span class="p">]</span> <span class="n">autorelease</span><span class="p">];</span>
</span><span class='line'>    <span class="n">NSArray</span><span class="o">*</span> <span class="n">array</span> <span class="o">=</span> <span class="p">[</span><span class="n">NSArray</span> <span class="nl">arrayWithObjects:</span> <span class="n">dict</span><span class="p">,</span> <span class="nb">nil</span><span class="p">];</span>
</span><span class='line'>    <span class="p">[</span><span class="n">callback</span> <span class="nl">call:</span><span class="n">array</span> <span class="nl">thisObject:</span><span class="nb">nil</span><span class="p">];</span>
</span><span class='line'>  <span class="p">}</span>
</span><span class='line'><span class="p">}</span>
</span>

As you can see in the above code, a callback instance takes as a parameter an NSArray, thus, I have to covert my NSDictionary into an array via Objective-C’s handy arrayWithObjects function.

The default module examples provided by Appcelerator naturally work, but alas, the non-callback style of invocation was less than appealing, especially if you are going to be coding an Appcelerator app in JavaScript. Nevertheless, you can do it easily enough provided you are willing to dig through myriad repositories…or you could save yourself the headache and read this blog post.

andrew_glover

When Andrew Glover isn't listening to “Funkytown” or “Le Freak” he enjoys speaking on the No Fluff Just Stuff Tour. He also writes articles for multiple online publications including IBM's developerWorks and O'Reilly’s ONJava and ONLamp portals. Andrew is also the co-author of Java Testing Patterns, which was published by Wiley in September 2004; Addison-Wesley’s Continuous Integration; and Manning’s Groovy in Action.

More from this author