Development

How to Implement Dynamic Themes for Titanium Alloy Mobile Applications

By March 22, 2016 No Comments

We’ve been using the Appcelerator Titanium mobile framework for years now (from v1.0 all the way to 5.2.0 as of this writing), and it has served us well for apps both large and small. However, we encountered a new challenge recently in one of our mobile apps: how to theme the app AND allow the user to select their preferred theme. For example, we wanted the app to have at least a “light” and a “dark” theme, and probably a few more colorful options down the road as well. We wanted to show the theme options in a settings screen and let the user bounce between themes while live-updating the app’s look and feel.

Titanium Alloy has some built-in support for themes, but you’re forced to choose one theme during compilation, which locks your app to that one theme. There isn’t a way to let the user of the app switch between themes “on the fly” while using the app. This is a significant limitation of the built-in theme support, and one I hope the Titanium team will address in a future release.

However, we didn’t let this stop us, after all, we’re developers!

Our Requirements for Dynamic Themes

  1. Each user should be able to select a theme from a list of available themes, within the app itself
  2. Ability to reference the current theme styles from Titanium Alloy TSS files (.tss)
  3. Ability to reference the current theme styles from Titanium Controller/Javascript files (.js)
  4. Have a default theme set when the app launches

How to hack add dynamic themes to your Titanium app

Based on the requirements above, here are the overall steps for implementing dynamic themes:

  1. Define your themes inside your alloy.js file
  2. Add a few global methods inside alloy.js for getting/setting the theme
  3. Define “proxy” TSS styles for your themes inside your app.tss file
  4. Reference your theme styles from Alloy views
  5. Reference your theme styles from Alloy controllers (.js files)
  6. Build a screen/UI to change the theme
  7. Implement “restart” app logic to close all windows and re-launch (to pick up new theme)

Step 1 – Define your themes

The first step is to define your themes. This means coming up with some naming conventions for how you want to refer to things like background colors, font colors, etc. We used names like: backgroundColor, primaryColor, secondaryColor, accentColor, dangerColor, etc. Once you have your naming conventions determined, open up your alloy.js file and add some lines like the following. We’re using 2 themes in this example: light and dark

// alloy.js

Alloy.Globals.themes = {
    light: {
    name: "light",
    backgroundColor: "white",
    primaryColor: "black",
    accentColor: "grey",
    dangerColor: "red"
  },
  dark: {
    name: "dark",
    backgroundColor: "black",
    primaryColor: "white",
    accentColor: "yellow",
    dangerColor: "darkred"
  }
};

Step 2 – Add some global helper methods

We need to add some global helper methods so we can get/set/switch themes from our controller code. Again, we’re going to edit the alloy.js file to make this happen:

// alloy.js

// this will hold a reference to the "current" theme
Alloy.Globals.theme = null;

// getter/setter helper methods
Alloy.Globals.getTheme = function() {
  if(Alloy.Globals.theme !== null) {
    return Alloy.Globals.theme.name;
  }
  return Ti.App.Properties.getString("app:theme", "");
};
Alloy.Globals.setTheme = function(name) {
  if(Alloy.Globals.themes.hasOwnProperty(name)) {
      Alloy.Globals.theme = Alloy.Globals.themes[name];
      Ti.App.Properties.setString("app:theme", name);
  }
};

Step 3 – Define some “proxy” TSS styles

This step is optional, and slightly tedious to be honest, but we found it helpful in our case. If you want to be able to reference your theme styles from your TSS stylesheets (.tss files), then this step is required. We are going to create “proxy” TSS style names that map to the current theme’s styles. It’s going to be quite verbose, but it’s a necessary evil to get the behavior we want based on the current state of Titanium theme support. So, open up your app.tss file and add the following styles (adjusted based on your specific themes, of course):

// app.tss

".backgroundColor": {
  backgroundColor: Alloy.Globals.theme.backgroundColor
}
".primaryColor": {
  color: Alloy.Globals.theme.primaryColor
}
".accentColor": {
  accentColor: Alloy.Globals.theme.accentColor
}
".dangerColor": {
  dangerColor: Alloy.Globals.theme.dangerColor
}

