Getting Started

Build AI chat interfaces for iOS and Android with @assistant-ui/react-native.

Quick Start

The fastest way to get started with assistant-ui for React Native.

Create a new project

npx assistant-ui@latest create --example with-expo my-chat-app
cd my-chat-app

Configure API endpoint

Create a .env file pointing to your chat API:

EXPO_PUBLIC_CHAT_ENDPOINT_URL="http://localhost:3000/api/chat"

Start the app

npx expo start

Manual Setup

If you prefer to add assistant-ui to an existing Expo project, follow these steps.

Install dependencies

npx expo install @assistant-ui/react-native @assistant-ui/react-ai-sdk

Setup Backend Endpoint

Create a backend API route using the Vercel AI SDK. This is the same endpoint you'd use with @assistant-ui/react on the web:

app/api/chat/route.ts
import { openai } from "@ai-sdk/openai";
import { convertToModelMessages, streamText } from "ai";

export const maxDuration = 30;

export async function POST(req: Request) {
  const { messages } = await req.json();
  const result = streamText({
    model: openai("gpt-4o-mini"),
    messages: await convertToModelMessages(messages),
  });
  return result.toUIMessageStreamResponse();
}

This is the same backend you'd use with @assistant-ui/react on the web. If you already have an API route, you can reuse it as-is.

Set up the runtime

hooks/use-app-runtime.ts
import { useChatRuntime, AssistantChatTransport } from "@assistant-ui/react-ai-sdk";

const API_URL = process.env.EXPO_PUBLIC_API_URL ?? "http://localhost:3000";

export function useAppRuntime() {
  return useChatRuntime({
    transport: new AssistantChatTransport({
      api: `${API_URL}/api/chat`,
    }),
  });
}

Use it in your app

Wrap your app with AssistantRuntimeProvider and build your chat UI:

app/index.tsx
import {
  AssistantRuntimeProvider,
  useAuiState,
  useAui,
} from "@assistant-ui/react-native";
import type { ThreadMessage } from "@assistant-ui/react-native";
import {
  View,
  Text,
  TextInput,
  FlatList,
  Pressable,
  KeyboardAvoidingView,
  Platform,
} from "react-native";
import { useAppRuntime } from "@/hooks/use-app-runtime";

function MessageBubble({ message }: { message: ThreadMessage }) {
  const isUser = message.role === "user";
  const text = message.content
    .filter((p) => p.type === "text")
    .map((p) => ("text" in p ? p.text : ""))
    .join("\n");

  return (
    <View
      style={{
        alignSelf: isUser ? "flex-end" : "flex-start",
        backgroundColor: isUser ? "#007aff" : "#f0f0f0",
        borderRadius: 16,
        padding: 12,
        marginVertical: 4,
        marginHorizontal: 16,
        maxWidth: "80%",
      }}
    >
      <Text style={{ color: isUser ? "#fff" : "#000" }}>{text}</Text>
    </View>
  );
}

function Composer() {
  const aui = useAui();
  const text = useAuiState((s) => s.composer.text);
  const isEmpty = useAuiState((s) => s.composer.isEmpty);

  return (
    <View
      style={{
        flexDirection: "row",
        padding: 12,
        alignItems: "flex-end",
      }}
    >
      <TextInput
        value={text}
        onChangeText={(t) => aui.composer().setText(t)}
        placeholder="Message..."
        multiline
        style={{
          flex: 1,
          borderWidth: 1,
          borderColor: "#ddd",
          borderRadius: 20,
          paddingHorizontal: 16,
          paddingVertical: 10,
          maxHeight: 120,
        }}
      />
      <Pressable
        onPress={() => aui.composer().send()}
        disabled={isEmpty}
        style={{
          marginLeft: 8,
          backgroundColor: !isEmpty ? "#007aff" : "#ccc",
          borderRadius: 20,
          width: 36,
          height: 36,
          justifyContent: "center",
          alignItems: "center",
        }}
      >
        <Text style={{ color: "#fff", fontWeight: "bold" }}></Text>
      </Pressable>
    </View>
  );
}

function ChatScreen() {
  const messages = useAuiState(
    (s) => s.thread.messages,
  ) as ThreadMessage[];

  return (
    <KeyboardAvoidingView
      style={{ flex: 1 }}
      behavior={Platform.OS === "ios" ? "padding" : "height"}
    >
      <FlatList
        data={messages}
        keyExtractor={(m) => m.id}
        renderItem={({ item }) => <MessageBubble message={item} />}
      />
      <Composer />
    </KeyboardAvoidingView>
  );
}

export default function App() {
  const runtime = useAppRuntime();

  return (
    <AssistantRuntimeProvider runtime={runtime}>
      <ChatScreen />
    </AssistantRuntimeProvider>
  );
}

What's Next?