Sunday 7 September 2014

Making the Malhar app

I took up the opportunity to build an app for a festival in my college. Having only three weeks to build and publish the whole thing, it seemed like a good time to try Cordova, after being frustrated by native (Java based) Android application development several times. I'd still go with native, as using a hybrid tool-kit is situational, as of today.

The app would be nothing complex, just displaying information about the festival itself, schedules and details of the departments involved in it. Mostly static content. Although there was a plan to include timed notifications for each of the festival events, I ended up giving up on it, discussed later in the post.


Choosing the hammer

First, and the most important task would be choosing a UI / page routing framework or library. First thought was Bootstrap or Foundation with AngularJS. Having worked with bootstrap before, and Angular routing, I could go about it quick. A friend recommended jQuery mobile. Ignorant me said 'meh'.

Ionic looked awesome, I started working on it. Things looked good, but then this happened ...


The app became slow and laggy after some time of scrolling and changing pages. This was on a desktop machine using Chrome. Phones don't have that kind of power so it would be worse on mobile.

I don't know what I was doing wrong, but this occurred when loading JSON data using the bundled Angular for doing all things MVC. There might be a very simple fix to this, but knowing very little about memory leaks or ever-increasing DOM nodes and listeners, nothing came to mind.

Tried out the nightly, reading on a GitHub issue that it had been fixed. The above was from using the beta version of Ionic.

No memory leak this time, but the number of nodes still won't go down.


After a bit of more research on how to fix this, I realised I was solving the wrong problem here. I had to build the app, and no matter how awesome Ionic looked and functioned, it wasn't working out for me. Time to move elsewhere. But this time I had no more time to spend on experimenting with frameworks.

jQuery mobile. It might not be as hip as Ionic but it had a stable version. A good theme builder, has been around much longer than most other frameworks with decent docs. Expecting the worse, I quickly rebuilt whatever I had on the Ionic test app but this time using jQuery mobile and it was much better. No more memory leaks or other issues. It was smooth, even on a mediocre Android. The decision was made. jQuery mobile it is. It's not as bad as it might seem at first.


Compatibility Issues

After testing stuff out with jQM (jQuery Mobile), I started working on building the app. Only to find another major issue. jQM and Angular don't work very well together. jQM has it's own routing system, which probably takes precedence over the one Angular has. So loading pages from external .html files seemed impossible. Again, there might be a way, something obvious that I might have missed, but I had limited knowledge and even lesser time. Here's what I came up with.

jQM fills the visible screen with a div of the specified id that has the data-role="page" attribute. I'm no fan of working on a single file for the entire app. But that's the way it had to be done. Angular offers a solution in terms of directives. I'm not sure if this is the way they are supposed to be used, but it worked for me. Without any visible side-effects or downsides. 

Create a placeholder div with a custom directive. And then load the specified template in that div

<!-- department listing for events -->
<div data-role="page" id="events_main" ng-controller="eventsMainController" events-main></div>

<!-- event listing for individual department -->
<div data-role="page" id="events_department" ng-controller="eventsController" events-department></div>

<!-- schedule -->
<div data-role="page" id="schedule_main" ng-controller="scheduleMainController" schedule-main></div>

This way I could have my pages in separate files, and load them on the main index.html when the app starts.

angular.module('malharApp', ['malhar.controllers'])

// events
.directive('eventsMain', function () {
 return {
  templateUrl: "pages/events/main.html",
  transclude: true
 };
})
.directive('eventsDepartment', function () {
 return {
  templateUrl: "pages/events/events.html",
  transclude: true
 };
})

// schedule
.directive('scheduleMain', function() {
 return {
  templateUrl: "pages/schedule/main.html",
  transclude: true
 };
})

Onwards

Another feature of the app was an image of a map that could be zoomed and panned into. Not having learnt enough from my previous non-productive time-spent encounter with Ionic, I tried out a couple of libraries that could be used to implement pan and zoom capabilities for the map, namely svgpan and Open Layers. I finally settled for jQuery Panzoom. Great library. Another lesson learnt here.
Given limited time, choose the simplest way to solve your problem.
I didn't have to work with the map vector (SVG) when a high-res PNG was good enough.


Why Angular for static content


Separation of code and data. Given the amount of content, it would be a huge unmanageable mess to have everything in one file. There might be a performance improvement, but if you want someone to re-use the code for a future version of the app, or expect any sort of readability in your code, keeping data away from code helps. However, if there was a way to compile the resulting HTML generated after all Angular code loads, it would be worth trying. I'm not sure how much that would help in terms of performance.


Not every requirement can be satisfied


Having notifications for the events would be nice, but another caveat of using Angular and jQM together was discovered here. Two-way binding wouldn't work as expected. I had file read and write working (using a FileSystem API plugin) for storing the event notification preferences but the UI is where it was too much work. The app had to be released in a few days, and I knew that getting it to work would take me more time than I could give.



In the end, even if your attempts fail, you learn a lot. Stuff you wouldn't learn if you never tried. Stuff like Angular's $digest won't handle $scope variable changes outside of user interaction, the web FileSystem API's file writer will not reset file size when overwriting a file (this had me confused for several hours, before I realised), thinking twice before running git reset, using callback functions (see nodejs) when dealing with asynchronous code, etc.


Let me know what you think, or if you want a peek at the source.