When developing a Titanium Android module, you may wish to include a mechanism
to instantiate one of your proxy classes by means other than the typical
module.createMyProxy({...})
method call. For example, you may wish to have
a singleton proxy instance of sorts. There is at least one example of this
in Titanium itself: the displayCaps
property of Titanium.Platform.
In Titanium’s Java sources, displayCaps
— a property of the PlatformModule —
is an instance of DisplayCapsProxy. App developers are not meant to get
the display capabilities by using code such as Platform.createDisplayCaps()
, but rather
to just use that displayCaps
property, which returns the same instance each
time you access it.
You may actually be able to use Platform.createDisplayCaps()
just fine; I haven’t tried it. But it’s not intended to be used
that way, and therefore Platform.displayCaps
returns what we might
consider a “singleton”, even if it’s really not.
Anyway, perhaps you might find yourself having a need to use a similar pattern in your own
custom Titanium Android module. That is, you may want to create
an instance of one of your proxies from inside your module code and
then pass it back to the caller on the Javascript side. The Javascript side
will then have an instance that was not obtained by calling
createXXXX()
. Sounds great, right?
Well, you have a problem: if you want to support event listeners in your proxy, you will eventually run into this bug:
I haven’t researched the cause of that bug — it might even be a bug introduced by yours truly back in my days at Appcelerator — and since no one is working on it yet, a workaround is in order.
The bug occurs only when using proxy instances that were not created by
the standard createXXXX()
method. So at the moment you have to use createXXXX()
if you want to avoid this bug. That’s a shame if indeed you want to do
some proxy creation inside your module’s Java code and pass it back.
Javascript to the rescue! Did you know that your custom Titanium Android module (and Titanium iOS module as well, for that matter), can be written in a mixture of “native” code (Java/Objective-C) and Javascript? Indeed it can, and we can use that to our advantage in attempts to overcome the TIMOB-13097 bug.
(Important: Consider this experimental. I just came up with the idea today and haven’t thought about possible drawbacks to this approach. So far my testing has not brought up any issues.)
To show this, I’ll use the default module code you get when you first
create a module project. I’ve created a module project named MixedModule with
id com.billdawson.timodules.mixed
. Two Java source files were generated for me:
Mixedmodulemodule.java
, the module itself; andExampleProxy.java
, a proxy class which would ordinarily then be created in Javascript (app code) using code such asmymod.createExample()
.
Mixedmodulemodule
contains a method named example
which just returns the string
“hello world”, and ExampleProxy
contains a method named getMessage
which
returns the string “Hello World from my module”. I mention those methods because
I’ll use them in the sample app code below to prove to myself that I haven’t
broken the normal module/proxy behavior after doing the workaround.
I want module users (app developers) to use a factory function of my own to get
instances of ExampleProxy
, rather than the typical createExample()
function.
Let’s say that special factory function is called exampleFactory
. Normally, I would then add
code similar to the following to Mixedmodulemodule.java:
@Kroll.method public ExampleProxy exampleFactory() { ExampleProxy proxy = new ExampleProxy(); // ... do stuff (or not) to get // ... proxy ready however you want // ... return proxy; }
But I know I’ll run into TIMOB-13097. I have to call createExample()
in Javascript
to get a proxy instance that avoids TIMOB-13097. So I turn my module into a mixed
Javascript/Java module by creating the file com.billdawson.timodules.mixed.js
under the assets
folder in my module project.
In that Javascript file, I’ll put the following:
exports = module.exports = (function() { var _createExample, initialized = false; function checkInit() { if (!initialized) { throw "Module not initialized. You must call .init()." } } function test() { return "Hello from inside Javascript module"; } function init() { // Save off the original, Titanium-provided createFactory // so you can use it (and thus avoid TIMOB-13097), // then disallow others from using it so you can have full // control over how Example objects are created (or perhaps // you always want to return a singleton, etc. etc.) // In this example, I'm clobbering the original // createExample(), i.e., I'm absolutely disallowing // anyone from using mymod.createExample(). But of course // this is optional. _createExample = this.createExample; // Clobber this.createExample = function() { throw "createExample() not available. Use exampleFactory() instead."; }; initialized = true; return this; } function exampleFactory() { checkInit(); // Calling the original createExample(), so as to // avoid TIMOB-13097. // In a real world module, my factory // method would probably be doing lots of other stuff. return _createExample(); } return { init: init, test: test, exampleFactory: exampleFactory }; })();
(If you didn’t already know that you can mix Javascript and Java in your modules, let me make something clear: the user of your module (an app developer) will be able to require(...)
the module as usual and then call both the Javascript functions you export and the Java methods marked by the @Kroll.method
(etc.) annotations.)
Note the following about my Javascript module file shown above:
- I created my special factory function (
exampleFactory
), which is the way I want people to instantiate myExampleProxy
. In the function, I call the original, Titanium-givencreateExample
function because that’s the way to avoid TIMOB-13097. - I required the module to be initialized first using the
init
function. This is so I can save and then clobber the originalcreateExample
method (see next bullet). I’ve made theinit
function return the module instance (this
) to allow for method chaining, thereby making it easy torequire
andinit
with one statement:
var mod = require("com.billdawson.timodules.mixed").init();
- Because I really really want people to only instantiate
ExampleProxy
using my specialexampleFactory
function, I’ve gone so far as to clobber the default, Titanium-givencreateExample
function, replacing it with my own. Mine will throw an exception and remind the app developer to useexampleFactory
instead. Of course, it’s optional to go that far in enforcing your factory function’s use. - I exported a
test
method which I can use in an app to further satisfy myself that calling into the Javascript part of the module works fine.
I then build the module and create a test project for it. The test project’s app.js is short and simple:
var mod = require("com.billdawson.timodules.mixed").init(), example; Ti.API.info("From module Javascript: " + mod.test()); Ti.API.info("From module Java: " + mod.example()); example = mod.exampleFactory(); example.addEventListener("dohicky", function() { Ti.API.info("Event was fired successfully"); }); Ti.API.info("From ExampleProxy Java (method): " + example.getMessage()); Ti.API.info("From ExampleProxy Java (property): " + example.message); try { // Should throw exception. mod.createExample(); } catch(e) { Ti.API.info("As expected, got exception trying to use createExample():"); Ti.API.error(e); } setTimeout(function() { example.fireEvent("dohicky", {}); }, 1000);
The test project’s app.js will prove to me that:
- I can call into the Javascript and Java parts of the module transparently and with expected results. (By “transparently” I mean that I, as an app developer using my module, see no distinction between calling a Javascript-exported function versus a Java “Kroll” method.)
- I can use the factory method
exampleFactory
to get anExample
instance. - I can add event listeners to my
Example
instance. I will need to prove that the TIMOB-13097 bug doesn’t happen when I back out of the app and then go back into it. - The event listeners work (I fire the event 1 second after the app loads.)
- I get an exception, as expected, if I try to use the classic
createExample()
method of instantiating anExample
. This exception occurs because, in my module’sinit
function, I clobbered the originalcreateExample
with my own, which throws the exception.
When I run the test app and check logcat output, I see all the TiAPI messages that I expect. And I can back out of the app and return to it without TIMOB-13097’s bug occurring.
Comments on this entry are closed.