Since Swift 3 has undoubtedly arrived, I needed a new project to hone my skills. So I sat down and wrote an app that requests bus arrival times for bus stops around one's current location. My goals for this app were simple. Firstly, it should be completely written in Swift 3. Secondly, it should be a shippable product based on best design principles.
The app itself is based on the MVVM pattern. When the app is launched, the user's current location is determined via CoreLocation. The code for this is encapsulated in a Singleton called TFLLocationManager. I used a Singleton here to conveniently determine the user's location from anywhere in the app when needed.
Once the current location has been retrieved, I determine which bus stops are nearby. Then using the bus stop's identifier, I request the bus arrival times for each stop. The network handling is contained in two classes. TFLRequestRequestManager is only responsible for executing simple HTTP requests. It has no knowledge of the actual TfL API. TFLClient however contains handler for each needed TfL API method. It sets up the required URL and calls TFLRequestmanager to execute the request.
When the bus arrival times are returned by the API, which are of course mere estimates based on the bus's current position, I prepare the data for the tableview, which includes
- setting up data model for each bus prediction
- calculating user's distance to each bus stop
- creating a tableview datamodel consisting of bus stop, bus arrival estimates and distance
After that the data is ready to be handed over to the tableview via a corresponding property on the tableviewcontroller.
Whenever new data is handed over to the tableviewcontroller, the tableviewcontroller itself does some preparation for the actual UI which includes
- sorting each bus stop related data by distance
- setting up a view model for each bus stop related data
Once this is done, the tableview will be reloaded to display the current data set.
I honestly wasn't quite satisfied when I completed the initial version of the app. There were two things that bothered me:
- Whenever I went back into the app after a few minutes, I was shown stale data until the API returned new data.
- To retrieve bus arrival estimates for each station, I needed the bus stop identifier. To get it however, I needed to make an additional API call.
To address the first issue, I had to make use of some data that is passed along with each bus arrival estimate. Each estimate contains two things: a timestamp saying when the data was updated by the API and a time-to-live timestamp (TTL) that says when the data expires. The latter is always set to a time after the bus arrival estimate e.g. if a bus is estimated to arrive at 3:15:20 pm the TTL is set around 3:15:50. So to fix my first issue with the app, I first removed all expired bus arrival estimates and then updated each remaining estimate based on the current time using the estimate's timestamp.
To address the second issue, I needed to do a little bit more. Bus stops hardly change. Sure, sometimes there is some construction or some maintenance work going on which prevents buses from arriving at a station but most of the time bus stops don't change. So why not keep them in a local database? The only drawback with that approach is that sometimes they do change. This however can easily be addressed by periodically updating the database. So putting words into actions, I downloaded the bus stop data for all bus stops from TfL into a database. Then each time I needed to request some bus arrival estimate, I asked the database for the corresponding identifier. At the same time, I issued a request to update my local bus stop database. So instead of having two consecutive API calls whereby the latter depended on the former, I now have two parallel calls which are independent of each other and enable me to get the data I need faster.
Checking out the project
I made the project available here under the MIT license. To use the app, you have to setup a TfL account and create an API key and identifier as mentioned above. There are two ways of getting key and identifier into the app. The straightforward one is to paste them into TFLRequestmanager. The alternate approach is to create an .env file in the project's root directory and add them in the following format:
I added a build step to the project which looks for the .env file and copies the keys out of this file into TFLRequestManager.
You can change the app in any way you like but mind that the use of TfL's unified API is subject to their terms & conditions.
Working on a project is different to working in a playground. It's just nice to build something which you can use later on. On top of that it helped me to solidify my Swift 3 skills. All in all, it was a fun and gratifying experience.