Wednesday, December 23, 2015

A new ParkInfo Mobile using Ionic & AngularJS

It's been three years this week, since I wrote my first mobile app: ParkInfo Mobile. And this week, the new one is still under wraps for some branding and color choices, but is functional. ParkInfo Mobile 2.0.0 should be in the app stores in a week or two, replacing my Christmas 2012 edition.

I'm not particularly sentimental, but it's a big step forward in a few ways. Let me share a few interesting changes in the new version, and lessons learned by writing it.

What's New

First and perhaps largest: It's written in Ionic, and thus AngularJS. This is a paradigm shift from jQuery Mobile, in which the DOM and global variables rule everything. The code is broken into a bunch of bite-sized segments: each panel is one HTML page and one paragraph of code, though some of them refer to the "global" functions for some commonly-used functions and needs.

It doesn't use an onboard JSON file, but HTTP hits to a web service. The original ParkInfo Mobile had a JSON file included, which could be used for searching and estimating distance without using the data connection. But it kinda sucks. Including the polygons made the file about 100 MB and would crash any phone, so it didn't happen. Using only the centroids means that we can estimate distance and direction to the centroid; for a city park that's not too shabby, but for a 500,000-acre preserve the centroid could be miles away and be misleading. The new one uses a web service to query the database; it's swift, simple, and effective. Better, the web service can accept a hint as to your origin lat-long and return the closest point to each of your results, greatly improving the accuracy and usefulness of the results.

The performance is fabulous, and look and feel is smoother. They said that AngularJS and Ionic have decent performance, and I know that jQuery Mobile kinda sucks... but dang, it's really that much improved. Navigation is smoother, more fluid. The default styling doesn't include so many drop shadows and rounded corners, so the whole thing feels more bare... if a bit cartoonish with those colorful buttons. (Like I said, the colors and branding stuff is still pending.)

The UI for offline tile caching is improved. The underlying technology is much the same: Cordova's file API, with ngCordova wrappers. But the UI for caching is greatly improved.
  • Caching by current map view is disabled, in favor of two new options: cache around a street address, or around your current location. This is a lot more useful, generally speaking.
  • "Passive caching" will cache your map tiles to device storage while you browse the map. If a specific address or your GPS location don't fit what you need, just let the cache populate as you pan and zoom.
  • While downloading tiles, the UI doesn't block you with a modal, but starts up a progress bar and lets you go on your way. Downloading 1000 tiles can take a few minutes, but you're not waiting around.

Lessons, Hacks and Limitations


Some of the things that went less smoothly:

When you need two controllers to know each others' state, AngularJS is a pain in the neck. The cache panel needs a list of map layers, but it's the map controller which has the Leaflet object and the L.TileLayers. Ultimately I really did need to separate out the layer listing into a Constant where it could be seen by both. Angular's all about not believing in globals, but dogma had to take a seat while pragmatism prevailed.

Ionic's sidemenu  is cute, but not flexible. It took me only 2 hours to come up with a thing that our clients will demand that it cannot do: put an icon in the top-right corner, which varies on every page (different icon & link, or none). I tried everything from position:fixed to negative top margins, even tried a nested view in the titlebar, and it's a no -- that titlebar isn't part of the dynamic part of the view. So I went the way I knew I would eventually, started with a blank template where each page has its own titlebar so we have maximum flexibility. I knew it would happen since our capricious designers are the ones calling the shots, but it irked me that those "copy-paste my code, and you're a mobile developer" articles basically have someone copy-paste the sidemenu starter, knowing that it paints you into a corner less than 2 hours later.

I've still not wrapped my head around Services and writing my own promise chains. The web service calls would ideally be a Service instead of a function defined in $rootScope. But a service which accepts callbacks as parameters, which makes a $http call and hands off to those callbacks just eluded me. After a day, I needed to just get past it so I gave up and went with a $rootScope function that accepts a pair of callbacks, without a promise involved. Similarly, I couldn't figure out a promise chain to wrap a dynamically-generated list of potentially thousands of Cordova File API calls and perform them sequentially, so I went with my old "i+1" method. Then again, other reading indicates that a chain of a few thousand promises maybe isn't such a hot idea anyway...

Debugging is a real nuisance when there aren't any globals, and you can't see into scopes private data. Keeping everything in tiny secret boxes, doesn't do well toward being able to type MAP.getCenter() into the Firebug Console to find out something so simple -- and that makes all debugging just that much slower. I need to put in some effort, into figuring out techniques for debugging AngularJS apps aside from console.log() inside these isolated black boxes.

Angular-leaflet-directive is pretty limited, and I've had to do a bunch of work to it myself. I'll likely fork it and start adding my own patches into it in a more concerted manner. But some of the adjustments I've discovered and invented include:
  • You must use in in a ion-view with overflow-scroll="true" or else the map will cease to receive click events if you navigate away from it. It's not just the map, it's something about the underlying DIV. Those "copy-paste your first mobile app!" postings don't mention stuff like that, eh?
  • It doesn't support popups, so I added a popup bit to it.
  • It doesn't propagate bounds changes back to the scope, only center. Fixed that. 


The Future of MobileMapStarter

My second mobile app was actually stripped down from my first: ParkInfo Mobile's map/cache/page functionality was stripped down to form MobileMapStarter.

Last year after I presented MMS at FOSS4G it got some popularity, but I've been less than 100% satisfied with it. It's jQuery Mobile so it's slow, the cache UI isn't so hot, etc.

But here we have a new framework with a map, panels, and caching. If I strip this down a bit, it's a fresh new start for "MobileMapStarter, Ionic Edition". Schedule permitting, maybe I can have that done in a couple of weeks.

No comments:

Post a Comment