6.2 KiB
Brownfield Troubleshooting
Cross-cutting issues that apply to both the isolated and integrated approaches. For approach-specific setup, see ./brownfield-isolated.md or ./brownfield-integrated.md.
Build failures
Symptom: Gradle or Xcode build fails after a config change, dependency upgrade, or Expo SDK bump.
- Integrated approach — regenerate native projects from scratch:
Then
npx expo prebuild --cleancd ios && pod installand re-open the.xcworkspace. - Isolated approach — clear the local Maven cache and rebuild the artifact:
rm -rf ~/.m2/repository/<group>/<libraryName> npx expo-brownfield build:android npx expo-brownfield build:ios - For stubborn iOS issues, also delete
ios/build/,ios/Pods/, andios/Podfile.lock, then re-runpod install. - For stubborn Android issues,
./gradlew cleanand delete the project's.gradle/andbuild/directories.
Missing autolinked Expo modules
Symptom: Compilation succeeds but a module throws "Native module cannot be null" / "Cannot find native module 'X'" at runtime.
- Install with
npx expo install <package>rather than plainyarn add—expo installpicks the version compatible with the current SDK. - After installing a new module, rebuild the native app. Autolinking runs at native build time, not at JS bundle time.
- For the isolated approach, you must re-run
npx expo-brownfield build:android|iosafter adding a module, and republish/re-embed the new artifact.
Metro connection
Symptom: "Could not connect to development server" / red screen on launch in debug.
- Ensure the device or emulator can reach the dev machine. The Android emulator can talk to the host via
10.0.2.2; physical devices need a reachable LAN IP. - For physical Android devices on USB:
adb reverse tcp:8081 tcp:8081. - Confirm Metro is actually running:
npx expo startfrom the Expo project (oryarn startfrom the workspace root). - Verify the debug
AndroidManifest.xmlenables cleartext traffic — Android 9+ blocks HTTP by default. The debug variant should includeandroid:usesCleartextTraffic="true"on<application>, or anetwork_security_configallowing the dev server. - iOS simulator: Metro should be reachable at
localhost:8081. If it is not, check that ATS exceptions are still in place inInfo.plistforlocalhost(the Expo template ships this by default).
iOS XCFramework signing (isolated approach)
Symptom: App launches but immediately crashes with "Library not loaded" or codesign errors during archive.
- Every xcframework produced by
build:iosmust be set to Embed & Sign in the app target's Frameworks, Libraries, and Embedded Content section. On SDK 56+ this is five frameworks:{TargetName}.xcframework,React.xcframework,ReactNativeDependencies.xcframework,ExpoModulesJSI.xcframework, andhermesvm.xcframework. On SDK 55 it's two:{TargetName}.xcframeworkandhermesvm.xcframework. Missing any of them is a common cause of runtime crashes. - The frameworks must be added to the app target, not a framework or extension target.
- Prefer the Swift Package output (
build:ios --package) — it links every bundled xcframework through one aggregate product, so you cannot forget one.
iOS architecture / simulator mismatch
Symptom: "Building for iOS Simulator, but the linked library was built for iOS" or "Undefined symbols for architecture arm64".
- The XCFramework includes both device and simulator slices. If a slice is missing, rebuild on the missing platform. The
expo-brownfield build:ioscommand produces both by default. - On Apple Silicon simulators, do not set
EXCLUDED_ARCHS = arm64for the simulator configuration — Apple Silicon simulators requirearm64. The classic Rosetta-only exclusion is no longer correct.
Android mavenLocal() not found (isolated approach)
Symptom: Gradle reports "Could not find com.example:mybrownfield:1.0.0" even after a successful expo-brownfield build:android.
mavenLocal()must be declared underdependencyResolutionManagement { repositories { ... } }insettings.gradle.kts, not the deprecated top-levelallprojects { repositories { ... } }block. The deprecated form is silently ignored whendependencyResolutionManagementis present.- Confirm the artifact actually landed in
~/.m2:find ~/.m2/repository -name "mybrownfield*" - Verify the
groupandlibraryNamein the consumer's dependency line match what the plugin config emitted.
Module name mismatch
Symptom: The native view loads but renders a blank screen, with "Application 'X' has not been registered" in the JS logs.
- The
moduleNamepassed toReactNativeViewController(moduleName: "main")(iOS) or returned fromgetMainComponentName()(Android) must equal the name passed toAppRegistry.registerComponent("main", () => App)in the JS entry point. - The default Expo template registers
"main". If you changed the registration, update every native call site.
Monorepo: autolinking can't find the Expo project
Symptom: Gradle or CocoaPods fails resolving Expo modules even though they are installed.
- Android (integrated): set
root = file("../../my-project")(or the correct relative path) inside thereact { ... }block inapp/build.gradle, and explicitly set the project root insettings.gradlebeforeexpoAutolinking.useExpoModules(). - iOS (integrated): set
:app_pathinuse_react_native!to the absolute path of the Expo project root. Optionally passEXPO_PROJECT_ROOT=/abs/pathtopod install. - Confirm
node_modules/is installed at the workspace root (yarn installfrom the monorepo root, not from the Expo project subdirectory).
After upgrading Expo SDK
If the brownfield setup stops building after an SDK upgrade:
- Re-run
npx expo install --fixin the Expo project to align native module versions. - Rebuild the artifact (isolated) or run
npx expo prebuild --clean(integrated). - Compare the new
templates/expo-template-bare-minimumfor the target SDK against your customized native files — Expo occasionally changes Gradle plugin names, Podfile helpers, or AppDelegate entry points across SDKs.