Last year, Flexport launched a new venture called Transmission — a suite of mobile and web applications for trucking carriers to manage their fleets. Now that the mobile apps have been live in production for over a year, we’d like to share our development experience with the broader community, including a deep dive into a few internal tools powered by Expo JavaScript bundles and app manifests.

Truck drivers can manage hours of service logs and driver vehicle inspection reports (DVIRs).

Project goals

A typical Transmission mobile user is a drayage truck driver who picks up shipping containers from the Port of Long Beach and delivers them to warehouses in Los Angeles. They use the app to share delivery status updates as well as manage their hours of service to comply with the 2017 electronic logging device (ELD) regulation.

While building the app, we optimized for these things:

Engineering velocity : Our number one goal was to build quickly. When we started making the app, the trucking team only had five engineers, and we were already stretched thin building web tools for internal operations and external trucking carrier office workers. The product requirement was to release the app in a few months to meet the ELD mandate deadline.

: Our number one goal was to build quickly. When we started making the app, the trucking team only had five engineers, and we were already stretched thin building web tools for internal operations and external trucking carrier office workers. The product requirement was to release the app in a few months to meet the ELD mandate deadline. Native-app feel : Our business and product stakeholders requested a listing in the app store, offline support, smooth rendering, and the use of native UI components. We didn’t want it to feel like a website, and we didn’t want a throwaway prototype — we wanted something we could invest in for the long-term.

: Our business and product stakeholders requested a listing in the app store, offline support, smooth rendering, and the use of native UI components. We didn’t want it to feel like a website, and we didn’t want a throwaway prototype — we wanted something we could invest in for the long-term. Support for Android & iOS : With most carriers, drivers use their own personal phones for Transmission instead of, say, company-provided tablets. So we needed to support both platforms from day one.

: With most carriers, drivers use their own personal phones for Transmission instead of, say, company-provided tablets. So we needed to support both platforms from day one. Access to hardware features: Though not needed for the initial release, we knew that in later iterations we’d need photo uploading, offline support, push notifications, and background GPS tracking.

Drivers provide delivery status updates to their dispatchers, who see all mobile updates in the Transmission web app.

Choosing a mobile tech stack

At first, we were unsure whether to write native mobile apps or use a cross-platform solution. Some of us had been burned by shipping slow mobile web apps instead of fully embracing mobile native. But on the other hand, Flexport’s product surface area is growing quickly as the business expands, so we are keen to adopt tools that increase engineering leverage.

React Native

To inform the decision, we built a prototype app with React Native and Expo during an October 2017 hackathon. It proved to us both (1) that the performance of React Native was indistinguishable from native code for our relatively simple use case and (2) that engineers with no mobile experience could be productive within hours.

A few other factors made React Native a good fit. First, Flexport’s web apps are all built with React, so the team had relevant experience and existing JavaScript infrastructure. Second, we were starting a greenfield app, so we didn’t need to integrate with a legacy native codebase. Third, none of us had production mobile native experience or an attachment to a particular platform. Fourth, Flexport’s engineering culture is full-stack: we try to empower engineers to own their features end-to-end within a given business domain. Finally, Transmission drivers are business partners rather than consumers. It’s much more important to them that our app helps them do their job than that it makes use of bleeding edge iOS features or has perfect animations.

Expo

Expo is a toolchain for developing and deploying cross-platform mobile apps. While React Native’s killer feature is using the same codebase for both iOS and Android, Expo’s killer feature is deploying certain mobile app updates without going through the stores. Expo also has great APIs to abstract away platform-specific complexity, simple integration with services like Sentry and Amplitude, and excellent documentation.

The Expo team and community are quite active too. We met in person with the Expo team a few times in 2018 and have an ongoing partner Slack channel with them. In our experience, they are very much a “listen-to-your-customer” type of organization, per their Canny feature request completions. Overall, we love Expo — it has dramatically increased our team’s leverage.

Using Expo for internal tooling

Expo’s app manifests and JavaScript bundles are powerful mechanisms for managing mobile app changes. We’ve built several workflow tools on top of them.

Mobile sandbox

Flexport’s web sandbox is an internal tool for previewing product changes. Given a git branch, it spins up versions of our main web applications running at HEAD with a dedicated production-like database. Anyone at Flexport can access the sandbox via a custom URL. Each time new code is pushed to the branch, the sandbox instance is automatically refreshed. It’s a great mechanism for gathering feedback from other teams and non-technical stakeholders.

Naturally, we wanted a similar workflow for mobile changes. So we integrated the sandbox system with Expo. When a developer enables the “mobile” option for their sandbox instance, we create a JavaScript bundle pointing to the sandbox backend instead of the production backend. We then publish it to an Expo release channel dedicated to that sandbox instance.