NOTE: You only need to define the TSS styles ONCE, no matter how many themes you have. At runtime, the TSS style definitions above will grab the current theme style dynamically – this is part of the magic 🙂

Step 4 – Reference your theme styles from Alloy views

There are 2 different ways you can reference your theme styles, and you can mix and match them as needed. The first way is to reference your proxied TSS class/style names inside your Alloy views:

<!-- app/views/page1.xml -->

<Alloy>
    <!--
        This view will have its backgroundColor set from the TSS class style,
        which references the current theme at runtime!
    -->
  <View id="page1View" class="backgroundColor">
        <!--
      This label will have its color set from the TSS class style,
            also based on the current theme
    -->
        <Label id="label1" class="primaryColor">Hello from a themed label</Label>
  </View>
</Alloy>

Step 5 – Reference your theme styles from Alloy controllers (.js files)

The second way to reference your theme styles is by going through the Alloy.Globals.theme object in your controller files:

// app/controllers/page1.js

$.page1View.backgroundColor = Alloy.Globals.theme.backgroundColor;
$.label1.color = Alloy.Globals.theme.primaryColor;

You can mix and match the “Alloy View TSS class” method and the “Alloy.Globals.theme” method of setting your styles and use them in whatever way is most convenient in your code. In our experience, having both options available made it more convenient to use.

Step 6 – Build a screen/UI to change the theme

We’ve set up all the plumbing needed to support our end goal. Now it’s time to actually build a screen for the user to switch between themes! I won’t go into all the details here, since each settings screen will be different. You’ll probably want to give them a list of the available themes to choose from (iterate over Alloy.Globals.themes object) and then click a button to select a theme.

To change the current theme, from your button click handler simply call Alloy.Globals.setTheme(themeName) with the theme name, like this:

Alloy.Globals.setTheme("dark");

However, the code above just changes the theme for new windows you open in your application. Which, depending on your app architecture, might be just fine. If so, then you can stop here, you’re done! If you need to refresh the entire app (like we did) then continue on to Step 7 below.

Step 7 – Implement “restart” app logic to close all windows and re-launch (to pick up new theme)

As mentioned above, calling Alloy.Globals.setTheme() only changes the theme for new windows, it doesn’t do anything to windows or views you already have open. To handle those cases, you’ll need to do a little work in your app to implement some kind of “restart” feature. Here are the high-level steps:

  1. Create a “restart” method on your root window (or any object that will be in the global scope)
  2. Add a global event listener for a custom “themeChanged” event that will call your restart method
  3. Fire the custom “themeChanged” event immediately after you call Alloy.Globals.setTheme()

Here’s how we implemented it:

Our index.js controller is just a “shell” controller (it doesn’t have a visible UI), but it manages a single “root window” that is the home screen of our app. The index.js never goes out of scope (default Alloy behavior), so it can manage the root window by closing it, and then re-opening it again in order to accomplish the “restart” operation. So, inside index.js we have code like this:

// index.js  (Alloy Controller)

// Restart behavior

// listen for our custom "themeChanged" event
Ti.App.addEventListener("app:themeChanged", function(e) {
    // implementations not shown here, but you can imagine what this does :)
  closeAllWindows();
  createRootWindow();
});

Then, inside our settings screen, or wherever you’re changing the theme, we have code like this:

// settings.js  (Alloy Controller)

// user clicks a button to change the theme...
$.changeThemeButton.addEventListener("click", function(e) {
    // set the new theme by name
    Alloy.Globals.setTheme("dark");
    // fire our custom "themeChanged" event
    Ti.App.fireEvent("app:themeChanged");
});

Conclusion

You did it! You now have custom dynamic themes that can be changed at runtime by the user. If you exclude the actual theme definition stuff in alloy.js, it’s actually not too much code in total. I’m hopeful that the Titanium team will implement something in the core SDK to make this easier in the future, but for now this home-grown solution is working for us.

I hope you found this post informative, thanks for reading!

Web Application Startup Guide

A 30-page ebook that covers positioning, marketing, pricing, and building your startup product, plus more.

Leave a Reply