Question: I want to create a chat UI that receives token by token like ChatGPT (stream data). But React Native does not support stream response. How to do it?
Ideal: we use react-native-webview
to invoke API that can receive stream data
Thanks to the author of the react-native-chatgpt library
Step by step
- Create API that simulates stream data
- Test API with your browser
- Create react-native app that receives stream data like ChatGPT
Step 1. Create API that simulates stream data
"""
# filename: main.py
# To install
pip install fastapi
pip install "uvicorn[standard]"
# To run
uvicorn main:app --reload
"""
import asyncio
from fastapi import FastAPI
from starlette.responses import StreamingResponse
from time import time
app = FastAPI()
@app.get("/")
def read_root():
return "Test stream"
async def generate_data():
for i in range(5):
yield f"message {i}\n"
await asyncio.sleep(1)
@app.get("/stream")
def stream():
return StreamingResponse(generate_data())
Step 2. Test API with your browser
- Open your browser
- Access link http://127.0.0.1:8000
- Open console and run code below
(async()=>{
const response = await fetch('http://127.0.0.1:8000/stream', {
method: 'GET',
responseType: 'stream',
});
async function *streamAsyncIterable(stream) {
const reader = stream.getReader()
try {
while (true) {
const {done, value} = await reader.read()
if (done) {
return
}
yield value
}
} finally {
reader.releaseLock()
}
}
for await(const chunk of streamAsyncIterable(response?.body)) {
const str = new TextDecoder().decode(chunk);
console.log(new Date().toISOString(), str)
}
})()
Step 3. Create react-native app that receives stream data like ChatGPT
Install libs
npm i react-native-webview
App.js
import React, { useRef, useState } from "react";
import { StatusBar } from "expo-status-bar";
import { Button, StyleSheet, Text, View } from "react-native";
import { WebView } from "react-native-webview";
export default function App() {
const [message, setMessage] = useState("");
const webviewRef = useRef();
const getStreamData = () => {
setMessage("");
const injectScript = `
(async()=>{
const postMessage = window.ReactNativeWebView.postMessage;
const response = await fetch('http://127.0.0.1:8000/stream', {
method: 'GET',
responseType: 'stream',
});
async function *streamAsyncIterable(stream) {
const reader = stream.getReader()
try {
while (true) {
const {done, value} = await reader.read()
if (done) {
return
}
yield value
}
} finally {
reader.releaseLock()
}
}
for await(const chunk of streamAsyncIterable(response?.body)) {
const str = new TextDecoder().decode(chunk);
postMessage(new Date().toLocaleTimeString() + " " + str);
}
})()
`;
webviewRef?.current?.injectJavaScript(injectScript);
};
return (
<View
style={{
flex: 1,
backgroundColor: "#d2d2d2",
textAlign: "center",
paddingVertical: 120,
}}
>
<WebView
ref={webviewRef}
style={{
height: 80,
width: "100%",
}}
source={{ uri: "http://127.0.0.1:8000/" }}
onMessage={(event) => {
const data = event.nativeEvent.data;
setMessage((prev) => prev + data);
}}
/>
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
<Text>{message}</Text>
<Button title="Press me" onPress={getStreamData}></Button>
</View>
</View>
);
}
Top comments (5)
Is there anyway to do it without webview?
You should go straight with webview.
Cause later we need to use some format engine line Mathjax.
Webview is good approach for now :D
Also. React native does not support streaming response.
You need to install the community customization for that.
Yes, you’re right, but using a web-view feels really pointless. It not only takes away the native feel of the app, but also doesn’t seem like a practical approach at all. Especially when there’s such a hype around AI in recent times, it’s hard to believe that React Native hasn’t addressed this yet…
If you're using expo, the SDK 52 is support fetch streaming. But the mathjax or katex are still the problem.
Wow thats perfect, at least there is a step :D