Like most of my apps, Nearest Bus (originally London's Nearest Bus) was written to scratch my own itch. We were living in Fulham, in London, and the easiest way to get to work was by bus. Around the same time, Transport for London developed, and released to the public, a bus prediction API called Countdown.
This is a multipart post-mortem-style post series on the design and development of Nearest Bus. I'm planning on covering the architecture (this post), design, code, widget and possibly some on adding a WatchKit app, once I finish that off.
Countdown, when you think about it, is a pretty amazing piece of software. Given about 6000 buses on the streets of London traveling to 20000+ stops on 1400 routes, it calculates how long it will take for a given bus to get to a given stop. This isn't based on the bus schedule, because in most cases, London doesn't have bus schedules - there is a start time (first bus leaves the first stop at 05:00), and a time to next bus (every 10 mins from 05:00 to 07:00, then every 5 mins until 09:00 etc). There isn't really a bus which comes at 09:05.
For the passenger, this is a pretty awesome system, as a bus is never very far away - except when they are. The countdown system can predict how long a bus will take to get between two stops, based on the traffic conditions experienced by the previous bus, the buses GPS location (every bus is tracked), and a host of other factors. In near-realtime, with about a 30 second resolution.
This data was primarily used for their bus board system - the electronic boards which are at more major stops, showing when the next buses are coming - and a phone / SMS system for stops without bus boards.
The countdown API is fairly simple, tho the return packets are a bit of an odd bastardisation of JSON.
[4,"1.0",1424664010775] [0,"Thames Barrier","9518","73936","490013206W","Canning Town",264,"P",51.502594,0.036482] [1,"Thames Barrier","9518","73936","490013206W","Canning Town",264,"P",51.502594,0.036482,"474","474",2,"Barking Road","Canning Town, Barking Road",1424664536000,1424664536000] [1,"Thames Barrier","9518","73936","490013206W","Canning Town",264,"P",51.502594,0.036482,"474","474",2,"Barking Road","Canning Town, Barking Road",1424664301000,1424664301000]
Really, it's not JSON at all, tho it looks a bit like JSON and parses based on some basic JSON rules.
After a year or so of not maintaining the app due to a combination of its crufty-ness and it "just working", I decided to rewrite it using techniques, ideas and skills I've learned working full time on iOS and Android apps for the past 18 months. It was also written using the old "Classic" API bindings from Xamarin, which do not support 64bit, so this was a chance to move it to the modern "Unified" API, especially as Apple are not accepting updates without 64bit anymore.
The criteria was:
- Keep the app as similar to the original as possible, especially for VI users.
- Clean up all the design and architecture things which pissed me off, and there were a lot of them
- Make it more maintainable, so I would maintain it.
- And most importantly for me - this is a "scratch my own itch" app after all - make it work in Auckland, as I don't live in London anymore!
The last point there is why I wanted to rewrite it - the backend was creaky, to say the least, and wouldn't have supported another backend being put on top of it. Coupled doesn't even come close. "Dogs breakfast" is more apt.
The basic architecture is pretty simple
- At a basic level, use of Dependancy Injection and Dependancy Resolution for find various parts of the app. I used TinyIOC for this, tho Ninject, AutoFac or some other IOC would have worked just as well.
- A message passing communication model between parts of the application. I used TinyMessenger for this, however after using Otto for a work project, I'm thinking of trying to recreate some of it's implementation ideas, especially auto-subscription.
- A provider-based model for each service. Each provider is a singleton, maintained by the IOC container, and responds to a specific message (or more than one), and once finished, passes a message on for the next component.
One of the main advantages of using the provider model is that I can have multiple things provide one service, or swap a provider out for debugging easily (most of my work was done on a ferry with limited, and expensive, 3G coverage).
For this, I have 3 main providers:
- A location provider. This wraps the
CLLocationManagerCocoaTouch class, and receives a message asking for the location to be updated. Once a location of sufficient resolution is found, it passes on a message - and the location - for other parts of the system to use. It also fires progress messages as needed. A fake version of this can give out any
CLLocation, allowing me to easily test the app as if I was standing at a bus stop in Fulham, even if I'm on the ferry from Waiheke.
- Stop providers. These are responsible for finding the stops which are close to the provided location. It's two basic modes are to find all stops around a given location, or to find a specific stop given the stop id. There are 2 of these registered - one for TfL and one for AT. The AT one has to be backed by a small sqlite database, which is created from the data AT provides to Google for the Google Transit integration. Once it's found the stops, the stop provider sends a message with a list of stops, which includes if stop is complete - TfL data comes in complete, AT needs each stop to be loaded individually.
Both of these implement either the
IStopProvider interface, and the front end app is only aware of these interfaces.
I like this method of composing mobile apps, as its very asynchronous, an essential trait of a responsive mobile app, but it also gives a great separation of concerns. The
ILocationProvider isn't aware of who wants the location, just that there is one. The
IStopProvider doesn't care where the location info comes from, just that it has one.
One thing you might be thinking right now is "but isn't Xamarin a cross platform framework? Why isn't this on Android, too?"
The main reason is a simple and selfish one - I don't use Android, not day to day anyway. I may do an Android version later (quite likely, to be honest), and the architecture would make this very simple. 90% of the backend code would be fine as-is (I'd need to refactor a dependancy on
CLLocation out, but thats easy as it's just a Lat/Lon pair), and I'd just need to write the front end.
Android has come a long way since the last time I wrote a cross platform app for it - the 4.x APIs are nice to use, and fairly consistent, especially once you throw in the support libraries. 2.2/2.3 is rare enough now that for most apps, it can be dropped. The move from
Fragmentsmakes the whole thing make a lot more sense.
Xamarin.Forms is another option for this, to make a single cross-platform codebase. Thats another route I might take, more to get some X.Forms experience than anything. I don't love the idea of common-denominator frameworks, but I think it'll be worth a try at the very least.
And no, I'm not going to write a Windows Phone version. Sorry.
In the next part, I'm going to look at the design of the application, such as it is, and what I was inspired by.
It's not overly hard to port an app from Classic to Unified, I just decided to rewrite instead. ↩︎