Build a React Native anime app
An Expo app with a search screen and a player, using the AnimeDoc API through the same small server from the React tutorial so your key never ships inside the app.
What you need
- Node.js 18+ and the Expo Go app on your phone.
- The
server.jsproxy from the React tutorial, deployed somewhere your phone can reach (or on your computer's LAN IP). - An AnimeDoc API key on a paid plan for playback; the $1 3-Day Pass is enough to try it.
npx create-expo-app anime-mobile
cd anime-mobile
npx expo install expo-videoWhy a server in between?
Anything bundled into a mobile app can be extracted, including an API key. Your app calls your server; your server adds the key. That's the same server.js from the React tutorial; its /api/stream route also handles seeking.
The app
import { useEffect, useState } from "react";
import { Button, FlatList, Image, Pressable, SafeAreaView, Text, TextInput, View } from "react-native";
import { VideoView, useVideoPlayer } from "expo-video";
const SERVER = "https://your-server.example.com"; // where server.js runs
function Player({ uri }) {
const player = useVideoPlayer(uri, (p) => p.play());
return <VideoView player={player} style={{ width: "100%", aspectRatio: 16 / 9 }} />;
}
function Watch({ item, onBack }) {
const [uri, setUri] = useState(null);
const [message, setMessage] = useState("Loading…");
useEffect(() => {
(async () => {
const detail = await fetch(`${SERVER}/api/detail?detailPath=${encodeURIComponent(item.detailPath)}`).then((r) => r.json());
const first = detail.data?.resource?.seasons?.[0] ?? { se: 0, maxEp: 0 };
const qs = new URLSearchParams({ subjectId: item.subjectId, detailPath: item.detailPath, se: String(first.se), ep: first.maxEp ? "1" : "0" });
const media = await fetch(`${SERVER}/api/sources?${qs}`).then((r) => r.json());
const source = (media.data?.downloads ?? []).find((d) => d.url && !d.vipLocked);
if (source) setUri(`${SERVER}/api/stream?url=${encodeURIComponent(source.url)}`);
else setMessage("No playable source right now.");
})();
}, [item]);
return (
<View style={{ flex: 1, gap: 12 }}>
<Button title="← Back" onPress={onBack} />
<Text style={{ fontSize: 18, fontWeight: "700" }}>{item.title}</Text>
{uri ? <Player uri={uri} /> : <Text>{message}</Text>}
</View>
);
}
export default function App() {
const [q, setQ] = useState("");
const [items, setItems] = useState([]);
const [picked, setPicked] = useState(null);
async function search() {
const res = await fetch(`${SERVER}/api/search?q=${encodeURIComponent(q)}`);
if (res.status === 429) return alert("Rate limit reached, try again soon.");
const body = await res.json();
setItems(body.data?.items ?? []);
}
return (
<SafeAreaView style={{ flex: 1, padding: 16 }}>
{picked ? (
<Watch item={picked} onBack={() => setPicked(null)} />
) : (
<>
<TextInput value={q} onChangeText={setQ} onSubmitEditing={search} placeholder="Search anime" style={{ borderWidth: 1, borderColor: "#e2e8f0", borderRadius: 8, padding: 10 }} />
<FlatList
data={items}
keyExtractor={(i) => i.subjectId}
numColumns={2}
renderItem={({ item }) => (
<Pressable onPress={() => setPicked(item)} style={{ flex: 1, margin: 6 }}>
<Image source={{ uri: item.cover?.url }} style={{ width: "100%", aspectRatio: 2 / 3, borderRadius: 8 }} />
<Text numberOfLines={2}>{item.title}</Text>
</Pressable>
)}
/>
</>
)}
</SafeAreaView>
);
}npx expo startScan the QR code with Expo Go, search, and tap a title to play its first episode.
Next steps
- Add a season and episode picker from
resource.seasons, like the React tutorial. - Add subtitles from
captionsin Media Sources. - Published apps with ads or purchases count as commercial use: use Pro or higher.