cometchat-native-bare-patterns
SkillDev toolsIntegration patterns for bare React Native CLI projects — pod install, Info.plist + AndroidManifest permissions, Apple privacy manifest, native module linking, Metro config.
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the cometchat-native-bare-patterns skill
What this skill tells your AI
The instructions your AI receives, as published by cometchat/cometchat-skills in skills/cometchat-native-bare-patterns/SKILL.md and read by ahel’s review.
Purpose
Teaches Claude how to integrate CometChat into a bare React Native CLI project. Covers:
- Installing the full peer-dependency set + native-module autolinking
pod installcadence for iOS- Editing
ios/<AppName>/Info.plistfor iOS permissions - Editing
android/app/src/main/AndroidManifest.xmlfor Android permissions - Android-specific async-storage Maven repo gotcha
- Apple privacy manifest (
ios/<AppName>/PrivacyInfo.xcprivacy) — required for App Store compliance - Wiring
index.js+App.tsxwith the provider chain
Read cometchat-native-core first (init/login/wrapper chain + anti-patterns), then cometchat-native-components, then cometchat-native-placement.
Ground truth: docs/ui-kit/react-native/react-native-cli-integration.mdx, apple-privacy-manifest-guide.mdx, react-native-conversation.mdx + react-native-one-to-one-chat.mdx + react-native-tab-based-chat.mdx, and examples/SampleApp/. Official docs: https://www.cometchat.com/docs/ui-kit/react-native/overview · Docs MCP: claude mcp add --transport http cometchat-docs https://www.cometchat.com/docs/mcp (or fetch the URL directly without MCP).
Use this skill when
- Project has
ios/andandroid/folders at the root package.jsonmainisindex.js(classic RN entry)- No
expoinpackage.jsondependencies - User says "React Native CLI", "bare RN", "ejected Expo", or "custom native modules"
Do NOT use this skill when:
- Project has
expoin dependencies +app.json/app.config.js→ usecometchat-native-expo-patterns - Project is Expo that's prebuilt into
ios/+android/folders → this can go either way. If the user's workflow is "I edit app.json and runexpo prebuild", stay on expo-patterns. If they've fully committed to bare (deletedapp.json, editInfo.plistdirectly), this skill applies.
Prerequisites
- Xcode 15+ for iOS (required for Apple privacy manifest)
- Android Studio with SDK 34+
- CocoaPods installed (
brew install cocoapodson macOS) react-native-clior@react-native-community/cliusable vianpx- React Native >=0.77 — older versions may work but are not officially supported by the UI Kit
Step 1 — Install dependencies
# Core SDK + UI Kit
npm install @cometchat/chat-sdk-react-native@^4
npm install @cometchat/chat-uikit-react-native@^5
# Natively-linked deps used by the kit + sample apps. (datetimepicker isn't a
# declared kit peer, but every shipped sample installs it — keep it for parity.)
npm install \
@react-native-async-storage/async-storage \
@react-native-clipboard/clipboard \
@react-native-community/datetimepicker \
react-native-gesture-handler \
react-native-localize \
react-native-safe-area-context \
react-native-svg \
react-native-video
# dayjs + punycode — no native code but required
npm install dayjs punycode
Bare RN uses autolinking, so no react-native link step is needed. Just confirm everything installed cleanly — if npm install errored mid-way, native modules won't be wired up correctly.
⚠️ RN 0.85+ build trap —
react-native-document-pickerv9.x breaks the Android build (ENG-35701). The unmaintainedreact-native-document-picker@9.3.1referencesGuardedResultAsyncTask, which React Native removed in 0.85.0. If the kit pulls it in transitively (or you add it for file uploads),:react-native-document-picker:compileDebugJavaWithJavacfails withcannot find symbol class GuardedResultAsyncTask. Swap to@react-native-documents/document-picker(bare RN) orexpo-document-picker(Expo):# Bare RN (the maintained fork) npm uninstall react-native-document-picker npm install @react-native-documents/document-picker cd ios && pod install && cd ..Then in code,
import { pick } from '@react-native-documents/document-picker'instead of the default export. The API surface is similar —pick({ type: ['*/*'] })returns aPromise<DocumentPickerResult[]>. Seecometchat-native-troubleshooting§3bb for the full migration matrix.
Optional — calling SDK
Only if the user's flow includes voice / video calls:
npm install \
@cometchat/calls-sdk-react-native \
@react-native-community/netinfo \
react-native-background-timer \
react-native-callstats \
react-native-webrtc \
react-native-url-polyfill \
react-native-performance \
valibot
react-native-url-polyfill,react-native-performance, andvalibotare not in the calls-sdkpeerDependenciesarray — but the calls-sdk'sdist/polyfills/browser.jsimports them at module top, so Metro fails the bundle without them. Omitting any of the three yieldsUnable to resolve module …at startup with no app render. (Validated 2026-05-26 on@cometchat/calls-sdk-react-native@5.0.0.)
WebRTC bloats the binary. Skip until the user actually wants calls.
Step 2 — iOS: pod install + Info.plist + PrivacyInfo
2a. Pod install
After every npm install of a native module (including the initial install above), run:
cd ios && pod install && cd ..
Without this, Xcode will fail to build with "module not found" errors for native classes. The warning signs:
No such module 'RNGestureHandler'during buildUndefined symbol: _OBJC_CLASS_$_RNCAsyncStorageduring linking- Build succeeds but runtime crash: "TurboModuleRegistry.getEnforcing(...): 'RNAsyncStorage' could not be found"
If pod install fails, see cometchat-native-troubleshooting § iOS pod install failures.
2b. Info.plist permissions
Open ios/<AppName>/Info.plist and add:
<key>NSCameraUsageDescription</key>
<string>Allow camera access to send photos and make video calls</string>
<key>NSMicrophoneUsageDescription</key>
<string>Allow microphone access to send voice messages and make calls</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Allow photo library access to send photos</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>Allow saving photos from chat to your library</string>
Permission-string best practice: the Usage strings show in the system prompt when iOS asks the user for permission — write them as user-facing copy, not developer notes. "Camera access for video calls" is fine; "for media upload" isn't a real reason a user would accept.
Merge, don't replace. The user may have existing permission strings for other libraries — add only what's missing, don't wipe the file.
2c. Apple Privacy Manifest — PrivacyInfo.xcprivacy
Required for App Store submission since iOS SDK 17 / Xcode 15. If it's missing or incomplete, App Store Connect rejects the upload.
Create ios/<AppName>/PrivacyInfo.xcprivacy with this exact content:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSPrivacyAccessedAPITypes</key>
<array>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>C617.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategoryUserDefaults</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>CA92.1</string>
</array>
</dict>
<dict>
<key>NSPrivacyAccessedAPIType</key>
<string>NSPrivacyAccessedAPICategorySystemBootTime</string>
<key>NSPrivacyAccessedAPITypeReasons</key>
<array>
<string>35F9.1</string>
</array>
</dict>
</array>
<key>NSPrivacyCollectedDataTypes</key>
<array/>
<key>NSPrivacyTracking</key>
<false/>
</dict>
</plist>
These 3 reason codes match what the kit's own examples/SampleApp/ios/SampleApp/PrivacyInfo.xcprivacy ships:
| API category | Reason code | What it's for |
|---|---|---|
NSPrivacyAccessedAPICategoryFileTimestamp | C617.1 | File-modified timestamps (RN bundler) |
NSPrivacyAccessedAPICategoryUserDefaults | CA92.1 | AsyncStorage (UserDefaults backend on iOS) |
NSPrivacyAccessedAPICategorySystemBootTime | 35F9.1 | Uptime for scheduling (RN + video cache) |
Optional fourth entry: if your app does explicit free-space checks before media uploads, add
NSPrivacyAccessedAPICategoryDiskSpace/E174.1too. The kit's sample doesn't ship this entry, so it isn't required for a default integration.
After adding:
- Open
ios/<AppName>.xcworkspacein Xcode - Right-click the app folder in the navigator → "Add Files to "<AppName>""
- Select
PrivacyInfo.xcprivacy— make sure "Add to targets: <AppName>" is checked - Rebuild
If the user already has a PrivacyInfo.xcprivacy, merge the 4 API types into their existing NSPrivacyAccessedAPITypes array — don't replace the whole file.
2d. Install pods after Info.plist / PrivacyInfo changes
cd ios && pod install && cd ..
Step 3 — Android: AndroidManifest + Maven repo
3a. AndroidManifest permissions
Open android/app/src/main/AndroidManifest.xml and add inside <manifest> (before <application>):
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.VIBRATE" />
<!-- Android 12 and below -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<!-- Android 13+ (API 33+) -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
Merge, don't replace. Keep the user's existing permissions for other libraries.
3b. Android: async-storage Maven repo (ONLY if you use async-storage v3+)
⚠️ Conditional — not part of the default build. The kit does NOT pin async-storage, and the shipped sample apps use @react-native-async-storage/async-storage@^2.2.0 (which needs none of this). This step applies only if you opt into async-storage v3+, whose local Maven artifact autolinking can't find by default — then ./gradlew assembleDebug fails with:
Could not find :react-native-async-storage_async-storage: on any of the paths.
Add the local Maven repo to android/build.gradle:
allprojects {
repositories {
google()
mavenCentral()
// Required for @react-native-async-storage/async-storage v3+
maven {
url = uri(project(":react-native-async-storage_async-storage").file("local_repo"))
}
}
}
Without this fix, an async-storage-v3+ Android build fails early. (On the default async-storage v2.2.0 — what the samples ship — this isn't needed.)
Known issue (F78) — chat-sdk 4.0.22 breaks bare RN at runtime. Separate from the build-time Maven error above:
@cometchat/chat-sdk-react-native@4.0.22declaresreact-native@0.64.2+ async-storage as hard deps (4.0.21 had none), so npm installs a nested duplicate react-native inside the SDK. The app builds fine but crashes at JS startup withCannot read property 'CometChatThemeProvider' of undefined(AsyncStorage native module mismatch). Expo is unaffected. Fix: add npmoverridesto dedupe, or pin@cometchat/chat-sdk-react-native@4.0.21. Full root cause + exactoverridesblock incometchat-native-troubleshooting§3bc. Tracked in ENG-35653.
3c. Android: Metro config for custom fonts or assets (if applicable)
If the project uses custom icon fonts or bundled assets, confirm react-native.config.js includes:
module.exports = {
assets: ["./src/assets/fonts/"], // only if the user has custom fonts
};
Run npx react-native-asset to link. Not required for the UI Kit itself — only relevant if the user extends with custom icons.
Step 4 — Wire index.js + App.tsx with the provider chain
4a. index.js — gesture handler FIRST
// index.js
import "react-native-gesture-handler"; // MUST be the first import
import { AppRegistry } from "react-native";
import App from "./App";
import { name as appName } from "./app.json";
AppRegistry.registerComponent(appName, () => App);
The react-native-gesture-handler import must be line 1. Not line 2. Not after React. Without it, swipe gestures on the composer and bottom sheets silently break — often only in release builds, which makes it hard to catch during development.
4b. App.tsx — provider wrapper chain
// App.tsx
import React from "react";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { SafeAreaProvider, SafeAreaView } from "react-native-safe-area-context";
import { CometChatThemeProvider, CometChatI18nProvider } from "@cometchat/chat-uikit-react-native";
import { CometChatProvider } from "./src/providers/CometChatProvider";
import { AppNavigator } from "./src/navigation/AppNavigator";
import Config from "react-native-config"; // or read from an env source
export default function App() {
return (
<GestureHandlerRootView style={{ flex: 1 }}>
<SafeAreaProvider>
<CometChatThemeProvider>
<CometChatI18nProvider>
<CometChatProvider
appId={Config.COMETCHAT_APP_ID!}
region={Config.COMETCHAT_REGION!}
authKey={Config.COMETCHAT_AUTH_KEY!}
uid="cometchat-uid-1" // dev mode only
>
<SafeAreaView edges={["top", "bottom"]} style={{ flex: 1 }}>
<AppNavigator />
</SafeAreaView>
</CometChatProvider>
</CometChatI18nProvider>
</CometChatThemeProvider>
</SafeAreaProvider>
</GestureHandlerRootView>
);
}
The provider chain matches the kit sample app (examples/SampleApp/App.tsx): CometChatI18nProvider sits inside CometChatThemeProvider, and a root SafeAreaView edges={["top", "bottom"]} wraps the navigator. The CometChatProvider itself lives in src/providers/CometChatProvider.tsx per cometchat-native-core § 6 — reuse that implementation.
Step 5 — Env vars
Bare RN has no built-in env var system. Pick one:
Option A — react-native-config (most common)
npm install react-native-config
cd ios && pod install && cd ..
Create .env at project root:
COMETCHAT_APP_ID=your_app_id
COMETCHAT_REGION=us
COMETCHAT_AUTH_KEY=your_auth_key
Read via:
import Config from "react-native-config";
const appId = Config.COMETCHAT_APP_ID;
iOS post-setup: Xcode needs to know about the .env file. Either use react-native-config's Xcode build-phase script (documented in its README) or create a .xcconfig file. Without this, Config.* returns undefined on iOS.
Option B — react-native-dotenv (@env imports, very common)
npm install --save-dev react-native-dotenv
Add to babel.config.js:
module.exports = {
presets: ['module:@react-native/babel-preset'],
plugins: [
['module:react-native-dotenv', { moduleName: '@env' }],
],
};
Create .env and consume via @env imports:
import { COMETCHAT_APP_ID, COMETCHAT_REGION, COMETCHAT_AUTH_KEY } from '@env';
⚠️ react-native-dotenv is a babel-time plugin — .env changes need a Metro cache reset (validated 2026-05-14).
The plugin reads .env at babel compile time and inlines values. Editing .env and reloading does NOT pick up new values — Metro keeps the previously bundled inline value forever. Workflow when creds change:
pkill -9 -f "react-native start"
npx react-native start --reset-cache
# react-native start does NOT run `adb reverse` like run-android does —
# restore the port forwarding manually:
adb reverse tcp:8081 tcp:8081
# Then force-stop + relaunch the app
adb shell am force-stop com.<package>
adb shell monkey -p com.<package> -c android.intent.category.LAUNCHER 1
Silent failure mode: device shows "unable to load scripts" (no adb reverse) OR app boots with old credentials (cache not cleared). Hit twice on the rn-new / rn-existing cohorts 2026-05-14.
Option C — babel-plugin-dotenv-import
Simpler but less mature. Only if the user's already picked this.
Option D — hardcoded constants (NEVER for production)
For a quick dev-mode proof-of-concept only, just declare the values as constants in a dedicated config file:
// src/config/cometchat.ts
export const CONFIG = {
APP_ID: "YOUR_APP_ID",
REGION: "us",
AUTH_KEY: "YOUR_AUTH_KEY",
};
Add src/config/cometchat.ts to .gitignore. Obviously do not commit real credentials.
Never put REST_API_KEY in the client
The REST API key is server-only. Production user management + auth token minting happens on a backend you control — see cometchat-native-production.
Step 6 — Run + verify
First run
# iOS
npx react-native run-ios
# Android
npx react-native run-android
First build takes several minutes. Subsequent runs with the Metro bundler already running are fast.
Verify
npx tsc --noEmit— TypeScript check- Open the chat screen in the simulator
- Tap the composer — keyboard should open smoothly
- Tap the "+" attachment button — action sheet should slide up (gesture handler)
- Send a message — it should appear immediately
If any of these fail, see cometchat-native-troubleshooting.
Re-run cadence
| Change | Command |
|---|---|
| JSX / TS changes | Fast Refresh handles it; r in Metro to reload |
| Added / removed a dep without native code | Restart Metro |
| Added / removed a native module | cd ios && pod install && cd .. then run-ios |
Changed Info.plist | run-ios (Xcode picks up changes) |
Changed AndroidManifest.xml | run-android |
Changed android/build.gradle | ./gradlew clean then run-android |
Hard rules
-
pod installis mandatory after every native dep change. Missing this → build fails with "module not found" or crashes at runtime with "TurboModuleRegistry could not find X". -
import "react-native-gesture-handler"is the FIRST line ofindex.js. Before React. Before any other import. Missing → swipe gestures silently break (often only in release builds). -
Android requires the local Maven repo entry for
@react-native-async-storage/async-storagev3+ (see § 3b). Without it, the Android build fails at the assembly step. -
The Apple Privacy Manifest is required for App Store submission. Skip it and your app gets rejected. Either create
PrivacyInfo.xcprivacy(§ 2c) or merge the 4 API types into an existing one. -
Merge permissions into
Info.plistandAndroidManifest.xml, don't replace. Wiping out the user's existing permissions breaks their other features. -
The four-wrapper chain is at the app root (
App.tsx), not per-screen. Re-wrapping per screen causes duplicate init + login, dropped WebSockets, and a 2-3 second flicker on first mount. -
REST_API_KEYis never in the client. Use an external backend for token minting — seecometchat-native-production. -
Every
<CometChatMessageList>must includehideReplyInThreadOptionunless you're wiring a full thread panel (seecometchat-native-placement§ Hard rule 5).
Common questions
Q: "TurboModuleRegistry.getEnforcing(...): 'RNAsyncStorage' could not be found"
Forgot pod install. Run cd ios && pod install && cd .. then rebuild.
Q: Android build fails with "Could not find :react-native-async-storage_async-storage:"
See § 3b — add the local Maven repo entry to android/build.gradle.
Q: App Store rejected the build with "ITMS-91053: Missing API declaration" Apple Privacy Manifest is incomplete. Use the full 4-API-type declaration in § 2c.
Q: Composer swipe gestures don't work in production
import "react-native-gesture-handler" isn't the first line of index.js. Move it above all other imports.
Q: CometChatUIKit.login({uid: "..."}) resolves but components don't render
You're probably rendering before init() completes. Gate rendering on CometChatProvider's isReady state — see cometchat-native-core § 6.
Q: I want to support Expo AND bare in the same codebase
Use Expo + npx expo prebuild to generate native projects on demand. Stay on cometchat-native-expo-patterns.
Skill routing reference
| Skill | When to route |
|---|---|
cometchat-native-core | Init / login / wrapper chain / anti-patterns |
cometchat-native-components | Component prop reference |
cometchat-native-placement | Where chat goes (stack / tabs / modal / bottom sheet / embedded) |
cometchat-native-expo-patterns | Expo managed workflow |
cometchat-native-bare-patterns | This skill — bare RN CLI |
cometchat-native-features | Calls, extensions, AI |
cometchat-native-theming | Theme customization |
cometchat-native-customization | Text formatters, events, custom views |
cometchat-native-production | Server-side auth tokens + user management |
cometchat-native-troubleshooting | pod install fails, build errors, missing modules, privacy manifest rejection |
Visual Builder integration (v4.3)
If the customer picks Visually in dispatcher Step 3.1, the bare RN recipe diverges from the standard provider chain. Skills runs cometchat builder export --platform react-native --json to emit src/config/{store.ts, config.json} (the Zustand-backed config store + 7-field envelope JSON), then patches App.tsx with useConfig + theme derivation.
Full recipe lives in cometchat-native-core §"Visual Builder integration". Bare RN-specific notes:
- No
npx expo install— install deps vianpm installand pin manually. The 11 explicit peer-dep list applies in full (see §"Mandatory peer deps"). - Env via
react-native-dotenvBabel plugin — the standard bare-RN convention. Add[['module:react-native-dotenv']]tobabel.config.jsplugins. Import viaimport { COMETCHAT_APP_ID, COMETCHAT_REGION, COMETCHAT_AUTH_KEY } from "@env". Add asrc/env.d.tstype declaration so TS recognizes the@envmodule. - Required additional deps:
zustand,@react-native-async-storage/async-storage, plus the standard 11 peers from §"Mandatory peer deps". useConfig(s => s.settings.style)— note the selector takesAppConfigdirectly, NOTs.config.settings.style(Finding F6, 2026-05-21).- Envelope is
settings: { chatFeatures, callFeatures, layout, style, noCode }— theme tokens live undersettings.style(there's nosettings.theme/settings.agent); the engagement feature group ischatFeatures.deeperUserEngagement(notdeeperEngagement). See core §"Feature flag access". - The reference builder app wires gesture-handler as a top-of-file side-effect import (
import './gesture-handler';), not a<GestureHandlerRootView>wrapper; its provider chain isSafeAreaProvider → SafeAreaView → CometChatThemeProvider → CometChatI18nProvider. The general bare provider chain above (withGestureHandlerRootView) is also valid — see core §"Visual Builder integration" for the reference-matched template. - iOS host: run
cd ios && pod installafter the deps land. B6 smoke validated: Metro bundles 4.6 MB iOS bundle clean on RN 0.81.5 + React 19.1.0.
Shortened here. Read the whole file on GitHub.
Signals
- GitHub stars
- 105
- Forks
- 2
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
cometchat-native-bare-patterns- Source
- github.com/cometchat/cometchat-skills