Ground truth:
@cometchat/chat-uikit-react-native@5+ catalogrn-v5.json. Docs:/ui-kit/react-native/expo-integration·expo-conversation·expo-tab-based-chat.
Companion skills (read first)
cometchat-react-native-core— install, provider chain, init→login→render. Assumed, not repeated here.cometchat-react-native-bare-patterns— the bare React Native CLI equivalent. Use that one, not this, for a non-Expo app.
Use this skill when
- the project has
app.json/app.config.jswith anexpokey, orexpoinpackage.json - "add chat to my Expo app" · "chat isn't working in Expo Go"
Prerequisites & install
⚠️ Expo Go does not work — and this is the #1 support issue
The UI Kit ships custom native modules. Expo Go is a fixed prebuilt binary that cannot load them. There is no workaround, no config flag, and nothing to fix in code. You need a development build.
Symptom: the app runs in Expo Go but chat screens crash or render nothing, with a NativeModule error
that names gesture-handler / svg / video rather than CometChat — so it reads as unrelated.
npx expo run:ios # or: npx expo run:android
Both produce a development build. From then on use that build, not Expo Go.
Packages
Same list as bare RN, plus punycode, which is Expo-only:
npm install @cometchat/chat-uikit-react-native@5 @cometchat/chat-sdk-react-native@4
npm install @react-native-clipboard/clipboard \
react-native-svg react-native-video react-native-localize react-native-gesture-handler \
react-native-safe-area-context @react-native-async-storage/async-storage@^2.1.2 dayjs punycode
Native config lives in app.json — never in ios/ or android/
This is the whole Expo/bare divergence: Expo generates the native projects, so hand-editing
Info.plist or AndroidManifest.xml is pointless — the next prebuild overwrites it. Declare
permissions in app.json and let Expo write them:
{
"expo": {
"ios": {
"infoPlist": {
"NSCameraUsageDescription": "Camera access for video calls",
"NSMicrophoneUsageDescription": "Microphone access for voice/video calls"
}
},
"android": {
"permissions": [
"android.permission.INTERNET",
"android.permission.CAMERA",
"android.permission.MODIFY_AUDIO_SETTINGS",
"android.permission.RECORD_AUDIO",
"android.permission.ACCESS_NETWORK_STATE",
"android.permission.BLUETOOTH_CONNECT",
"android.permission.VIBRATE"
]
}
}
}
BLUETOOTH_CONNECT(Android 12+) lets calls route audio to a Bluetooth headset —/calls/react-native/setuplists it; the calls SDK's own manifest only merges the legacyBLUETOOTH.- No
READ_/WRITE_EXTERNAL_STORAGE, though theexpo-integrationpage lists them: they are no-ops on modern Android (WRITE from 11, READ from 13), and the UI Kit's own manifest already merges them for older devices.
After changing app.json, rebuild — a JS reload will not pick up a native permission change.
Calling on Expo
Calling adds native WebRTC, so it needs a fresh development build too:
npm install @cometchat/calls-sdk-react-native@5
# 6 REQUIRED peer deps — the calls SDK's declared peerDependencies. Pinned; do NOT float them.
npm install @react-native-async-storage/async-storage@^2.1.2 react-native-background-timer@^2.4.1 \
react-native-performance@^5.1.2 react-native-svg@^15.12.0 react-native-url-polyfill@2.0.0 \
react-native-webrtc@124.0.7
npx expo run:ios
⚠️ react-native-url-polyfill + react-native-performance are bundle-critical (the UIKit
module-scope require()s the calls SDK, so Metro resolves its peers even in a chat-only build — a
missing one fails the bundle). Keep react-native-performance on 5.x (a floated ^6 breaks every
later npm install with ERESOLVE). @react-native-community/netinfo / react-native-callstats are
NOT declared peers — do not add them.
Simulators cannot capture camera or microphone — verify calls on a real device.
What is the same as bare RN
Everything above the native layer: the provider chain, init → login → render, every component,
theming, navigation. Only install and native config differ. Do not re-derive app code per toolchain.
Common pitfalls
- Testing in Expo Go — custom native modules cannot load. Development build, always.
- Editing
ios/orandroid/directly — prebuild overwrites it. Useapp.json. - Forgetting
punycode— Expo-only, and its absence surfaces as an unrelated module error. - Reloading JS after an
app.jsonchange — native config needs a rebuild. - Expecting calls to work on a simulator — no camera or mic capture.
Verify it works
- The app runs from a development build, not Expo Go.
npm run verify:fences:rn-v5is green.- On device: chat renders, the composer survives the keyboard, gestures respond, and — if calling is installed — the permission prompts appear on first call.