Increasingly Functional.
by Joshua Miller | on Twitter | on the web | on github

ClojureScript on tvOS

October 26th 2015

Tagged: clojure ios

Since winning the lottery for a next-generation AppleTV development kit, I've been doing some experimenting with apps with both TVOS and Swift and with TVML and TVJS. TVML is an HTML-like markup language Apple provides that you can use to instantiate media browser-style components, and you can use TVJS, a Javascript implementation with some AppleTV-exclusive APIs, to manipulate it.

Better yet, you can use ClojureScript to generate your TVJS! Or at least if you shave a few yaks first. Here are the critical sticking points I had, and how to get over them.

First, the TVML document model. When a TVML-built app launches, its first order of business is to download a Javascript file that will execute in the TVML document context (see Apple's documentation for starting up a TVML app here). What you have is a NavigationDocument in the global JS context as navigationDocument, which is just a container for actual TVML documents you will push onto the stack for the user to see.

So given that, you'll need to use ClojureScript to download a TVML file, turn it into a Document, and the push it onto the stack. (I'm using cljs-ajax here for GET):

(defn startup
  []
  (GET "http://localhost:3000/index.tvml"
       {:handler (fn [response]
                   (let [doc (parse-document response)]
                     (.pushDocument js/navigationDocument doc)))}))

That's fairly clear I think: You have a server running that will provide index.tvml, you fetch it, parse it, and push it onto the navigationDocument stack. What took me a while was figuring out exactly what parsing that document should look like: You can't push a string of TVML onto the stack, and there's no new Document() in TVJS. (And incidentally, the debugging option is just using the Safari developer console attached to the AppleTV Simulator, which isn't the greatest). Here's what you actually need:

(defn parse-document
  [doc]
  (let [parser (js/DOMParser.)]
    (.parseFromString parser doc "application/xml")))

Fairly simple, but not well-documented.

For the record, stock Reagent was my initial plan for handling state and behavior on a ClojureScript-on-TVOS app, but that's not currently an option. There's a very alpha implementation of React for TVOS, but it seems like it might be a while until that's a viable option. I'll just be using stock ClojureScript for the time being.