Skip to content

React Native ​

@aw-chat/react-native renders the SDK with native components only — View, FlatList, TextInput, Modal, react-native-svg. No WebView.

Install ​

bash
pnpm add @aw-chat/react-native react-native-svg react-native-fast-image
  • react-native-svg — peer (already present in Expo apps).
  • react-native-fast-image — peer. The SDK renders all images through it: it plays the animated-WebP channel emoji on iOS + Android and disk-caches remote feed photos. It uses no expo-image and adds no other native image library, so shipping SDK updates over-the-air needs no native rebuild (the host binary just has to include react-native-fast-image).

Mount ​

tsx
import { AWChat } from "@aw-chat/react-native";

export function SupportScreen() {
  return (
    <AWChat
      userId={user.id}
      sessionToken={user.token}
      serverUrl="https://chat.example.com"
      telemetry={{
        appVersion: "6.1.4",
        hardwareId: "iPhone15,2",
        osVersion: "iOS 17.4",
        deviceLang: "ru",
      }}
      fcmToken={fcmToken}
      onPickFiles={pickFiles}
      theme="light"
      locale="ru"
      onSessionExpired={refreshToken}
    />
  );
}

Render inside a flex container (e.g. SafeAreaView with flex: 1).

Attachments (onPickFiles) ​

On native there is no hidden <input type="file">. The host must pass onPickFiles or the attach control stays hidden and uploads cannot start.

Use a document picker (not photos-only) and honour opts.accept / opts.multiple so chat matches web/TMA: JPEG/PNG/GIF/WEBP/BMP, PDF, TXT, MP4. Comments pass image-only accept. Tapping a non-image chip opens/downloads it via the OS (Linking.openURL), same as web.

tsx
import * as DocumentPicker from "expo-document-picker";

onPickFiles={async (opts) => {
  const r = await DocumentPicker.getDocumentAsync({
    multiple: opts.multiple,
    copyToCacheDirectory: true,
    type: [...opts.accept],
  });
  if (r.canceled) return [];
  return r.assets.slice(0, opts.maxFiles).map((a) => ({
    uri: a.uri,
    name: a.name,
    type: a.mimeType ?? "application/octet-stream",
    size: a.size ?? 0,
  }));
}}

Return { uri, name, type, size }[]. Shared MIME lists: CHAT_ACCEPT_MIMES / COMMENT_ACCEPT_MIMES from @aw-chat/react-native (or @aw-chat/core). See Props.

Two modes (do not mix them):

  1. All-in-one <AWChat /> (default) — SDK owns an internal navigator. Fast to integrate; iOS back is an edge swipe (not the system interactive pop).
  2. Host stack — you register Support screens in your react-navigation Stack. System iOS swipe-back works. Use this when you need native gesture feel.

iPhone swipe-back behaves differently from Android?

This is the most common integration issue, and it is not an SDK bug — it is a gesture conflict. A react-navigation native stack enables the interactive swipe-back gesture by default on iOS, but not on Android.

