So, I’m getting back to work on the next big feature, which is to allow the user to ignore specific train lines. I wasn’t 100% sure how to actually give the user an intuitive way to set this, but I’ve decided to build on my idea in my last post.
I’ve set the modal to have 2 ‘tabs’
the favorites tab
the ignore tab
My hope is that the user understands that each of the lines is a clickable object that will set that specific train to be ignored or not.
Each train line image is rendered as a <TouchableOpacity> which sets the state for each individual line to toggle a boolean. The opacity of the image rendered is influenced by the value of the bool:
I’ve clicked on the C and the F trains.
I’m pleased with this implementation so far. I still feel confused as to how to deal with the Franklin Avenue Shuttle and the Grand Central/Times Square Shuttle, but for now, I’ll leave that be.
The next steps:
save this information to UserDefaults so your preferences remain saved to your device
automatically load these preferences on open
include this information in the api call to the back end
set up the back end to be able to receive this array
change the redis search to disregard ignored lines
clean up some of the margins/padding etc.
I think this is a pretty exciting new feature and I’m sure people will use it. I personally find myself needing an F train in manhattan and when I open HustleTime and see two incoming M trains, I’m disappointed.
I’m actively working on this app, so if anyone has any suggestions for features that aren’t on my Trello Board please comment and let me know of a feature you’d like to see (and yes, I know people are hoping for buses).
You can get HustleTime on both Android and iPhone free!
In my last post, I talked a little about the occasional WireType error, and of course, over the past weekend, the BDFM feed was issuing corrupt data for over an hour.
I don’t have a real answer to how to deal with this sort of situation. The data I’m getting from the MTA lacks the necessary entity which contains arrivals and departures, so I don’t really have a choice but to not serve information for (in this case) the BDFM lines.
I also need to implement changes that allow for when a WireType error occurs. I started detailing this last week, but I think I finally have a temporary solution.
How I’m working this all out:
There are undoubtedly many ways to safely test code, especially when you have code in production. In my case, we’re starting to pick up a little steam:
just a little steam…
It’s just under 100 users! and that’s Android alone, on the AppStore, I’ve had about 60 installations in the last week. At the very beginning stages of getting an app ‘out there,’ I feel it’s important not to alienate our early adopters by breaking the working code. If I was in a vacuum, I could of course shut the whole thing down and make fixes. Not right now – even with my small group of 100-200 users.
What I’m doing is spinning up a local version of my back end and seeding my own redis manually – the actual server seeds redis (which stores all the MTA data) every minute with an active crontask.
To test- I’ll simulate an api call from a device with Postman a super simple way to manually make api calls to wherever!
HustleTime is looking for a latitude and longitude among other things, so I’m sending that information to my local server with postman and checking the results.
I was asking myself how I would simulate a WireType error, and I decided to ping the MTA api, and then manually muddle up the data. Fortunately for me, When I called the MTA’s 12345 feed, I got a WireType error off the bat! Here’s what the response looks like from the MTA when a WireType error occurs:
When I try to decode this message with Protobuf, it throws the WireType error that I’m so concerned about – so the first step will be to enact some error handling with WireType in mind.
For any given feed: the first beginnings of this look like this:
errors = []
.
.
.
begin
feed123456S = Transit_realtime::FeedMessage.decode(data123456S)
rescue Protobuf::InvalidWireType
errors = errors + 'skipped: 123456S (wireType) '
# Do something
end
once that decoding is done, I need to run my getArrivals(lineFeed, timeNow, data_array) method on each feed, and if there’s that WireType error, ruby will throw yet another error. So I need to either add a conditional in that method, or add some more error handling. I’m going to add some more error handling:
begin
getArrivals(feed123456S, time_now, data)
rescue
puts "did not seed 123"
# Do something
end
At this point I have the structure to handle the errors I’m seeing. Now I just actually need to do something about it. My DataHelper.rb file has some class methods including a retrieve_data function as such:
def self.retrieve_data
if @@data.length == 0
@@data = JSON.parse(@@redis.get(@@redis.get(@@data_key)))
end
@@data = JSON.parse(@@redis.get(@@redis.get(@@data_key)))
end
This basically grabs the data in redis and parses it. Since I’ve required DataHelper.rb into my CronHelper.rb file, I simply have to call the function and then sift through that data and grab all the data that pertains to the line that’s having the WireType error. It looks like this:
station_arrivals is the array that stores all the MTA and is what’s pushed into redis, so I’m basically calling all the old data, finding all the relevant arrivals and pushing it into station_arrivals to send back and save to redis.
Looking forward to further testing this and deploying to production in the next day or two!
Occasionally the MTA sends corrupt or incomplete data. It’s particularly frustrating for me because of the structure of how I’ve built HustleTime.
The MTA sends transit data through multiple GTFS feeds, the subway system (and SIRR) are covered by 9 individual feeds. My code looks a little like this:
As you can see, I’m touching each of these endpoints in one file, and assigning each feed to a variable which is then parsed by protobuf:
begin
feed123456S = Transit_realtime::FeedMessage.decode(data123456S)
rescue Protobuf::InvalidWireType
errors = errors + 'skipped: 123456S (wireType) '
end
What’s happening here is that a few times a week a feed starts sending corrupt data, and returns an InvalidWireType error. I’m catching this error and storing the error in an errors variable. The problem with this method is that when all the various feeds are agglomerated, if one feed received an error, that entire feed will be omitted from the station_arrivals object that is being sent to redis.
This all means that if there’s an error, all the old data is rewritten in Redis without the offending line at all- and this isn’t a great way to approach this since there is actually data that I could use.
Imagine HustleTime is seeding normally. The MTA sends about 1 hour of future arrivals when you hit their api. So if I am pinging the BDFM feed, I’ll see predictions for then next hour of arrivals at all stations along the Orange line. When you encounter a WireType error, all that old data would be rewritten, so I really need a way to go into the old data and grab those numbers, and splice them into the new data when an error is caught.
Remember, this only happens on occasion, but my good friend BanjoCat was good enough to set up some monitoring for me so I can see when it’s happening, and to be honest, it’s happening more than I would like. So I’ve shelved Ignoring trains to work on this issue next. I’m developing a few methods now that do what I need and I’m currently testing. Hopefully I can avoid WireType blackouts in the future.
You can get HustleTime on both Android and iPhone free!
The next big feature I’ve been meaning to add to HustleTime is a way for users to ignore specific train lines.
Let’s say you’re on lower level at West 4th street station in manhattan, and you need to take the to Brighton Beach but all you’re seeing in the HustleTime Ui is other trains that are of no use to you. This feature will allow you to ignore trains so that you’ll only see the train you need.
How can I accomplish this?
At it’s most basic level the Front end will need to send something to the back end and the back end will need to send the requested information sans the ignored stations. It will also be necessary to save the ignored lines into both state and the phone memory, so that a user will not need to select ignored trains every time they open the app. We also need to make sure the back-end is backwards compatible with prior versions without the ignore feature. Finally, we need a way for the user to actually select the stations they hope to ignore.
How to get started is always tricky for me: I never know where to start. As HustleTime gets more complex, it will become more challenging to decide how to begin modifying all the various moving parts – and I’m sure as I become more experienced as a developer, this process will become easier and easier.
Real Estate
The screen on a mobile device has very limited real-estate. I have a modal built into HustleTime already, and that shows you your saved favorite places.
Where can I add ignored lines??
With the limited amount of space, I think it will probably be best to display the ignore options in a separate tab of the menu. Something like this…
There, I’ll put checkboxes for the various lines, each checkbox will toggle a boolean value in state for each of these lines. I’ll then JSON.stringify that object when I save it to phone memory or send it to the back-end. In tomorrow’s post I’ll have something to show in regards to my progress on this feature.
As I push forward with new iterations of HustleTime, I wanted to create a blog to detail my progress, perhaps it will foster creativity, engagement with my small (but growing) audience, and / or simply serve to document my progress.
I am only a few days removed from my Android release, a painstakingly difficult process that led me down many dead ends, and eventually rebuilding the app in a completely different code repo.
The draw and selling points of React-Native eventually ended up failing me. Now that I’ve created the app, I can say that there are still a lot of advantages to building in React-Native, but it’s never as simple as it seems.
An example is in NSUserDefaults (Ios) and DefaultPreferences (android):
As you can see, the code is very similar, but one library can take an array, and the other forces me to use a string. It’s little things like this, coupled with various dependency issues – particularly how gradle dependency versioning works that really slowed me down.
I hope to blog more about how I improve the product in the future as well as a write a retrospective on my journey in future blog posts.