In this blog, we will learn how to create a React Native audio and video calling app using Agora.
Introduction
Create an audio and video calling app in React Native using Agora to improve communication. Follow this guide for step-by-step instructions and code examples.
Implementation
Now we will implement the app, step by step.
Setting Up Your Project with React Native
To set up the environment, check out our previous blog, Getting Started With React Native, where we walk through the entire setup process.
Set Up Agora SDK
1 |
npm install react-native-agora |
Click on “Agora” to learn how to create a project in your Agora account and obtain the App ID and temporary token.
Set Up Navigation
For set-up navigation use React Navigation.
Create the App Components
We’ll start by creating a helper file named helper in the src/helper folder.
1. helper
The getPermission
function handles camera and audio permissions on Android using PermissionsAndroid.requestMultiple
.
1 2 3 4 5 6 7 8 9 10 |
import { PermissionsAndroid, Platform } from 'react-native'; export const getPermission = async () => { if (Platform.OS === 'android') { await PermissionsAndroid.requestMultiple([ PermissionsAndroid.PERMISSIONS.RECORD_AUDIO, PermissionsAndroid.PERMISSIONS.CAMERA, ]); } }; |
2. VideoCall
The Video Call screen allows users to join or leave a channel, toggle between Host and Audience roles, and view live video streams. It uses Agora’s SDK for real-time video and event handling.
Here’s a basic example of what your index.tsx might look like:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 |
// Import React Hooks import React, { useRef, useState, useEffect } from 'react'; // Import user interface elements import { SafeAreaView, ScrollView, Text, View, Switch } from 'react-native'; import { Platform } from 'react-native'; // Import Agora SDK import { createAgoraRtcEngine, ChannelProfileType, ClientRoleType, IRtcEngine, RtcSurfaceView, RtcConnection, IRtcEngineEventHandler} from 'react-native-agora'; import { getPermission } from '../../helper/helper'; import styles from './styles'; // Define basic information const appId = 'app_id'; const token = 'temp_token'; const channelName = 'test'; const uid = 0; // Local user Uid, no need to modify const VideoCall = () => { const agoraEngineRef = useRef<IRtcEngine>(); // IRtcEngine instance const [isJoined, setIsJoined] = useState(false); // Whether the local user has joined the channel const [isHost, setIsHost] = useState(true); // User role const [remoteUid, setRemoteUid] = useState(0); // Uid of the remote user const [message, setMessage] = useState(''); // User prompt message const eventHandler = useRef<IRtcEngineEventHandler>(); // Implement callback functions useEffect(() => { // Initialize the engine when the App starts setupVideoSDKEngine(); // Release memory when the App is closed return () => { agoraEngineRef.current?.unregisterEventHandler(eventHandler.current!); agoraEngineRef.current?.release(); }; }, []); // Define the setupVideoSDKEngine method called when the App starts const setupVideoSDKEngine = async () => { try { // Create RtcEngine after obtaining device permissions if (Platform.OS === 'android') { await getPermission(); } agoraEngineRef.current = createAgoraRtcEngine(); const agoraEngine = agoraEngineRef.current; eventHandler.current = { onJoinChannelSuccess: () => { showMessage('Successfully joined channel: ' + channelName); setIsJoined(true); }, onUserJoined: (_connection: RtcConnection, uid: number) => { showMessage('Remote user ' + uid + ' joined'); setRemoteUid(uid); }, onUserOffline: (_connection: RtcConnection, uid: number) => { showMessage('Remote user ' + uid + ' left the channel'); setRemoteUid(0); }, }; // Register the event handler agoraEngine.registerEventHandler(eventHandler.current); // Initialize the engine agoraEngine.initialize({ appId: appId, }); // Enable local video agoraEngine.enableVideo(); } catch (e) { console.log(e); } }; // Define the join method called after clicking the join channel button const join = async () => { if (isJoined) { return; } try { if (isHost) { // Start preview agoraEngineRef.current?.startPreview(); // Join the channel as a broadcaster agoraEngineRef.current?.joinChannel(token, channelName, uid, { // Set channel profile to live broadcast channelProfile: ChannelProfileType.ChannelProfileCommunication, // Set user role to broadcaster clientRoleType: ClientRoleType.ClientRoleBroadcaster, // Publish audio collected by the microphone publishMicrophoneTrack: true, // Publish video collected by the camera publishCameraTrack: true, // Automatically subscribe to all audio streams autoSubscribeAudio: true, // Automatically subscribe to all video streams autoSubscribeVideo: true, }); } else { // Join the channel as an audience agoraEngineRef.current?.joinChannel(token, channelName, uid, { // Set channel profile to live broadcast channelProfile: ChannelProfileType.ChannelProfileCommunication, // Set user role to audience clientRoleType: ClientRoleType.ClientRoleAudience, // Do not publish audio collected by the microphone publishMicrophoneTrack: false, // Do not publish video collected by the camera publishCameraTrack: false, // Automatically subscribe to all audio streams autoSubscribeAudio: true, // Automatically subscribe to all video streams autoSubscribeVideo: true, }); } } catch (e) { console.log(e); } }; // Define the leave method called after clicking the leave channel button const leave = () => { try { // Call leaveChannel method to leave the channel agoraEngineRef.current?.leaveChannel(); setRemoteUid(0); setIsJoined(false); showMessage('Left the channel'); } catch (e) { console.log(e); } }; // Render user interface return ( <SafeAreaView style={styles.main}> <Text style={styles.head}>Agora Video SDK Quickstart</Text> <View style={styles.btnContainer}> <Text onPress={join} style={styles.button}> Join Channel </Text> <Text onPress={leave} style={styles.button}> Leave Channel </Text> </View> <View style={styles.btnContainer}> <Text>Audience</Text> <Switch onValueChange={switchValue => { setIsHost(switchValue); if (isJoined) { leave(); } }} value={isHost} /> <Text>Host</Text> </View> <ScrollView style={styles.scroll} contentContainerStyle={styles.scrollContainer}> {isJoined && isHost ? ( <React.Fragment key={0}> {/* Create a local view using RtcSurfaceView */} <RtcSurfaceView canvas={{ uid: 0 }} style={styles.videoView} /> <Text>Local user uid: {uid}</Text> </React.Fragment> ) : ( <Text>Join a channel</Text> )} {isJoined && remoteUid !== 0 ? ( <React.Fragment key={remoteUid}> {/* Create a remote view using RtcSurfaceView */} <RtcSurfaceView canvas={{ uid: remoteUid }} style={styles.videoView} /> <Text>Remote user uid: {remoteUid}</Text> </React.Fragment> ) : ( <Text>{isJoined && !isHost ? 'Waiting for remote user to join' : ''}</Text> )} <Text style={styles.info}>{message}</Text> </ScrollView> </SafeAreaView> ); // Display information function showMessage(msg: string) { setMessage(msg); } }; export default VideoCall; |
3. VoiceCall
The Voice Call screen allows users to join or leave channels and switch between host and audience roles with Agora’s SDK. It manages real-time audio communication.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
// Import React Hooks import React, { useRef, useState, useEffect } from 'react'; // Import user interface elements import { SafeAreaView, ScrollView, Text, View } from 'react-native'; import { Platform } from 'react-native'; // Import Agora SDK import { createAgoraRtcEngine, ChannelProfileType, ClientRoleType, IRtcEngine, RtcConnection, IRtcEngineEventHandler } from 'react-native-agora'; import { getPermission } from '../../helper/helper'; import styles from './styles'; // Define basic information const appId = 'app_id'; const token = 'temp_token'; const channelName = 'test'; const uid = 0; // Local user Uid, no need to modify const VoiceCall = () => { const agoraEngineRef = useRef<IRtcEngine>(); // IRtcEngine instance const [isJoined, setIsJoined] = useState(false); // Whether the local user has joined the channel const [isHost, setIsHost] = useState(true); // User role const [remoteUid, setRemoteUid] = useState(0); // Uid of the remote user const [message, setMessage] = useState(''); // User prompt message const eventHandler = useRef<IRtcEngineEventHandler>(); // Callback functions useEffect(() => { // Initialize the engine when the App starts setupVideoSDKEngine(); // Release memory when the App is closed return () => { agoraEngineRef.current?.unregisterEventHandler(eventHandler.current!); agoraEngineRef.current?.release(); }; }, []); // Define the setupVideoSDKEngine method called when the App starts const setupVideoSDKEngine = async () => { try { // Create RtcEngine after obtaining device permissions if (Platform.OS === 'android') { await getPermission(); } agoraEngineRef.current = createAgoraRtcEngine(); const agoraEngine = agoraEngineRef.current; eventHandler.current = { onJoinChannelSuccess: () => { showMessage('Successfully joined channel: ' + channelName); setIsJoined(true); }, onUserJoined: (_connection: RtcConnection, uid: number) => { showMessage('Remote user ' + uid + ' joined'); setRemoteUid(uid); }, onUserOffline: (_connection: RtcConnection, uid: number) => { showMessage('Remote user ' + uid + ' left the channel'); setRemoteUid(0); }, }; // Register the event handler agoraEngine.registerEventHandler(eventHandler.current); // Initialize the engine agoraEngine.initialize({ appId: appId, }); } catch (e) { console.log(e); } }; // Define the join method called after clicking the join channel button const join = async () => { if (isJoined) { return; } try { if (isHost) { // Join the channel as a broadcaster agoraEngineRef.current?.joinChannel(token, channelName, uid, { // Set channel profile to live broadcast channelProfile: ChannelProfileType.ChannelProfileCommunication, // Set user role to broadcaster clientRoleType: ClientRoleType.ClientRoleBroadcaster, // Publish audio collected by the microphone publishMicrophoneTrack: true, // Automatically subscribe to all audio streams autoSubscribeAudio: true, }); } else { // Join the channel as an audience agoraEngineRef.current?.joinChannel(token, channelName, uid, { // Set channel profile to live broadcast channelProfile: ChannelProfileType.ChannelProfileCommunication, // Set user role to audience clientRoleType: ClientRoleType.ClientRoleAudience, // Do not publish audio collected by the microphone publishMicrophoneTrack: false, // Automatically subscribe to all audio streams autoSubscribeAudio: true, }); } } catch (e) { console.log(e); } }; // Define the leave method called after clicking the leave channel button const leave = () => { try { // Call leaveChannel method to leave the channel agoraEngineRef.current?.leaveChannel(); setRemoteUid(0); setIsJoined(false); showMessage('Left the channel'); } catch (e) { console.log(e); } }; // Render user interface return ( <SafeAreaView style={styles.main}> <Text style={styles.head}>Agora Voice Calling Quickstart</Text> <View style={styles.btnContainer}> <Text onPress={join} style={styles.button}> Join Channel </Text> <Text onPress={leave} style={styles.button}> Leave Channel </Text> </View> <ScrollView style={styles.scroll} contentContainerStyle={styles.scrollContainer}> {isJoined ? ( <Text>Local user uid: {uid}</Text> ) : ( <Text>Join a channel</Text> )} {isJoined && remoteUid !== 0 ? ( <Text>Remote user uid: {remoteUid}</Text> ) : ( <Text>Waiting for remote user to join</Text> )} <Text>{message}</Text> </ScrollView> </SafeAreaView> ); // Display information function showMessage(msg: string) { setMessage(msg); } }; export default VoiceCall; |
4. HomeScreen
The Home Screen provides navigation options to start a video or voice call using Agora’s communication features. It welcomes users with a title and a brief description.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
import React from 'react'; import { View, Text, TouchableOpacity } from 'react-native'; import styles from './styles'; export default function HomeScreen({ navigation }) { return ( <View style={styles.container}> {/* Title Section */} <Text style={styles.title}>Welcome to Agora Call App</Text> <Text style={styles.subtitle}> Experience seamless communication with Agora's Video and Voice call features. </Text> {/* Buttons Section */} <TouchableOpacity style={styles.button} onPress={() => navigation.navigate('VideoCall')} > <Text style={styles.buttonText}>Start Video Call</Text> </TouchableOpacity> <TouchableOpacity style={[styles.button, styles.voiceButton]} onPress={() => navigation.navigate('VoiceCall')} > <Text style={styles.buttonText}>Start Voice Call</Text> </TouchableOpacity> </View> ); } |
5. Style
We have created separate style files for the VideoCall, VoiceCall, and HomeScreen components.
style file for VideoCall and VoiceCall
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
import { StyleSheet } from "react-native"; export default StyleSheet.create({ button: { paddingHorizontal: 25, paddingVertical: 4, fontWeight: 'bold', color: '#ffffff', backgroundColor: '#0055cc', margin: 5, }, main: { flex: 1, alignItems: 'center' }, scroll: { flex: 1, backgroundColor: '#ddeeff', width: '100%' }, scrollContainer: { alignItems: 'center' }, videoView: { width: '90%', height: 200 }, btnContainer: { flexDirection: 'row', justifyContent: 'center' }, head: { fontSize: 20 }, info: { backgroundColor: '#ffffe0', paddingHorizontal: 8, color: '#0000ff' }, }); |
style file for HomeScreen
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
import { StyleSheet } from "react-native"; export default StyleSheet.create({ container: { flex: 1, backgroundColor: '#e3f2fd', alignItems: 'center', justifyContent: 'center', padding: 20, }, title: { fontSize: 24, fontWeight: 'bold', color: '#0d47a1', marginBottom: 10, }, subtitle: { fontSize: 16, color: '#37474f', textAlign: 'center', marginBottom: 30, }, button: { backgroundColor: '#1e88e5', padding: 15, borderRadius: 10, marginBottom: 15, width: '80%', alignItems: 'center', }, voiceButton: { backgroundColor: '#8e24aa', }, buttonText: { fontSize: 16, fontWeight: '600', color: '#ffffff', }, }); |
6. Route
The Route component sets up navigation using a stack navigator with three screens: Home, VideoCall, and VoiceCall. It initializes the Home screen as the default and provides titles for each screen.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
import React from 'react'; import { createStackNavigator } from '@react-navigation/stack'; import { NavigationContainer } from '@react-navigation/native'; import HomeScreen from '../screens/HomeScreen/index'; import VideoCall from '../screens/VideoCall/index'; import VoiceCall from '../screens/VoiceCall/index'; // Define the type for the navigation stack export type RootStackParamList = { Home: undefined; VideoCall: undefined; VoiceCall: undefined; }; const Stack = createStackNavigator<RootStackParamList>(); const Route: React.FC = () => { return ( <NavigationContainer> <Stack.Navigator initialRouteName="Home"> <Stack.Screen name="Home" component={HomeScreen} options={{ title: 'Home Screen' }} /> <Stack.Screen name="VideoCall" component={VideoCall} options={{ title: 'Video Call' }} /> <Stack.Screen name="VoiceCall" component={VoiceCall} options={{ title: 'Voice Call' }} /> </Stack.Navigator> </NavigationContainer> ); }; export default Route; |
7. App
The App component renders the Route component, which handles navigation between screens.
1 2 3 4 5 6 7 8 |
import React from 'react'; import Route from './src/navigation/Route'; const App: React.FC = () => { return <Route />; }; export default App; |
Now save or reload your react native app to view changes.
Here is the Output of the project
Create Audio & Video Calling App Using Agora.
Use the Agora SDK to integrate audio and video calling features into your app. It provides real-time communication APIs for seamless implementation across platforms.
Conclusion
Thanks for reading this blog.
Please check my other blogs here.