Cookbooks · In your product
A call screen in a React Native app
A screen in an Expo or React Native app that puts the person holding the phone on a call with your agent, with mute and hang up. The server side is the same as for a web page; only the client changes.
Before you start
- An agent, and its ID. The Quickstart makes one.
- A workspace API key in the
CRACKLE_API_KEYenvironment variable. See Authentication. - A route on your backend that starts a call and returns
livekitUrlandtoken. The one in A talk button for your website will do. - An Expo app you can make a development build of. Calls use native audio code, so they do not run in Expo Go.
Install LiveKit's React Native packages
Terminalnpx expo install @livekit/react-native @livekit/react-native-webrtc livekit-client \ @livekit/react-native-expo-plugin @config-plugins/react-native-webrtc expo-keep-awakeAdd the two config plugins and say why the app wants the microphone. The plugins do the native setup when the app is built. LiveKit’s own guide at docs.livekit.io covers a bare React Native project, where that setup is done by hand.
app.json{ "expo": { "plugins": ["@livekit/react-native-expo-plugin", "@config-plugins/react-native-webrtc"], "ios": { "infoPlist": { "NSMicrophoneUsageDescription": "Allow the microphone so you can talk on a call." } }, "android": { "permissions": ["android.permission.RECORD_AUDIO"] } } }Terminalnpx expo prebuild npx expo run:ios # or run:androidRegister the WebRTC globals
This has to run once, before anything creates a room, so it goes in the app’s entry file.
index.jsimport { registerGlobals } from '@livekit/react-native'; import { registerRootComponent } from 'expo'; import App from './App'; // Once, before any room is created: this gives React Native the WebRTC pieces a browser has built in. registerGlobals(); registerRootComponent(App);Build the call screen
The screen starts the phone’s audio session, asks your backend for a call, and hands the result to
LiveKitRoom, which connects, publishes the microphone and plays the agent. There is no audio element to attach, as there is in a browser.CallScreen.jsximport { useEffect, useState } from 'react'; import { Button, Text, View } from 'react-native'; import { AudioSession, LiveKitRoom, useRoomContext } from '@livekit/react-native'; import { useKeepAwake } from 'expo-keep-awake'; export default function CallScreen({ onDone }) { const [session, setSession] = useState(null); const [error, setError] = useState(''); useKeepAwake(); // a call that stops when the screen dims is a dropped call useEffect(() => { let cancelled = false; (async () => { try { await AudioSession.startAudioSession(); // Your own backend, which calls POST /v1/calls with the API key. const response = await fetch('https://your-app.com/api/call', { method: 'POST' }); if (!response.ok) throw new Error('The call could not be started.'); const body = await response.json(); if (!cancelled) setSession(body); } catch (err) { if (!cancelled) setError(err.message); } })(); return () => { cancelled = true; AudioSession.stopAudioSession(); }; }, []); if (error) return <Text>{error}</Text>; if (!session) return <Text>Connecting…</Text>; return ( <LiveKitRoom serverUrl={session.livekitUrl} token={session.token} connect audio video={false} onDisconnected={onDone} > <InCall /> </LiveKitRoom> ); } function InCall() { const room = useRoomContext(); const [muted, setMuted] = useState(false); const toggleMute = async () => { await room.localParticipant.setMicrophoneEnabled(muted); setMuted(!muted); }; return ( <View> <Button title={muted ? 'Unmute' : 'Mute'} onPress={toggleMute} /> <Button title="Hang up" onPress={() => room.disconnect()} /> </View> ); }
Try it
Run the development build on a real phone if you can: that is where the microphone, the earpiece and the lock screen behave as they will for the people using your app. Open the screen, allow the microphone, and the agent greets you. Hanging up disconnects the room, which ends the call with the reason user-left.
What a phone adds
| Situation | What to do |
|---|---|
| The screen locks or dims mid-call | Keep it awake for the length of the call, as useKeepAwake does above. |
| The app goes to the background on Android | Run a foreground service of the microphone type for the call, or Android stops the microphone. Google Play asks you to declare it. |
| The app goes to the background on iOS | Turn on the audio background mode for the app. |
| The person picks the earpiece or the speaker | Choose the output with AudioSession.selectAudioOutput. |
| The network changes or drops | onDisconnected fires. Show that the call ended and offer to call again. |
Live captions and the agent’s state arrive the same way as on the web, on the goblins-transcript topic: see Web and mobile clients. React Native does not always include TextDecoder; if yours does not, add a polyfill before decoding the messages.