Improved commuting with Expo and React Native
Content Breakdown
- Discovering Expo
- Why Expo?
- Inspiration now arriving at Platform 1!
- A lesson in UI design ā The goggles, they do nothing!
- Deciding to rewrite ā Short-lived success
- We can rebuild him. We have the technology.
- The story of UI and UX ā The epiphany
- Further UI psychology ā Data now arriving at Platform 3!
- Creating an API for the app ā A Bigger Boat
- Creating my own dataset ā Euston, we have a problem
- Building the app for myself with Turtle-cli!
- Conclusion and future plans!
My name is Scott Pritchard, and Iām a Javascript developer in my late 20ās based in the north of England. Iāve been coding since around 2006 in my own time, creating projects that range from setting up and adapting a CMS and forum, building a desktop hardware monitor for Windows, building a live chat platform for a major outsourcing company and working on sportsbooks for some big names in the industry.
Today, Iām going to tell you how I built my own commuting app and API to optimize my daily commute to and from work. First Iāll cover how I discovered Expo, my inspiration for the project and my first attempt at the project. Then, Iāll cover some key lessons I learned about user behaviour and UX.
The project Iāll be discussing today is called Panther Commute (available on the Google Play Store for Android, and the Apple App Store for iOS).(At the time of writing, the app only supports mainland UK train stations and times)
Discovering Expo
Day to day, I work as a full stack developer but primarily focus on front-end projects including websites, web apps, and mobile development. It was June 2018 when I first heard about Expo.
Iād mentioned to one of my colleagues that Iād been trying to get started with React Native for my own projects but had trouble setting up react-native-maps due to the native parts of it. It was at this point that he mentioned Expo, briefly explaining some key facts about it. That evening, I had my task ā learn more about Expo.
Why Expo?
In addition to the native parts of adding libraries, I discovered that Expo had more to it than just libraries. Sure, the libraries were a big part of it, but the workflow it allowed made things easier. I didnāt need to compile the app on my system, I just had to write JS, let expo handle the bundle and compilation.
On top of this, I didnāt need any native libraries that werenāt included with Expo. It gave me all the libraries I felt I needed. Even to this day, Iāve not found any native library that I absolutely need thatās not included with Expo. Your mileage may vary, but Iām a strong advocate for the platform because it simplifies the development process allowing me to focus on the key part ā developing my projects.
Iād worked with mobile a little in the past through the Delphi Firemonkey framework. However, now that I work in the world of Javascript, that was no longer an option, and the costs for Delphi are exorbitant. If you think buying Apple hardware and maintaining developer licenses is expensive, that pales in comparison to the cost of even a single year Delphi architect license at over Ā£4000 GBP (even their lowest level professional license costs in excess of Ā£1200). In addition to this, the community around it has receded into near-hiding, and is even more disliked and dreaded than PHP.
Inspiration now arriving at Platform 1!
The project Iād been wanting to work on was a train times app. The options that were out there didnāt quite offer things in the way I wanted them. I wanted information quickly, with minimal navigation needed.
It was also a perfect project for learning React Native ā lots of data, doing my own styling, navigation, all the goodies. Commuting apps do exist of course, but I found that there was always something that took more steps than it needed or were less than optimal for 1-handed usage.
My end intention was to provide users with the ability to view nearby train stations, view departures and arrivals at stations, bookmark stations and services, plan journeys, and view where a service was in as few taps as possible and without having to hand-juggle their device.
I had more than a few reasons for doing this. Firstly, I commute using a high powered electric scooter for the ālast mileā parts of my journey (a minimum of 3.6 miles per day, but often in excess of 9 miles) and take this on the train for the bulk of the journey. I need to know which platform to be at to ensure I can get a spot on the train and to prevent me from having to go in and out of lifts more than necessary. It weighs almost 30KG so stairs arenāt an option.
Secondly, knowing if my train is delayed and the reason why lets me prepare for the rest of the evening, and even to let relevant parties know if Iām going to be late. After all, āIām going to be lateā looks bad, moreso if you canāt provide a reason.
Finally, it allows me to know if I can stay in bed a few minutes more in the morning. If my train is going to be late, no sense leaving the house early and sitting in the cold for 20 minutes more than needed.
Now that I had a project, plenty of reasons, and a framework to build with, I could begin. To cut a long story short, within 6 weeks, Iād released my first React Native app, called āPantherā, to the Play store. It made use of TransportAPI as itās data source. Iāve glossed over this part ā this is because it was the first version of the app, and thatās not the primary focus of this article.
The goggles - they do nothing!
Considering Panther was my first project, I thought Iād made a good impression. I was amazed that Iād produced something that run on mobile without workarounds or wrapping a webapp in a webview. Looking at it now, itās amazing to me that I like this UI and thought it was easy to use.
Believe me, the bottom navbar was meant to be blue like the header. Before you ask, you are seeing double navigation of tab navigator with tabs inside. I think at one point in development, I had another level of tabs inside one of the screens.
Remember, this was after 6 weeks of learning a framework where I had only a little bit of previous experience (due to a project Iād been assigned) and having only learned React ~3 months prior. I didnāt even use maps in this version of the project in the end so Expo might not have been needed.
With my hand on my heart, I can say without a doubt that the UI is a retina-searing mess of almost-neon.
It didnāt last long
It was only around 2 weeks after releasing this that I decided to rebuild. Iāve waited for trains that have been delayed longer than that!
Iād learned enough about RN and had enough willingness to rebuild the app avoiding the mistakes Iād made the first time around. Around the same time, I moved from Android (Huawei Mate 10 Pro) to iOS (iPhone XS ā which had only just been released) for day to day usage.
The project had bugs. The āsplash screenā was scaled incorrectly (I put that in quotes because it wasnāt technically a splash screen in the way youāre meant to make them), scaling was way off, and of course there were the notch-issues. It had buttons and icons which did things but didnāt provide user feedback. Plus, it was bright purple. It worked, but it wasnāt what I had envisioned at all. You could say it was a project of exploring what was possible.
I could have updated the project to work with iOS, but I wanted to do things differently.
We can rebuild him. We have the technology.
Thus, in the middle of September 2018, it was decided. It would be rebuilt from the ground up. Iād go with proper theming, using the brand-colours approach with primary, secondary and tertiary colours rather than āwell, letās use gradients everywhereā. Iād limit colours to ensure consistency, and make sure that my icons were all consistently styled.
Within a few days, Iād created a basic rebuild that would lead me on a 6 month journey:
Black and Yellow. Not the colours I ever expected Iād put together for a UI but it turned out to work perfectly. I realized that this provided the dark UI that was much easier on the eyes and using a contrast colour which was less likely to interfere with sleep.
It had support for bus stops and train stations. It had 3 tabs, a hero unit (of sorts) to display information about a stop, and a header to allow displaying of the list icon. The layout works and it remained this way for a very long time. For the next few months, right up until December 2018, I kept this design.
Did I change it? I had to. Why did I change it? Usability and UX. The top right corner isnāt accessible, but itās how you access a list of stations instead of the map view. The map itself is on the edge of the thumb stretch zone. Thereās also information that really isnāt relevant to the user such as the stop number and geographical coordinates. Sure, they might want this information, but mostly it was just present without the majority of users caring about it.
Lesson #1 ā More data in your UI is not always better.
By the end of November, more features had been added, but the UI was still similar;
Those buttons still took up lots of space so theyāre touchable, but that icon in the top left was still inaccessible. The tabs had text and icons on them. Itās good, but thereās still so much more that can be done.
Day to day, I work with a truly world-class team of developers and designers, and Iād picked up a few key design tips. Slowly these were materializing, but I hadnāt realized it.
The epiphany
The christmas holidays began, and I had plenty of time to actually sit down and design something better. I realized something ā Iād been assuming that the app users wouldnāt understand what an icon did.
Thereās psychology behind this ā people donāt naturally read first before taking an āautomaticā action. Take a look at the problem we have with doors and door handles. Even if a door says āpushā, if it has a handle on the same side as the sign, people will instinctively try to pull the door open. Their reflex action is much quicker than their brain processing the action of reading a sign, understanding it, and adapting their bodies action in response.
Labels are overrated and overused. An icon can represent your intention just as well while reducing space usage. There are well established icons which represent common tasks, such as geolocation, bookmarks, information, etc. Minimizing the processing the brain has to do allows users to easily navigate through your app.
Lesson #2 ā Icons and labels can both serve the same end result, but do so in vastly different levels of effectiveness.
Those huge buttons had to go. They may fit the theme, but they didnāt fit the usability criteria I had in my mind. The button in the top right was a mess on other devices, being out of alignment and even completely broken on some of them. The map needed to be the primary focus, and I should make use of the areas surrounding the notch.
The final lesson ā Make the primary purpose of a screen easily evident. If itās a full screen feature like a map, adapt your UI around that to make it as easy to use as possible.
Thus, I set about refactoring the UI, and eventually ended with what is the current design;
The selected station name is clearly presented at the top. The header area has an overlay near it so that the clock and status icons are readable. The icons in the area above the tab bar represent what they do (timetable, bookmark toggle, station information). The timetable icon could be represented with several other options, but this represents that itās a searchable list.
The tab buttons are just icons now, which represent what they do. There was room for UX improvement even here, not just by removing the labels. For example, if you press the left-most tab icon when itās active, itāll scroll the map to your current position.
Youāll notice that the focused tab has a different colour and an āoverlineā. I feel that it is imperative that users with visual disabilities are able to access the app as easily as possible. If a user is colourblind, they can still identify the active tab from the overline. At one point in development, I also adjusted the size of the focused icon to make it more prominent, but decided against this as it would break the UI on some devices.
The second tab along is the list of stations. If you press that when itās the active tab, itāll focus the search bar thatās in that screen. This prevents the need to stretch to the top of the screen. These 2 icons also change when theyāre the active tab;
This is to indicate that the functionality has changed. To reinforce this, is an available behaviour, youāre presented with an overlay the first time you visit the search tab telling you that you can press the tab again to focus the search input box.
All of this was done with Expo, without detaching, and without any trickery. It started in Expo SDK 29, but was upgraded to SDK32 the same evening it became available (primarily in preparation for future plans).
I didnāt need to do anything complicated to make the app work. I didnāt need to deal with native linking, or cocoapods (trust me, Iāve dealt with them in projects Iāve been assigned, and Iām not a fan due to the spectacular way things can fail and the incompatibilities between native libs).
Data now arriving at platform 3
What about train services though? So far, weāve covered the first thing users see, but the app has more to it than that. Letās take a look at the services screen;
The first thing that stands out is the layout. Expanding on the rule of not assuming users are stupid, consistency is key. The left side always contains times, the right side always contains platform, and the center always contains the station and other key information. If a train platform is yet to be confirmed, the platform will display as āTBCā.
You can see that it shows some departed services. What I found is that it was more difficult for me to identify if Iād missed a train if it had simply disappeared from the list, so I opted to show a small selection of recently departed services. This will be split into itās own section in the list in a future release.
We can see that each service has a lot of information. You can see the name of the destination of the train, the list of stops (which is a marquee), the platform, the scheduled departure time, and in case of delay, also the expected departure time. There is a lot of logic involved in all of this, ranging from the colours of each item to the list of stops to display, to the platform, and the times.
The next thing you might notice is the search bar. Itās at the top, and thus not 1-handed accessible. This is an issue that I couldnāt quite solve in time for release, but Iām aiming to fix in a future release.
Hereās another example of that screen with different data;
As you can see, thereās a lot of delays and cancellations here due to an incident but the information layout is consistent and easy to read. āCancelledā is reiterated but thatās because I consider it a key piece of information that users absolutely need to know about.
Since taking this screenshot, the delayed services also show which stations the service will stop at. You see, even though I know my regular train services by heart, I realized I might want to get on a different train that stops at a specific station before the destination. Also, if one of my trains departs from a different platform to itās usual one, then I can identify it much more easily even if itās cancelled.
To put perspective on the logic of this screen;
- Is the service delayed or cancelled?
- Is the estimated time the same as the scheduled time?
- Is the platform TBC or known?
- Has the service already departed?
- Does the service have a reason for being delayed or cancelled?
- Is the service going to stop at only a selection of stations or will it stop at all of them along itās route?
All of this logic has to work together seamlessly, and one piece of data or logic should not have a direct effect on any other ā they all have to be calculated independently to ensure accurate display of information.
Designing this page and the logic took around 1 week of casual work integrating the basics, and 3 solid days of work integrating the logic and a design that worked with all that data in a user-friendly way.
A bigger boat
Around the same time as Iād been refactoring the UI, I discovered that Iād been overusing the TransportAPI service, resulting in throttled service. This proved to be a big problem as it meant that not only was I potentially causing problems for other users of the service, but I wasnāt able to quickly load information in the app when I needed it the most. I hadnāt thought Iād been overusing it, but the throttling was apparent and so I took it as a strong hint that I should look at another solution.
As I have a background in node.js development and had been itching to build a server-side project for a long time, I jumped at the opportunity to build my own rail API.
Thatās right; I built my own API to go with this app, using the official services provided by National Rail (a few systems, one of which is known as Darwin). Firstly, by moving to their systems, Iām given several million api hits every 4 weeks. This represents more than a 16000% increase compared to Transport API.
The downside to Darwin ā it uses SOAP and WSDL, something that doesnāt integrate nicely with Node (compared to JSON). Thankfully, I found that a library had been made only a short time before to handle this for me which allowed me to focus on parsing the data.
This also opened up many more possibilities. I could finally format data as I wanted it, do my own data correlations, provide support for incidents (e.g. maintenance work), etc.
Most importantly, I could optimize for speed, data, or different endpoints to choose between speed or data, and build the service as I grow.
Euston, we have a problem
I found myself with a data predicament still. See, when I was using TransportAPI, they provided accurate GPS locations for stations and a comprehensive list of stations within a radius. The data-set for these was impossible to find on itās own, especially when combined with other information. I had to resort to using a mix of data in JSON, XML and CSV formats from 6 different sources and merging it together. Each data set had different fields. Compiling the information into a single consistent data-set was a huge task and took around 4 days. By this point, we were into 2019 and work schedule was about to resume.
By completing this step, I could share this data with both my app and my API. This allowed Panther to have offline-first support for station information. I could cross-reference data in both places to make both projects better. I still found that some stations were missing. It was at this point I adapted my API to request missing information from the official data sources. If a user viewed a list of departures at a station, and a train service was passing through a station that wasnāt recognised, it would request data for that station and update the API data source. After a few days, it had gathered information for around 60 more stations than Iād compiled.
Building the app for myself
One interesting development in recent times is that the expo team released Turtle CLI. This tool allow me to build the apps on my mac mini. Combined with the ability to export the bundle, Iāve been able to setup my own mini CI process using only NPM commands and webhooks. You could use a true CI tool of course, but I have no need for anything that complex right now.
By building it on my own machine, I can go from starting bundle compilation through to being uploaded to Testflight in around 8 minutes. Add 4 more minutes and I also have an Android build ready to go.
Conclusions and future plans
This project taught me how to refine my development process and truly understand what UX is really about. Despite taking 6 months to release, Iāve got a much better app than I would have if Iād released in October like Iād planned to do so originally. It taught me some of the key pillars of UX, allowed me to build an API to overcome some serious limitations, and took me through many of the pages in the expo documentation.
Panther is available already, but the development doesnāt stop. My next plan is to add support for TFL (Transport For London) to allow London Underground users to see timetables and stations too. This will require new UI elements and some data wizardry. Fixing up the UI issues surrounding search, and reinforcing the one-handed support, are also on the agenda.