Post image for Overcoming TIMOB-13097 in Titanium Android modules

Overcoming TIMOB-13097 in Titanium Android modules

by Bill Dawson on 29 March 2013

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; and
  • ExampleProxy.java, a proxy class which would ordinarily then be created in Javascript (app code) using code such as mymod.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 my ExampleProxy. In the function, I call the original, Titanium-given createExample 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 original createExample method (see next bullet). I’ve made the init function return the module instance (this) to allow for method chaining, thereby making it easy to require and init with one statement:
var mod = require("com.billdawson.timodules.mixed").init();
  • Because I really really want people to only instantiate ExampleProxy using my special exampleFactory function, I’ve gone so far as to clobber the default, Titanium-given createExample function, replacing it with my own. Mine will throw an exception and remind the app developer to use exampleFactory 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 an Example 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 an Example. This exception occurs because, in my module’s init function, I clobbered the original createExample 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.

Previous post:

Next post: