Connecting an AI model to a chat screen takes relatively little code. Building a reliable frontend requires more: protecting credentials, handling interrupted responses, managing conversation history, and keeping the interface responsive.
React Native adds mobile-specific challenges. Users switch apps, connections drop, and long answers can compete with scrolling and keyboard interactions.
For applications integrating OpenAI, Anthropic’s Claude, and Google Gemini, a practical architecture separates the interface, application backend, and provider-specific adapters.
Why Should AI Requests Go Through a Backend?
For standard requests using a company’s provider API key, the React Native application should communicate with an authenticated backend.
OpenAI advises developers against exposing API keys in browsers or applications. Google similarly recommends a backend proxy to protect Gemini credentials in client-side applications.
An environment variable does not protect a secret if the build process includes its value in the application bundle.
React Native’s security documentation also identifies AsyncStorage as unencrypted. Secure device storage is appropriate for sensitive application credentials, such as user session tokens, but it does not solve the problem of distributing a shared provider key.
The backend should authenticate users, verify conversation access, enforce usage limits, validate input, and select an approved model. A proxy that forwards arbitrary requests without these checks remains vulnerable to abuse.
How Do OpenAI, Anthropic, and Gemini Differ?
All three providers support streaming, but their request structures and response events differ.
| Provider | Example integration | Adapter responsibility |
|---|---|---|
| OpenAI | Responses API with streaming enabled | Interpret typed response events |
| Anthropic | Messages API and TypeScript streaming helpers | Separate text updates from other content events |
| Gemini | Google Gen AI SDK’s generateContentStream | Extract text and preserve relevant completion metadata |
OpenAI documents server-sent event streaming. Anthropic provides streaming helpers and asynchronous event iteration. Google documents streamed text generation through its Gen AI SDK. These are available integration paths rather than exhaustive API lists.
A backend adapter can translate these differences into a stable application contract. The frontend then handles consistent events without embedding provider-specific logic throughout its components.
However, successful completion, output limits, refusals, and failures should remain distinguishable.
Define a Shared Event Contract
The following TypeScript example illustrates an application-owned protocol for a text-based assistant:
type EventIdentity = {
generationId: string;
messageId: string;
sequence: number;
};
type ChatEvent = EventIdentity & (
| { type: "text_delta"; text: string }
| {
type: "finished";
reason: "complete" | "limit" | "blocked";
}
| {
type: "failed";
code: string;
retryable: boolean;
}
);The generation ID separates individual attempts. The message ID identifies the assistant message being updated. Sequence numbers can support duplicate detection.
These fields do not automatically enable reconnection. Resumable delivery also requires server-side event persistence and a replay mechanism.
Applications supporting tools, images, or audio will need additional event types rather than forcing every output into text.
How Does Streaming Work in React Native?
Streaming displays partial output before generation finishes. It improves perceived responsiveness without necessarily reducing total generation time.
Expo’s official documentation demonstrates streaming with expo/fetch and response.body.getReader(). Developers should check their installed SDK’s documentation rather than assume every React Native networking implementation behaves identically.
A request to the application backend might start like this:
import { fetch } from "expo/fetch";
async function openChatStream(
apiUrl: string,
token: string,
conversationId: string,
text: string,
signal: AbortSignal
) {
const response = await fetch(`${apiUrl}/chat`, {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
Accept: "text/event-stream",
},
body: JSON.stringify({ conversationId, text }),
signal,
});
if (!response.ok || !response.body) {
throw new Error(`Stream failed: ${response.status}`);
}
return response.body;
}This snippet opens the stream. It does not implement parsing, token refresh, or reconnection.
Network chunks are not complete events. One event can span multiple reads, while a single read can contain several events. Calling JSON.parse() on each raw chunk is therefore unreliable.
The transport must decode bytes incrementally and parse the framing protocol. An SSE parser should handle event boundaries and multiline data. Incremental decoding must also preserve UTF-8 characters split across chunks.
Production proxies must permit incremental delivery; response buffering can make a working stream appear non-streaming.
How Should React Manage Partial Responses?
A single loading flag cannot represent an AI request’s lifecycle.
The interface should distinguish waiting, streaming, completion, interruption, cancellation, and failure.
If connectivity disappears midway through an answer, preserve the received text and mark it as interrupted. Presenting partial output as complete can mislead users.
Generation IDs help prevent another common bug: late events from a cancelled request modifying a newer response. The state layer should reject events associated with the wrong generation or message.
A stop action should cancel the client transport and communicate cancellation to the backend, which should abort upstream generation where supported. Closing a screen alone does not establish that generation stopped.
For longer-running jobs, an explicit cancellation endpoint may provide clearer control than relying only on connection closure.
How Can Long Answers Stay Responsive?
Updating the entire conversation for every small fragment creates unnecessary rendering work.
A practical implementation can accumulate fragments briefly and apply updates in small batches. The batching interval should come from device profiling rather than an assumed universal value.
Historical message components should remain stable while the active response changes.
For long conversations, FlatList provides virtualization. React Native’s documentation explains the trade-offs between memory consumption, rendering coverage, and responsiveness when configuring lists.
Markdown rendering also deserves attention. Re-parsing an expanding answer after every fragment can become expensive. Lightweight rendering during generation, followed by richer formatting after completion, is one implementation option.
Automatic scrolling should respect reading position. When someone scrolls upward, new output should not repeatedly move them to the latest message.
Where Should Conversation History Live?
The visible transcript and the model’s input context have different purposes.
The interface may display the full conversation while the backend selects relevant history within the application’s context and cost limits.
An application-owned record should preserve message IDs, generation status, provider selection, and metadata required for future requests. Once tools or attachments are involved, storing text alone may be insufficient.
The backend should retrieve authorized history instead of treating a client-submitted transcript as authoritative.
Switching providers also needs explicit handling. Provider-specific tool results, file references, and conversation identifiers are not automatically portable because the frontend looks identical.
How Should Retries and Costs Be Controlled?
Retries should account for whether output has already reached the user.
Before generation starts, a transient failure may justify a bounded retry. After partial output appears, restarting can produce a different answer. The interface should show a new attempt instead of silently appending it to the incomplete response.
An application request ID can help the backend detect duplicate submissions and avoid starting the same job twice.
Useful measurements include time to first visible text, total response duration, interrupted generations, provider-reported usage, and cost per completed task. Character counts should not substitute for token accounting.
Before release, test network loss, cancellation, app backgrounding, authentication expiry, long responses, and conversation switching during generation.
A reliable React Native AI frontend makes request state understandable, keeps provider secrets on the backend, and separates provider behavior from interface logic. That foundation supports additional models without requiring a new chat architecture each time.





















Add Comment