Customers

Fieldy

How Fieldy cut its AI wearable app down to size with Expo

Scale

Startup

Platform

iOS, Android

A BLE-heavy app with no room for bloat

Fieldy builds an AI wearable assistant: a physical Bluetooth device paired with an app that handles audio transfers, generates summaries, and takes actions on the user's behalf. That combination puts constant pressure on performance. Because the app leans on Bluetooth Low Energy and background processes to stay connected to the wearable, the team watches RAM usage and app size closely. The operating system is always looking for an excuse to kill a background task, so every extra megabyte is a liability, not just a nice-to-have to trim later.

That protectiveness is why Fieldy had never seriously looked at Expo. The team wasn't opposed to it, they just hadn't used it, and they carried a specific worry: that a managed layer on top of their heavy native requirements would add bloat or block them from reaching native APIs when they needed to.

An outside push from a part-time hire

The shift started when Fieldy brought on Tomasz Sapeta, introduced by their investors at Inovo.vc, initially part-time. He suggested moving to Expo. The team was skeptical about the overhead an additional framework layer might introduce, but they trusted his judgment enough to hand over the repository and let him work.

Auditing the bundle with Atlas

Rather than bolt on new safeguards, the team ran a full audit of what was already shipping. They set their start command to run Expo Atlas by default, a bundle analysis tool included with Expo that shows a visual breakdown of what's contributing to JS bundle size, and inspected every entry in package.json for real usage.

Atlas surfaced three redundant packages in a single pass: react-native-calendar-events, kept around for permissions checks the app no longer needed; react-native-calendars, used only for a date picker but pulling in the entirety of lodash; and react-native-date-picker, used only for a time picker. The team deleted all three, replaced them with a small custom date picker component, and saved 10% of the whole JS bundle while removing three native dependencies in the process.

Removing react-native-calendars didn't fully solve the lodash problem. Atlas showed the app was still carrying roughly 700KB of lodash because the code imported the whole library instead of individual functions. The same pattern showed up with date-fns. Switching both to direct, per-function imports, combined with the deleted packages, cut over 2MB from the JS bundle. Between the package removals and the import fixes, the app went from 19.8MB to 17.8MB.

A separate look at native build assets turned up a bigger issue: SF-Pro.ttf and other bundled fonts adding up to 18.6MB. SF Pro is the system font on iOS, so it doesn't need to ship inside the app at all, and bundling Apple's system font on Android made no sense either. Deleting the fonts dropped the app binary by nearly 5MB.

Standard fetch and streaming instead of native workarounds

File uploads and streaming had historically been the painful part of React Native for Fieldy's team, requiring a stack of proprietary libraries to do what's straightforward on the web. Expo SDK 54 added WinterCG compliance to native apps, meaning native fetch behaves like web fetch. That let the team drop react-native-fs and RNBlobUtil, which they had used to write files to disk and wrap them in blob utilities for multipart audio uploads. Now they pass a standard File object from expo-file-system directly to fetch.

Streaming responses saw the same simplification: instead of manually tracking string indices with XHR, the app now uses a standard ReadableStream.

Continuous Native Generation and a 40% faster build

Getting there meant adopting Continuous Native Generation, which regenerates the ios/ and android/ folders from app.config.js on every build instead of keeping them committed to git. Native code becomes a build artifact rather than a source of truth the team edits by hand. The team wasn't initially sold on the reasoning, EAS Workflows required it to automate deployments, so they adopted it out of necessity. It paid off in a concrete way: when something breaks in the native layer, the fix is usually one command, bun expo prebuild --clean, instead of debugging stale native state.

Config Plugins let the team modify that regenerated output. One example: a plugin that adjusts gradle.properties to strip x86 architectures from the build, which sped up builds by 40%.

EAS Workflows, Expo's CI/CD service, handles building, submitting to the app stores, and pushing JavaScript-based OTA updates, all triggered by git pushes. Because EAS fingerprints native dependencies, it can skip a full native build and push a JS update instead when nothing native has changed, so most of Fieldy's deployments take minutes rather than the usual 30-minute build followed by days of app store review.

Writing the BLE layer natively with Expo Modules

A persistent Bluetooth connection to the wearable is core to Fieldy's product, and the JS thread isn't reliable enough to guarantee it in the background: the OS will kill it to save battery, and Headless JS is effectively abandoned. After trying react-native-ble-plx and react-native-ble-manager and finding neither reliable enough, the team wrote their entire BLE layer in native Swift and Kotlin using Expo Modules, which let JS and native code call into each other directly.

They used the same approach for app termination handling. When a user force-quits Fieldy, the app needs to warn them that recordings will stop, but JavaScript is already dead by that point. An AppDelegate subscriber wired through Expo Modules handles it.

A deployment mistake, and the process fix that followed

Fieldy runs separate development and production app variants with different bundle IDs, which RevenueCat uses to verify subscriptions. A local deployment using eas update picked up a developer's local .env file, set to APP_VARIANT=development, and pushed that configuration to the production channel. Production users ended up talking to RevenueCat with development keys, the paywall went down, and the team lost revenue while users were left confused.

The fix was procedural: stop deploying from local machines entirely. All releases now go through EAS Workflows, which sets environment variables automatically based on the build profile, removing the chance of a laptop's local config leaking into a production push.

“A common concern with Expo is that it locks you out of native code. It doesn't. Expo Modules let you write Swift and Kotlin alongside your JS — and the two layers talk to each other cleanly.” (Adomas Valiukevičius)

Sources

From skeptic to convert: how Fieldy adopted Expo for their AI wearable (2026-03-24)

Explore Expo for your team

The framework behind thousands of companies.

Get started for free