Or: An example of a master > detail app.
We have updated our Titanium RSS Reader sample app to use Alloy and show some of the best practices when working with the MVC framework. The app lets you pull a live RSS feed from the internet, list the articles and then drill down to the article itself.
- Source: GitHub Repo
In this article we will walk through some of the capabilities this app includes. As you'll see the source code itself is also heavily documented to explain what is going on.
Custom Android Material Theme
For Android, the app uses a custom Android Material Theme. Material made it much easier to create custom Android themes and all you have to do is override some of the default colors in platform/android/res/values/custom_theme.xml and select the style name in tiapp.xml under android/manifest/application@android:theme.
- Guide: Android Themes
We've made it very easy to change the URL for the RSS feed without touching the code. It’s set in app/config.json under global/url and read as
Alloy.CFG.url in app/controllers/master.js. As you can read in the guide, we could even set a different value for this propery based on the platform and environment the app runs on.
For iOS we need to set the colors for our toolbars on each Window. Fortunately we can use app/styles/app.tss to set styles that apply to all views. This is also a great place to reset/normalize, like we do for Labels, which are by default
white on Windows and some
gray on Android.
- Guide: Alloy Styles and Themes
As the screenshots show, we use platform and even device specific UI elements to provide the best user experience.
We could use Titanium and Alloy's support for platform-specific resources and create an Android-specific view in
app/views/android/index.xml and an iOS-specific view in
In this case we chose to use Alloy's support for conditional code to keep them all in one view. We would have needed this for an iPhone and iPad specific view anyway and this way you can see how we handle all platforms and form factors in one glance.
You will see we use conditional code in the other two views as well to use platform-specific UI elements like Android ActionBar menu items and a RefreshControl for iOS.
- Guide: Require Element
The app uses the following navigation patterns:
NavigationWindowfor iPhone and the similar
NavigationGroupfor MobileWeb. The classic example of this pattern is the Settings App on iOS. It stacks multiple windows, showing the title of the current window in a navigation bar that also has a Back button to pop back to the previous one. Ideal for hierarchies of two or more levels like our master > detail.
SplitWindowfor iPad is a pattern you will know from the Mail App on iOS. It displays two windows, one narrow and the other wide. Ideal for a single level master > detail app on bigger screens.
- And then finally, for Android we simply open windows on top of each other without managing much about them. And for Android this works just great because it has hardware buttons for navigation back as well as an Action Bar that we can set to display a back button.
ListView vs TableView
In app/views/master.xml you can see we use a ListView for iOS and Android and a TableView for MobileWeb, which does support ListViews.
ListView is the more efficient and faster successor of TableView, but there are some specific use cases for which we keep the TableView around for all platforms. The main difference is that ListViews use templates and data while TableViews are composed of regular views.
ListViews can be more difficult to implement in Titanium, but as you can see Alloy hides most of that complexity from you. So if you can, always use ListViews instead of TableViews.
Collections, Models & Data-Binding
Perhaps the most complex part of our app is the use of Alloy Collections, Models and data-binding to fetch and display the RSS feed. It is really just seen as complex because Alloy does most of the work, which makes it feel like magic.
Embrace the magic and unleash the power of BackBone.js in Alloy:
Models can be seen as the rows of a table and their definition configures the columns and the connection to where the rows are read from and synced to. The columns are not required for all types of sync adapters and as you can see our app/models/feed.js only has the configuration for our custom RSS sync adapter.
Alloy comes with a few ready-made sync adapters of which the one for SQLite is probably the most interesting one. You can add custom sync adapters by dropping a CommonJS module that follows the expected signature in
app/lib/alloy/sync and use the filename in the model definition. A popular community adapter is restapi by Mads Möller.
For this app, we created a custom rss adapter that reads a URL or path to a local file. For MobileWeb we use a local file because our RSS feed currently doesn't send a
Access-Control-Allow-Origin header. When it is done, it calls the
opts.success callback with the fetched data and BackBone will create and collect the models. We also fire a
fetch event so that our data binding is triggered.
Collections can be seen as tables. They keep models and fire events if there are any changes. This is exactly what we need for data binding.
Data binding simply comes down to listening to changes on an object and updating the UI accordingly. To do this we add a
dataCollection tag to a view element and Alloy will use that element's children to populate the view for each model in the collection.
In the master controller we can now call the collection's
fetch() method to have it populate itself using the rss sync adapter which in turn will trigger the data binding to display them in the list. As you can see we also have the option to transform the data by setting a
dataTransform function in the view.
- Guide: Alloy Models, also on Collections, Data Binding and Sync Adapters
Opening the detail view
The last piece of our puzzle is opening an article from our list. The master view sets a
select callback to be called when the user taps on a row. You can also see that it binds the identifying
guid field of the models to a special
itemId attribute of our rows.
The master controller defines the callback, receives the id of the model that comes with the event, looks up the model and then fires an event on the controller itself. We haven't talked too much about it, but every controller extends the
BackBone.Events prototype, which allows it to dispatch events.
The reason we don't open the detail window directly from masters is that it depends on the navigation pattern how exactly we should. It's a best practice to keep controllers unaware of their context so we can re-use them in different contexts. So in the index view we bind an
onSelect listener to the
select event of the master controller. In the index controller you will see that this again uses Conditional Code to determine how exactly to open the detail window, passing the model to the detail controller.
The detail controller then receives the model and uses it to set the window title and WebView URL. The WebView can be seen as a chromeless browser, embedded in the app. Though one of Titanium's unique features is the use of native UI components, you can easily intergrate existing local or remote HTML and even communicate with it.
- Guide: Integrating Web Content