Product managers, designers, and other engineers can use a QR code to open that version of the mobile app in the Expo client app on their phones. Each time the git branch is updated, a new JS bundle is deployed. It makes collaborating on mobile pull requests quite seamless!

Example Transmission mobile pull request. Product managers can scan the QR code to open the changes in the Expo client app on their phone.

JavaScript bundle deployment process

We have two versions of the mobile app on each platform: release candidate (RC) and production. The apps have separate Expo projects and separate listings in the stores, with RC only available via TestFlight and the Play Store beta testing program. All versions use the production backend GraphQL endpoint. Our engineers and product managers have the two apps installed side-by-side for testing.

Production icon on the left, release candidate icon on the right. Other than the icon colors and the code version, the two apps are identical.

For the release candidate, every hour a new JavaScript bundle is automatically cut from our master branch and published to Expo via a Buildkite pipeline. For production, the process is similar, except that new JavaScript bundles must be deployed manually. Deploys at a given commit on master can be triggered with a single button click on Buildkite.

This approach lets us approximate the continuous integration and deployment we have for Flexport’s main web applications, with the difference that we must manually deploy new prod JS bundles. Since there are costs to each new bundle deployment (increased cold boot time and mobile data consumption), we like having more control with this manual step. Below are our Buildkite pipelines for RC and Prod, with the RC deploy getting automatically kicked off shortly after 10am.

The Buildkite pipelines for release candidate and production bundle deployment. (The webpack pipeline is unrelated.)

For the vast majority of our improvements to the mobile app, a simple JavaScript bundle deployment is all that’s needed. We go through the hassle of publishing a new binary version through the app stores only when we need to upgrade Expo or request new device permissions. We do this about once per quarter.

Mobile upgrade prompts

Each time we deploy a new production mobile app, we automatically store a copy of the application manifest in our database as part of the Buildkite pipeline. We later use this history on the client side to either prompt or force users to upgrade their apps. This is useful because old binaries with an earlier version of the Expo SDK don’t receive the latest JS bundles updates. So to ensure users get the latest features, we needed a mechanism to ensure they upgrade.

On our backend, we configure in code which versions we want users to use.

Using the same system, we also lightly prompt users to get the latest JavaScript bundle when a new one is published. For the time being the app is evolving quickly, so we prioritize making sure everyone has the latest version over avoiding interrupting their workflows.

Outcome

Transmission mobile is a success! On the product side, it’s used by drivers at half of Flexport’s top ten trucking carrier partners. Delivery status updates from the mobile app are instant, compared to conventional industry updates that take hours or even days to notify customers about the status of their freight.

“It’s simple, and I know exactly what to do every time I pull it up.” — feedback from a driver at Wilmac Enterprises

On the engineering side, we’ve found that Expo and React Native deliver on their promises of seamless cross-platform development. The codebase is manageable: we have about 20k lines of code and 25 screens. We haven’t needed to write any native code, and we only have two React Native components with platform-specific definitions ( DatePicker and CountryPicker ). For device access, we use in-app GPS to help drivers find nearby trucks and the camera to let them upload “Proof of Delivery” documents.

We’ve had between zero and five engineers working on the app at any given time. Engineers can move fluidly between the backend, web, and mobile — this lets us scale up and down mobile investment sprint-by-sprint based on product requirements. The team relocated to Chicago in January, and we’ve been able to ramp up new hires quickly. One of our engineers, Paul Scheid, came from writing low-level code at a high-frequency trading firm to shipping non-trivial app features within days.

Transmission driver Leroy at Eastern Drayage!

There have been a few minor hiccups worth noting. In April 2019, our Android app was removed from the Play Store for about 12 hours because Google blocked an automatically bundled Expo module that we didn’t use. The Expo team responded quickly and we were able to resolve the issue the same day. Another issue is that we haven’t found a great way to get high-quality scans from photos of documents without ejecting from Expo, which would disrupt our workflows. There’s an open Expo canny.io issue, so we’ll see. (Star it if you’re into it!)

What’s next

A concern we had about React Native and Expo was that they’d work well early on, but that’d we’d eventually outgrow them in a production environment. That hasn’t happened for us. Flexport is starting to explore new use cases for greenfield mobile apps, and our plan is to invest further in React Native and Expo, hopefully reusing some of the infrastructure we created to support Transmission mobile.

And if you found this post interesting — you guessed it — we are hiring engineers in San Francisco and Chicago. We’d love to hear from you!

Thanks to Derek Li, Yingfu Xiong, Ethan Goldberg, Jonathan Taratuta-Titus, Yan Li, Desmond Brand, and many others for their contributions to this project.