I build FitStreak solo: a workout-habit app for iPhone, with a home-screen widget and an Apple Watch app. The app itself is React Native + Expo. The widget, the Watch app, its complication and the Shortcuts intents are native Swift targets that share data with the app through App Groups, via a small local native module (WidgetData) that writes to the shared container and pushes payloads to the Watch over WatchConnectivity.
For several releases, the widget and the Watch received nothing on real devices. No crash, no visible error. And every signal said things were fine.
Everything was green
- The EAS build passed.
- Code signing and App Store validation passed.
- The module's class was inside the IPA.
- Locally, every Swift target compiled.
The actual cause
The module's podspec sat at the module root. React Native's autolinking found it and compiled the pod into the app. But Expo's module resolve step only searches subdirectories, so the module was never written into the generated ExpoModulesProvider.swift.
So requireNativeModule('WidgetData') threw on every real launch, our JS caught it, and every widget write, WidgetCenter reload and WatchConnectivity send became a silent no-op.
The fix was one line in expo-module.config.json:
{
"platforms": ["apple"],
"apple": {
"podspecPath": "WidgetData.podspec",
"modules": ["WidgetDataModule"]
}
}
We also added a Jest test that runs Expo's real resolver against the repo and fails if the module isn't registered, so this can't silently regress.
How it was finally found
Not from any build output. From the phone's unified log:
sudo log collect --device-udid <hardware UDID from `xcrun xctrace list devices`>
log show --predicate 'process == "wcd"' system_logs.logarchive
An app with a working WCSession shows up as a wcd client within seconds of launch. Ours never did. That was the thread to pull.
The lesson
Compiling, signing and passing App Store validation are three separate gates, and none of them proves a native module is registered. If you ship native targets or local modules alongside Expo, test registration, not just compilation. And when a native feature is "silently doing nothing", go to the device log before you trust any green checkmark.
FitStreak, if you're curious: guided daily workouts with step-by-step demos, 30-day programs, streaks where a planned rest day doesn't break the streak, chair and low-impact options, and a companion pet that grows with your streak. Free on the App Store, with an optional subscription: https://apps.apple.com/app/id6759306186
Top comments (0)