When building an application for both iOS and Android with Titanium you may run into a set of styling challenges. What do I do when my UI components are essentially the same in concept, but have slightly different styling properties? Luckily there are some things built into the Titanium framework to support this and also some not so obvious solutions that help us as well.

To get things started, lets focus on what is currently available with the Titanium platform. First of all there are target specific directories that you can put your resources in. Most developers are aware of this because they have stored device specific images in those directories. For example, I may have an image (backgorund.png) I use as a window background that exists in the following:

  • android/images/res-long-land-hdpi/background.png
  • android/images/res-long-land-ldpi/background.png
  • android/images/res-long-port-hdpi/background.png
  • android/images/res-long-port-ldpi/background.png
  • android/images/res-notlong-land-hdpi/background.png
  • android/images/res-notlong-land-ldpi/background.png
  • android/images/res-notlong-land-mdpi/background.png
  • android/images/res-notlong-port-hdpi/background.png
  • android/images/res-notlong-port-ldpi/background.png
  • android/images/res-notlong-port-mdpi/background.png
  • iphone/images/background.png

Anything that we put in the target specific directory structure will be resolved for that particular device.

Javascript Style Sheets (JSS) are another option. These are great when your logic involves static styling that does not need to be merged. You can create elements that will automatically be pulled into the styling that are in a resource specific directory from above. Here is a sample app.jss with the definition of a “window” class.

1
2
3
.window {
   backgroundImage:'images/background.png';
}

And here is the usage of it in the construction of a window element:

1
2
3
var mywindow = Ti.UI.createWindow({
   className: "window"
});

Unfortunately this breaks down for dynamic attributes which we’ll get into next. Additionally it appears JSS is going to be rolled out of the roadmap in upcoming versions of Titanium and is not supported for mobileweb.

Another technique that is used includes conditional resolution of properties based on the Ti.Platform.osname. For example, below we conditionally set “width” to a different value based on whether its iphone or not:

1
2
3
4
5
6
7
8
9
10
var label1 = Titanium.UI.createLabel({
   color: '#999',
   text: text,
   font: {
      fontSize: 20,
      fontFamily: 'Helvetica Neue'
   },
   textAlign: 'center',
   width: (Ti.Plaform.osname == "iphone") ? 'auto' : 300
})

There are other shorthand ways that we can apply this technique. Its great when the attribute can be directly replaced, but breaks down when the changes are wide across an object or the attribute sets are disjoint. Lets consider another scenario where this is the case:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var text = "I am Window 1";
if (Ti.Platform.osname == 'iphone') {
   label1 = Titanium.UI.createLabel({
      color:'#999',
      text:text,
      font:{fontSize:20,fontFamily:'Helvetica Neue'},
      textAlign:'center',
      shadowColor: '#000',
      shadowOffset: {x: -0.5, y: 0},
      width:'auto'
   });
} else {
   label1 = Titanium.UI.createLabel({
      color:'#AAA',
      text:text,
      font:{fontSize:20,fontFamily:'Helvetica Neue'},
      textAlign:'center',
      width:'auto'
   });
}

As we can see, the sets are disjoint. The iPhone target includes the properties “shadowColor” and “shadowOffset” whereas the Android target does not. Meanwhile, the attribute value “color” is different. Most importantly, we have a dynamic value being set for the attribute “text”. To resolve this, we introduce a solution that includes:

  • target specific attribute set values, statically defined
  • dynamic values that are merged into the resolved attribute sets

We’ve created a solution that is available under https://github.com/ehirelabs/pragmatics_skills. Lets first define our combine function in combine.js. In this example we use underscore to help us with creating a copy of the static definition. You may however want a more advanced implementation that does a deep merge rather than a shallow merge.

1
2
3
4
5
6
var _ = require('underscore-min');
exports.combine = function(source, destination) {
   var target = _.clone(destination);
   var result = _.extend(source, target)
   return result;
};

And lets also define our iphone/style.js which is also implemented as a commonJS module.

1
2
3
4
5
6
7
8
9
10
11
12
13
exports.style = {
   label2: {
      color: '#999',
      font: {
         fontSize: 20,
         fontFamily: 'Helvetica Neue'
      },
      shadowColor: '#000',
      shadowOffset: {x: -0.5, y: 0},
      textAlign: 'center',
      width: 'auto'
   }
};

Now that we’ve done the setup, we can now construct the label in a target agnostic way in app.js:

1
2
3
4
5
6
7
8
9
var _style = require('style').style;
var _combine = require('combine').combine;
...
var text2 = "I am Window 2";
var label2 = Titanium.UI.createLabel(_combine(_style.label2, {
   text:text2
}));
 
win2.add(label2);

As you can see the logic in our application is a bit more clean and only includes the dynamic property “text”. Obviously we also create a corresponding android/style.js which has the android specific styles.

Having complete target specific style definitions removes the need for conditional values that pollute the codebase and easily expand beyond the duplicate style.js definitions quickly – especially for large applications. You’ll also find that its quite nice having all the style definitions in a single location much like CSS for web apps.