Titanium application structure – learning from Tweetanium
Up until recently, like so many other lost souls, we've followed the same Titanium application structure as Appcelerator's KitchenSink application. In this paradigm, when you want to open a new window, you'd typically do something like this:
Inside somefile.js the window would be populated with controls, using the Ti.UI.currentWindow reference. We can access the data from the previous window through win.importantdata.
This approach, even when improved upon, has several disadvantages:
- If you have a file with application helper functions, you need to import that in every other file that needs the functionality. In my Tristania app for example, pretty much every file starts with Ti.include(“../assets/utils.js”).
- Every time a window is meant to be created, the relevant file must be read and code parsed.
- The structure of the app becomes rather opaque.
- It is very difficult to unittest this approach.
It all works, sure, but I've never been comfortable with my application structures, feeling there must be a better way. Even though there are (many) ways to improve on the above example, I wanted a different structure paradigm alltogether. Alas, I was too lazy to get around to experiment with it, so “it remained but a beautiful thought”, as we say in Sweden.
Fortunately, Kevin Whinnery, Chad Auld and the rest of the Appcelerator team have no such character flaws! Recently they opensourced Tweetanium, a fully functioning Twitter client coded with best practices regarding both JavaScript in general and Titanium in particular.
Which was like christmas to me! The only non-Krawaller Titanium source code I've gleaned (with a few none-noteworthy exceptions) is the aforementioned KitchenSink, which – let's be honest – is not a great piece of work at all. It demonstrates the API very well (which is great since the docs are crap), but as an application example, it leaves me wanting.
So, enter Tweetanium. As soon as you open up the sourcecode, it is evident that they're doing things very differently! I have created a stripped-down, barebones version of it called Struct, aiming to expose the new structure paradigm. You'll find the git repo here. This is what it looks like – isn't knowledge beautiful? :)
The main point in the Tweetanium approach is that my various files (like somefile.js in the above example) are not messing with Ti.UI.currentWindow – instead, it augments a global namespace with a constructor variable. In Struct, I end up with the following global object:
The program flow is that the otherwise pretty empty app.js imports struct.js, which can be considered your main app definition file. This file creates the global namespace and maybe some utility functions, and then includes the other files to populate the rest of the namespace.
This means that as soon as the struct.js file is included, the entire app structure is created in memory. No more parsing of files will be done during the session.
App.js will then call the createApplicationWindow function, call the open method on the returned window, and maybe do some other app initialization stuff. Now the app is up and running! In Struct, app.js looks like this:
So what are the great advantages to this approach? I've already found quite a few, but it boils down to a single keyword: closure. Every single function is executed in the same closure (ok, maybe subclosures, but you get the gist). Read that sentence again until the implications set in.
This is good news from a performance perspective (and most likely stability) – no more importing the same file a gazillion times! But from an infrastructure point of view its even yummier:
- As we always have access to the same scope, we can keep app-wide data in a single object, instead of JSON:ing back and forth with Ti.App.Properties.
- All kinds of other convenience structures are suddenly very easy to build. In Struct there is a global messaging system, using one single view, instantiated only once. Tweetanium does a similar thing with a loader view, and has a very neat Model baseclass thing going.
- You can instantiate all app windows and views immediately, as they all live in the same closure. Windows opened at a later point will also live in the same closure, since that's where the constructor function is defined.
- Creating your own navigation system ("immersive UI", to use Apple's lingo) is a breeze. In Struct I've stolen the home-brewed tabs from Tweetanium.
Now, having autopsied Tweetanium, created Struct and looking at the layout before me, it seems very obvious and self-evident. Perhaps it already was to the rest of you, but for me it was a new-found revelation, and I can't wait to test it out in our next project! So, for those of you who like me hadn't caught on to this train before, check out Struct & Tweetanium and try it out!
41 comments
Be aware of the non-declared variable animationDuration in "ui.js". It throws an error when tries to animate the tab (applicationwindow, line 90)
Thanks again
Fixed now in the repo. Thanx again!
I was trying to understand the whole thing. But it was too much to follow! This is so kind of you to clear it out for noobies like me!
Also, I couldn't make the configuration for tweetanium to work with oAuth thing! I have no idea how to do that which makes it even harder for me to follow the design pattern that tweetanium did!
Thank you so much. I will add you in twitter iSam_sa. I want to ask you a question about tweetanium!
Regards!
Sam
Any questions just ask away!
Thank you so much
Good luck
Seriously man awesome job! I spent hours looking at the tweetanium code trying to understand what on earth was going on with no success. Thanks for stripping it down to the core and making it so easy to grasp.
cheers
Thanks for hard work!
@Matt: as I read them, mixin and combine are clever ways of managing and overrriding default properties for object. Using mixin, you can change the e.g. one thing you need this time round and pull in the other default properties for the object you are using. Some info here http://docs.dojocampus.org/dojo/mixin
If we're ever in the same city, I'm buying you lunch.
https://gist.github.com/1084006
Thanks!
And, since we stored the mainTabGroup in S.app (I've since started calling it S.state, which makes more sense), we have direct access to the current tab from within foowin.js! All we need is to change this:
subwin.open();
...to this:
S.app.mainTabGroup.activeTab.open(subwin);
See new fork of your Gist here: https://gist.github.com/1084092/
I managed to skip some of the goodness held within S. There's a big mental shift from the documented structure of opening all those windows by URL, this helps a LOT! Thank you so much!
Thank you so much
A TabGroup is a view, so you can add view children to it as you usually would. These will live "above" the tabs, and aren't affected when you switch between the tabs.
Great post! This has really helped me understand the tweetanium structure which is better than the kitchen sink approach i used to build my app. I have a follow up question though - I understand the whole global context approach but have a major concern - Memory Consumption.
I have several heavy weight windows in this app (think of kitchen sink) and using a global context approach loads everything into memory. So basically once a window is opened the allocated memory is held forever and not all of it is released even when the window is closed. So I was hoping you could give me a few tips on how to tackle this issue. I'm using the approach you mentioned here:
https://github.com/krawaller/Struct/blob/nativetabs/Resources/struct/ui/barwi...
I've been struggling with this for awhile so any pointers would be greatly appreciated.Thanks
@Nick: I must admit that memory consumption in Titanium apps isn't one of my strengths, although I share your concern and plan to immerse myself in the subject at some point in the future. However, the single-closure structure in and of itself really shouldn't be any different from the KitcchenSink approach in regards to memory!
Global context doesn't mean you HAVE to have lots of window instances at the same time. It just means you CAN. You can also choose to instanciate them only when needed and then close them again, much like KitchenSink does. The only difference is that the CONSTRUCTORS are already defined, and live in a single closure. This shouldn't be a significant memory-overhead.
In my (admittedly limited) experience, having lots of window instances simultaneously is pretty cheap. In our latest app, I instanciate every single window within the app at startup, and then just switch between them as the user navigates around. Not sure if this actually is better than to just instanciate the current window and then close it again, but it definitely seems to flow smoothly.
Are you sure you're seeing a difference memory-wise when comparing the two methods? I know that Titanium is generally not too efficient with regards to memory, and that the cleanups aren't doing all one could wish from them.
Thanks for your reply. I must confess that I have not implemented the whole app with the new single closure approach but simply replicated basic functionality in 2 windows and hence don't have a true comparison between the 2 methods. I presumed the whole global context might have severe effect on the memory consumption. I understand titanium has it's limitation and I guess sometimes you just have to take the good with the bad.
I'm intrigued by your approach of initializing windows on startup. That should definitely give the app a smoother outlook. Do you init all sub windows as well or is that done on the fly? I'm personally struggling with the fact that my heavy weight sub windows consume 'x' amount of memory when opened and not releasing all of it.
However, your post has certainly given me some ideas on how I might be able to tackle my problem ( i guess talking it out helps). Will def post my successes or failures if you're interested. Once again appreciate your reply. Btw, if your app is available on the app store do let us know, would love to check it out.
Cheers mate.
But this has been a real eye opener and if it helps with stability and code I'm much happier!
So thanks again!
Is there a way of modifying the label which is created by the createpulldown from the event listener in createtableview ?
I know I could use global variables, but I'd rather everything stay local if I can.
@Nick: I init all subwindows too in my app. Still not sure whether or not this is a good idea, but definitely works! Would love to hear how you fare, so by all means, give a shout when you have war stories to share!
@Paul: Glad it was of help! Regarding your question, it can be solved by saving a reference to the label on the returned object from the createpulldown constructor. Here's a new pastie: http://pastie.org/2333857 (not tested, so pls don't sue me if I screwed sth up)
the sign-in/sign-up window doesn't belong to any of 4 tabs. in this senario, what is the best practice to create and open the independent modal window? i can create and open a modal window in a kitchen sink way. (here's what i did: http://pastie.org/2343917) but, i wish i can use your approach. Please enlighten me a little bit.
I tried debugging this on Android, but I get the following error:
TypeError: Cannot find function createApplicationWindow in object [object Object].
Any ideas on this?
Thanks in regards!
What about Android's Hard-Key arrows for navigating elements for non-touch devices.
With a normal Tab Group it can select the tab button and press center key but using your tabs it's not going to work.
What do we do to combat this?
This code works well on the ipad but crashes on my Asus tablet.. :s
Someone got an idea?
Super work by the way !!
I noticed that tweetanium uses views rather than separate windows. These views are dependent on a a film strip? Does this prevent imageview from loading on iOS? Maps? I've read around that tweetanium as a structure is very limitied. Have you had any experience with this.