So when <AWChat /> sits on a host Stack.Screen and you leave that gesture on, an iPhone left-edge swipe is caught by the host stack and pops the entire Support screen (chat → your app's page) in one swipe — instead of the SDK's chat → hub. Android has no host edge-swipe, so the SDK's own back runs there and it "just works". That asymmetry is exactly the "swap back works strange, different than Android" report.

Fix depends on which mode you want:

  • Staying all-in-one → turn the host gesture off on the Support screen (set gestureEnabled: false in its Stack.Screen options). The SDK's internal edge-swipe then handles chat → hub, and onClose dismisses Support at the hub. One line.
  • Want the real native iOS interactive swipe → use Host stack mode below. Each Support screen becomes its own Stack.Screen with gestureEnabled: true, so iOS pops natively — and you must wire emitSupportFocus (see below), or the native swipe will reach the hub without restoring your header/tab bar.

Do not leave the host gesture enabled over an all-in-one <AWChat />.

All-in-one (default) ​

The SDK owns internal navigation (hub → chat / ticket / complaint / channel). You do not wire a navigator for inner screens.

Use onRouteChange to hide the host tab bar / header when the user leaves the hub (name !== "hub"), so chat content can go fullscreen. The callback also fires { name: "hub" } when <AWChat /> unmounts (restore chrome). Error / welcome swaps inside the widget do not restore chrome.

Back is handled inside the SDK:

  • Android hardware / system gesture back pops overlays → inner screens → hub.
  • iOS (and Android) left-edge swipe does the same.
  • At the hub (or welcome gate), back fires onClose — pass it so the host can dismiss the Support screen instead of closing the whole app.
tsx
<Stack.Screen
  name="Support"
  options={{ gestureEnabled: false }} // disable host swipe-back while Support is open
/>

<AWChat
  onClose={() => navigation.goBack()}
  onRouteChange={({ name }) => setChromeHidden(name !== "hub")}
  /* … */
/>

Host stack (system iOS swipe) ​

Mount AWSupportProvider once above a nested Support Stack (shared store / session). Map SDK routes to your screen names via AWSupportNavigation:

SDK exportSuggested Stack.ScreenParams
AWSupportHubSupportHub—
AWSupportChatSupportChatprefill?
AWSupportTicketsSupportTicketsopenId?
AWSupportNewTicketSupportNewTicket—
AWSupportChannelSupportChannelchannelId
AWSupportSoonSupportSoontitle

Comments and complaint sheets stay SDK overlays (not stack routes). While an overlay is open, disable the host gesture or the swipe will pop the screen underneath.

tsx
import {
  AWSupportProvider,
  AWSupportHub,
  AWSupportChat,
  AWSupportTickets,
  AWSupportNewTicket,
  AWSupportChannel,
  AWSupportSoon,
  type AWSupportNavigation,
  type AWSupportRoute,
  teardown,
} from "@aw-chat/react-native";

const supportNav: AWSupportNavigation = {
  navigate: (route: AWSupportRoute) => {
    switch (route.name) {
      case "hub":
        navigation.navigate("SupportHub");
        break;
      case "chat":
        navigation.navigate("SupportChat", route.params);
        break;
      case "tickets":
        navigation.navigate("SupportTickets", route.params);
        break;
      case "newTicket":
        navigation.navigate("SupportNewTicket");
        break;
      case "channel":
        navigation.navigate("SupportChannel", route.params);
        break;
      case "soon":
        navigation.navigate("SupportSoon", route.params);
        break;
    }
  },
  goBack: () => navigation.goBack(),
};

// System swipe keeps lower screens mounted — wire focus so chrome/tab bar
// restores on pop (SDK back button already emits via the navigation bridge).
import { emitSupportFocus } from "@aw-chat/react-native";
const focus = (name: "hub" | "chat" | "tickets" | "newTicket" | "channel" | "soon") => ({
  focus: () => emitSupportFocus(name),
});

// Provider wraps the Support stack (not each screen).
<AWSupportProvider
  navigation={supportNav}
  onClose={() => navigation.goBack()}
  onRouteChange={({ name }) => setChromeHidden(name !== "hub")}
  onOverlayChange={(overlay) => setGestureEnabled(overlay == null)}
  userId={…}
  sessionToken={…}
  serverUrl={…}
  telemetry={…}
>
  <Stack.Navigator>
    <Stack.Screen name="SupportHub" component={AWSupportHub} options={{ gestureEnabled: true }} listeners={focus("hub")} />
    <Stack.Screen name="SupportChat" options={{ gestureEnabled: true }} listeners={focus("chat")}>
      {({ route }) => <AWSupportChat prefill={route.params?.prefill} />}
    </Stack.Screen>
    <Stack.Screen name="SupportTickets" options={{ gestureEnabled: true }} listeners={focus("tickets")}>
      {({ route }) => <AWSupportTickets openId={route.params?.openId} />}
    </Stack.Screen>
    <Stack.Screen name="SupportNewTicket" component={AWSupportNewTicket} listeners={focus("newTicket")} />
    <Stack.Screen name="SupportChannel" options={{ gestureEnabled: true }} listeners={focus("channel")}>
      {({ route }) => <AWSupportChannel channelId={route.params.channelId} />}
    </Stack.Screen>
    <Stack.Screen name="SupportSoon" listeners={focus("soon")}>
      {({ route }) => <AWSupportSoon title={route.params.title} />}
    </Stack.Screen>
  </Stack.Navigator>
</AWSupportProvider>

// Deep link: navigate into the stack instead of remounting with initialRoute.
// supportNav.navigate({ name: "chat" });
// supportNav.navigate({ name: "tickets", params: { openId } });

// On logout:
await teardown();

Gesture rules:

  • Hub / chat / tickets / channel / soon: gestureEnabled: true (system pop).
  • While onOverlayChange reports "comments" or "complaint": force gestureEnabled: false.
  • Wire listeners={focus(name)} on every screen. The iOS native swipe-back pops the screen itself — it never calls the SDK's goBack bridge — so without emitSupportFocus the gesture reaches the hub but onRouteChange never fires and your header/tab bar stays hidden. The SDK's own back button does not need this (it emits through the bridge); the native swipe does.
  • Do not mount <AWChat /> and AWSupportProvider for the same session.

AW Channel ​

Native feed parity with web: posts, rich text, media, reactions, views, comments, buttons. Data for posts/comments is Postgres via your backend — not Intercom (Data model).

Push (FCM) ​

Pass fcmToken to register. On logout, unmount the component (or call teardown from @aw-chat/core). Map push payloads to initialRoute + targetId when remounting.

Account switch ​

When the signed-in userId / sessionToken changes, remount <AWChat /> (or ensure props update so the live store re-initializes). Otherwise channel/comment caches from the previous user can linger for the session.

Offline ​

The SDK backs off polling on network failure and shows a session-expired panel on 401. A dedicated offline panel needs optional @react-native-community/netinfo.

Expo ​

Works with Expo (SDK 52+). examples/react-native is an Expo app with a react-native-web preview — see Try it for how to run it.

Because react-native-fast-image is a native module, running on a device needs a custom dev build (expo prebuild + expo run:ios / run:android, or an EAS dev build) — Expo Go can't load it. Only that native module is build-time; your own JS/asset updates still ship over-the-air.

AW Chat SDK — integration & platform handoff docs.