API Development

How to Globally Access Variables and Functions in Titanium

Since the release of Titanium SDK 9.0.0.GA, variables defined in app.js/alloy.js aren’t accessible anywhere else anymore. So now it is time to implement some best practices in your app, instead of using globals everywhere.

In this blog post, I’ll focus on Alloy, but some options might help you in classic as well. That said, if you still have a classic app you can migrate it to Alloy in an hour (no matter the size of your app) and you can start using the Alloy benefits. To get started, you only need to follow step 1 of the migration guide.

Notice: Only the implicit global scope has been removed in SDK 9.0.0.GA. Alloy.Globals remains (and will remain). This blog post is to help you implement best practices to avoid putting variables or functions in a global scope when you shouldn’t, not when you couldn’t.

Defining static variables

If you have variables in your app that are static (like a certain color, API keys, or URL), you should never define these in alloy.js. You shouldn’t have done so in the first place and now is a good time to fix it. The place to put these is in config.json. The properties in these files are accessible globally by default. When you open up your config.json file, by default it contains a few elements:

{
"global": {},
"env:development": {},
"env:test": {},
"env:production": {},
"os:android": {},
"os:ios": {},
"dependencies": {}
}

As you can see, you can define configuration properties per OS and deploy target. But you can also mix these by adding more configurations like this

"os:ios env:production": {},

Once you understand how you can define different properties per deployment target, you can easily understand how your API URL should be configured, or how you can define colors per environment, like a red bar on a dev build, but it’s your default color on a production build.

An example

{
"env:development": {
"api_url": "https://dev.example.com"
},
"env:test": {
"api_url": "https://dev.example.com"
},
"env:production": {
"api_url": "https://api.example.com"
}
}

Now anywhere in your app, you can get this URL like this:

const api_url = Alloy.CFG.api_url

This URL will automatically adjust based on how you build your app. So now you can put any variable you’ve otherwise defined in alloy.js in config.json. If you don’t need your variables to be changed based on deployment target, you can just put them all in the global: {} section and they’ll be available regardless of target.

Defining dynamic variables

When you want to define variables that can change on the fly, you need to know whether or not this variable needs to be saved across sessions, or be session-based. I will explain both!

Stored properties

If it does need to be saved/stored, you will have to use a storage method. The best place to start with is properties. The easiest way to do this is get/set the properties directly in the place where you need it, however, this is not a best practice, as when you want to rename your property ID, or you want to change the method of storage, you will need to refactor your app in many places. Instead, make a library file that handles storing for you. For this example, we’re going to create a file in the lib directory: /app/lib/user.js.

Inside this lib file, we can define something like this (as a simple example)

function getUser() {
  let user = Ti.App.Properties.getObject('user');
  if (user) return user;
  // set default if no user is present
  return setUser({name: "default", id: 123});
}
function setUser(user) {
   Ti.App.Properties.setObject('user', user);
   return user;
}
module.exports = {
  getUser,
  setUser
}

Now, you got 2 exposed functions for getting and setting the user, with default handling included. Of course, you could integrate API calls in here, and use callbacks if you like to make it async for example. You could make it as complicated (or as simple) as you like. One thing to take away from this is you can call these methods at as many or few places you want, there is only one file you need to alter if you want to change the property name, or more importantly, to storage method.

To call the function above, the only thing you need to do is this:

const user = require('/user').getUser();

And there is one extra benefit: you don’t need to implement default handling anywhere else, and if you make sure the getUser() function always returns the same user object/structure, you don’t have to do any validation anywhere either.

Temporary/session-based globals

So here’s the next part where you should avoid globals, which are temporary or session-based globals. Of course, the easiest thing to do is just create a couple of global variables and be done with it, however, that can cause a whole lot of issues down the line, or even immediately (think of all the variable names you use throughout your app, are you sure nothing is named the same as the global?)

The best way to do this is to use the same trick as with stored properties. Library files in the lib directory. Any lib file you require in your app will have persistent memory for the duration of your app session. Garbage collection does not clean up variables defined in the top level of any required file, but those variables are only accessible within the scope of those files.

So let’s say you want access to a certain TabGroup throughout your app. (So you can open new windows in any tab of your choosing from anywhere in your app), then you can just do something like this: /app/lib/tabgroup.js

var tabGroup = false;
function setTabGroup(tg) {
  tabGroup = tg;
}
function openWindowInTab(tab, window) {
    tabGroup.tabs.open(window);
}
module.exports = {
    setTabGroup,
    openWindowInTab
}

In the example above I add functionality directly, instead of just offering a “get tabgroup” function. The reason for doing this is it centralizes logic otherwise scattered around. You can also easily plug in analytics at this stage to keep track of which windows are opened, and of course add added functionality (such as changing colors based on certain conditions, triggering events for other windows, etc). And now, all of a sudden you have access to open windows in any tab, without the need to keep track of which added functionalities there are in your app.

Setting it up

To set the TabGroup initially, you don’t have to create it using classic code, but you can use Alloy instead. A simple example would be

<TabGroup id="tg">
<Tab><Window></Window></Tab>
</TabGroup>

Then you can call the setTabGroup function directly in the related controller:

require('/tabgroup').setTabGroup($.tg)

So, anywhere in your app you can just call the methods using

require('/tabgroup').openWindowInTab()

Memory Persistence

As mentioned earlier, any variables defined in the top-level scope in a required file are memory-persistent and won’t be garbage collected. This is something to be aware of, as any reference stored in a required file, like with our TabGroup above, won’t be cleaned up and thus the TabGroup will remain in memory even if said TabGroup has been closed. So make sure you clean up any references you store once you want to get rid of them. The easy thing to do is calling the setter function again, but with null

require('/tabgroup').setTabGroup(null)

Further Steps

With that all in mind, you now know the best ways to have variables and methods accessible globally without polluting the global namespace. Now it is up to you to implement it!

RC Release of Titanium SDK 9.1.0 and Appcelerator CLI 8.1.0