Calling Javascript from native, a usability question.
I’ve been thinking about the mechanics of calling javascript from native and how to make it as nice as possible for native developers.
I was going to ask this to just the developers at Future Platforms who are working with Kirin at the moment, but thought it applicable to any interested developers: so I spun it out into a blog post.
In the spirit of “APIs as UIs for developers”, I’m treating this as a usability question.
What is happening now, for contrast:
In the UIViewController’s viewDidLoad method, a kirin helper is made,
binding a native object to the named javascript module. In this case, it’s
MyScreen, which corresponds to a javascript file MyScreen.js.
self.kirinHelper = [KIRIN bindScreen: self withModuleName: @"MyScreen"];
The KirinHelper method provides a number of methods, including calling
and cleaning up callbacks into javascript and calling methods on the
javascript module. It also provides access to a dropbox, and in some cases
the current UIViewController.
Once the kirinHelper is constructed, you can call methods on the
javascript module:
[self.kirinHelper jsMethod: @"fooMethod"];
which will call a method in the MyScreen.js module:
exports.fooMethod = function () {};Calling methods with arguments is a little more work. To call method:
exports.barMethod = function (myBoolean, myNumber, myString) {};For three args it boils down to:
[self.kirinHelper jsMethod: @"barMethod" withArgsList: @"true, 2, 'string'"];
There are helper methods, but they only make it safer, not more readable or writeable:
[self.kirinHelper jsMethod: @"barMethod" withArgsList:
[KirinArgs args:[KirinArgs bool: YES],
[KirinArgs integer:2],
[KirinArgs string: @"string"],
nil]];[KirinArgs args:] is a va_args method, concats the strings together
into the form above. Each of the other KirinArgs methods transforms an
objective-c type into a string representation usable by javascript. Also
supported are NSDictionarys and NSArrays.
There is also a method in KirinArgs called taintedString which URL
encodes the string before quoting it. This if for strings from the user
that may contain “ or ‘ characters.
What I’ve tried, but haven’t been able to do:
[self.kirinHelper jsMethod: @"barMethod" withArgsList: YES, 2, @"string"];
This will work when literals are supported, but not just yet: YES and 2 are
primitive types, which don’t work with va_args.
This would work for Java, because of auto boxing and varargs:
mKirinHelper.jsMethod("barMethod", true, 2, "string");Both would simplify things at the call site, though would provide no additional compile time safety. They don’t have any intrinsic support for tainted strings either.
What I’m proposing, and asking feedback for:
self.kirinHelper = [KIRIN bindScreen: self withModuleName: @"MyScreen" andProtocol: @protocol(MyScreenProxy)]; self.myScreen = (id) [self.kirinHelper proxy];
The self.myScreen is an instance of NSProxy which forwards any calls to
javascript. The MyScreenProxy is a protocol which declares in Objective-C
the javascript methods that will be called:
@protocol MyScreenProxy - (void) fooMethod; - (void) barMethod: (BOOL) myBoolean : (int) myNumber : (NSString*) myString; @end
Calling:
[self.myScreen fooMethod]; [self.myScreen barMethod: YES : 2 : @"string"];
Feasibly you could use named arguments too:
- (void) bazMethod: (NSString*) name withOptions: (NSDictionary*) dict;
but there would have to be a simple mapping between that and javascript:
[self.myScreen bazMethod: @"string" withOptions: options];
which would naïvely map to:
exports.bazMethodwithOptions = function (name, dict) {}; // note non camel caseA more involved mapping may solve this camel casing problem, but I expect most uses would avoid named arguments.
In Java (for Android) this would be and in all likelihood use the
java.lang.reflect.Proxy
object:
mKirinHelper = mKirin.bindScreen(this, "MyScreen", IMyScreen.class); mMyScreen = mKirinHelper.createProxy(); mMyScreen.fooMethod(); mMyScreen.barMethod(true, 2, "string");
Questions:
Most of these questions will be answerable by people who haven’t used Kirin in its current form (i.e. on the kirin-fragments branch) but who are familiar with the intentions of Kirin. I have also included Android versions that may help those who don’t speak Objective-C.
- does chore of maintaining of a protocol outweigh the benefit of type safe calls into Javascript?
- is the language of proxies and protocols clear in its intention?
- Is this the best way of calling from native to javascript?
- How do you call strings that are “tainted”, i.e. may contain ‘ or “ characters? Is there an automated (and nice way of doing it);
- Can we get rid of the helper method altogether, or combine the helper with proxy? Is it desirable? If so, how do you call callbacks? How do instantiate the combined object?
- Any additional thoughts?