Using the templates and TVML, I start my app with my own loading page, and then call a service to create the main page for the user.
If I initiate the call to the server inside didFinishLaunchingWithOptions
, I get the error ITML <Error>: undefined is not an object - undefined - line:undefined:undefined
.
From this I assume my asynchronous call to the server is finishing before the javascript App.onLaunch
function has completed, and I can only get it to work if I force a wait time before the server is called.
Here is the AppDelegate method:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
window = UIWindow(frame: UIScreen.mainScreen().bounds)
let appControllerContext = TVApplicationControllerContext()
// our "base" is local
if let jsBootUrl = NSBundle.mainBundle().URLForResource("application", withExtension: "js") {
appControllerContext.javaScriptApplicationURL = jsBootUrl
}
let jsBasePathURL = appControllerContext.javaScriptApplicationURL.URLByDeletingLastPathComponent
baseUrl = jsBasePathURL?.absoluteString
appControllerContext.launchOptions["BASEURL"] = jsBasePathURL?.absoluteString
appController = TVApplicationController(context: appControllerContext, window: window, delegate: self)
// initiate conversation with the server
myPageCreator = PageCreator()
myPageCreator?.delegate = self
myPageCreator?.startDataCall(baseUrl!)
return true
}
Here is the (somewhat boilerplate) javascript function:
App.onLaunch = function(options) {
var javascriptFiles = [
`${options.BASEURL}ResourceLoader.js`,
`${options.BASEURL}Presenter.js`
];
evaluateScripts(javascriptFiles, function(success) {
if (success) {
resourceLoader = new ResourceLoader(options.BASEURL);
var index = resourceLoader.loadResource(`${options.BASEURL}myLoadingPage.xml.js`,
function(resource) {
var doc = Presenter.makeDocument(resource);
doc.addEventListener("select", Presenter.load.bind(Presenter));
navigationDocument.pushDocument(doc);
});
} else {
/* handle error case here */
}
});
}
Now, if I change the call to the server in the didFinishLaunchingWithOptions
, and force it to wait, like this:
...
// race condition hack:
_ = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: "testing", userInfo: nil, repeats: false)
return true
}
// initiate conversation with the server
func testing() {
myPageCreator = PageCreator()
myPageCreator?.delegate = self
myPageCreator?.startDataCall(baseUrl!)
}
.. it will work. But I don't like that solution! What can I do to stop this race condition from happening?
You need a way for Javascript to communicate with Swift so that you know when App.onLaunch has finished running it's scripts.
Run this code in your didFinishLaunchingWithOptions method. It will allow you to call onLaunchDidFinishLoading() in Javascript and handle the callback in Swift.
appController.evaluateInJavaScriptContext({(evaluation: JSContext) -> Void in
let onLaunchDidFinishLoading : @convention(block) () -> Void = {
() -> Void in
//when onLaunchDidFinishLoading() is called in Javascript, the code written here will run.
self.testing()
}
evaluation.setObject(unsafeBitCast(onLaunchDidFinishLoading, AnyObject.self), forKeyedSubscript: "onLaunchDidFinishLoading")
}, completion: {(Bool) -> Void in
})
func testing() {
myPageCreator = PageCreator()
myPageCreator?.delegate = self
myPageCreator?.startDataCall(baseUrl!)
}
Inside App.onLaunch just add onLaunchDidFinishLoading() when the template is done being loaded.
App.onLaunch = function(options) {
var javascriptFiles = [
`${options.BASEURL}ResourceLoader.js`,
`${options.BASEURL}Presenter.js`
];
evaluateScripts(javascriptFiles, function(success) {
if (success) {
resourceLoader = new ResourceLoader(options.BASEURL);
var index = resourceLoader.loadResource(`${options.BASEURL}myLoadingPage.xml.js`,
function(resource) {
var doc = Presenter.makeDocument(resource);
doc.addEventListener("select", Presenter.load.bind(Presenter));
navigationDocument.pushDocument(doc);
//ADD THE FOLLOWING LINE
onLaunchDidFinishLoading();
});
} else {
/* handle error case here */
}
});
}