Creating an app for video calls with QuickBlox React Native SDK

Elena Kvitkovska
QuickBlox Engineering
15 min readFeb 27, 2020

In this article, we will build a React Native application that provides an ability to make video calls using quickblox-react-native-sdk.

You can find a full code sample in quickblox-react-native-samples repo.

Creating application backbone

React Native CLI provides us an easy way to create a new React Native application:

react-native init AwesomeWebRTCApp

Once react-native-cli created a project we should update `ios/Podfile`:

platform :ios, ‘10.0’. Since QuickBlox React Native SDK has minimum supported iOS version 10.0

We will need the following packages in addition to preinstalled:

  • quickblox-react-native-sdk — main package of this application
  • redux — to maintain application state
  • react-redux — react bindings for redux
  • redux-persist — to keep application state persistent across reloads
  • @react-native-community/async-storage — as a storage engine for redux-persist
  • redux-logger — helper that will put all actions and store changes in debugger’s console
  • redux-saga — library for making application side effects (i.e. asynchronous things like data fetching and impure things like accessing the cache)
  • react-navigation — routing and navigation for React Native apps
  • react-native-reanimated — dependency of react-navigation
  • react-native-gesture-handler — dependency of react-navigation
  • react-native-screens — dependency of react-navigation
  • final-form — form state management library
  • react-final-form — form state management for React
  • react-native-incall-manager — handling media-routes/sensors/events during a audio/video chat on React Native
  • react-native-flash-message — flashbar and top notification alert utility

To install all the packages, run:

npm install — save quickblox-react-native-sdk redux react-redux redux-persist @react-native-community/async-storage redux-logger redux-saga react-navigation react-native-reanimated react-native-gesture-handler react-native-screens final-form react-final-form react-native-incall-manager react-native-flash-message

Project structure

We will use separate folders for components, containers, sagas, and other parts of the application:

  • actionCreators — application’s action creators
  • componentspresentational components
  • constants — constants representing actions names
  • containerscontainer components
  • reducers — application’s reducers
  • sagassagas (API calls, calls to SDK, etc.)
  • store — redux store initialization
  • images — key-value images collection used in this application (imported with require, so can be used as Image.source)
  • QBConfig — object with credentials for QuickBlox SDK initialization
  • theme — app-wide styles (colors, navigation header styles, etc.)QuickBlox application credentials

In order to use QuickBlox SDK, we should initialize it with correct application credentials. To create an application you will need an account — you can register at https://admin.quickblox.com/signup or login if you already have one. Create your QuickBlox app and obtain app credentials. These credentials will be used to identify your app.

All users within the same QuickBlox app can communicate by chat or video chat with each other, across all platforms — iOS, Android, Web, etc.

In this app, we will store QuickBlox application credentials in file src/QBConfig.js. So once you have app credentials put them into that file:

export default {
appId: ‘’,
authKey: ‘’,
authSecret: ‘’,
accountKey: ‘’,
apiEndpoint: ‘’,
chatEndpoint: ‘’,
}

Configuring application

Our application has several points that we should configure. Let’s move over them:

  1. constants — this folder will have only one file (index.js) that will export string constants which we will use in this app (check it out in repository)
  2. actionCreators — this folder will have several files for different app parts and one file exporting all action creators (check it out in repository)
  3. reducers — this folder will have several files for different app parts and one file that exports all reducers combined AKA root reducer (check it out in repository)
  4. sagas — this folder will have several files for different app parts and one file that exports all application sagas combined into one saga AKA root saga (check it out in repository)
  5. store — this folder will have only one file (index.js) that will export function to set up redux store for application (check it out in repository)

Once that done we can configure our entry point at src/index.js:

import React from ‘react’
import { Provider } from ‘react-redux’
import { PersistGate } from ‘redux-persist/integration/react’
import { enableScreens } from ‘react-native-screens’
import App from ‘./App’
import Loading from ‘./components/Loading’
import configureStore from ‘./store’
import rootSaga from ‘./sagas’
enableScreens()const { runSaga, store, persistor } = configureStore()
runSaga(rootSaga)
export default () => (
<Provider store={store}>
<PersistGate loading={<Loading />} persistor={persistor}>
<App />
</PersistGate>
</Provider>
)

Before starting our app we should also setup routing / navigation. Here is the code we will use (check it out in a repository):

import { createAppContainer, createSwitchNavigator } from ‘react-navigation’
import { createStackNavigator } from ‘react-navigation-stack’
import CheckAuth from ‘./containers/CheckAuth’
import Login from ‘./containers/Auth/Login’
import CheckConnection from ‘./containers/CheckConnection’
import Users from ‘./containers/Users’
import CallScreen from ‘./containers/CallScreen’
import Info from ‘./containers/Info’
import { navigationHeader } from ‘./theme’
const AppNavigator = createSwitchNavigator({
CheckAuth,
Auth: createStackNavigator({
Login,
Info,
}, {
initialRouteName: ‘Login’,
defaultNavigationOptions: navigationHeader,
}),
WebRTC: createSwitchNavigator({
CheckConnection,
CallScreen,
Main: createStackNavigator({
Users,
Info,
}, {
initialRouteName: ‘Users’,
defaultNavigationOptions: navigationHeader,
})
}, {
initialRouteName: ‘CheckConnection’
})
}, {
initialRouteName: ‘CheckAuth’
})
export default createAppContainer(AppNavigator)

There is also a logic behind deciding should we use StackNavigator or SwitchNavigator — when the application starts it will display a route that will check if the user authenticated. If not — The Login screen will be displayed. Otherwise, we can route the user to the application. But then we should check if we have a connection to chat and connect if not connected. Then we can route the user further: if there is a WebRTC session — route to CallScreen, otherwise to the main screen.

Start application

Now that we have navigation, store and other things set up we can run our app. Let’s update src/App.js to display our router:

import React from ‘react’
import { StatusBar, StyleSheet, View } from ‘react-native’
import FlashMessage from ‘react-native-flash-message’
import Navigator from ‘./Navigation’
import NavigationService from ‘./NavigationService’
import { colors } from ‘./theme’
export default class App extends React.Component { render() {
return (
<View style={styles.container}>
<StatusBar
backgroundColor={colors.primary}
barStyle=”light-content”
/>
<View style={styles.navigatorView}>
<Navigator ref={NavigationService.init} />
</View>
<FlashMessage position=”bottom” />
</View>
)
}
}

Our application is using QuickBlox React Native SDK for audio/video calls. In order to use it, we should initialize it. So let’s update src/App.js and add SDK initialization upon app starts:

import React from ‘react’
import { connect } from ‘react-redux’
import { StatusBar, StyleSheet, View } from ‘react-native’
import FlashMessage from ‘react-native-flash-message’
import Navigator from ‘./Navigation’
import NavigationService from ‘./NavigationService’
import { appStart } from ‘./actionCreators’
import { colors } from ‘./theme’
import config from ‘./QBConfig’
const styles = StyleSheet.create({
container: {
alignItems: ‘center’,
backgroundColor: colors.primary,
flex: 1,
justifyContent: ‘center’,
width: ‘100%’,
},
navigatorView: {
flex: 1,
width: ‘100%’,
},
})
class App extends React.Component { constructor(props) {
super(props)
props.appStart(config)
}
render() {
return (
<View style={styles.container}>
<StatusBar
backgroundColor={colors.primary}
barStyle=”light-content”
/>
<View style={styles.navigatorView}>
<Navigator ref={NavigationService.init} />
</View>
<FlashMessage position=”bottom” />
</View>
)
}
}const mapStateToProps = nullconst mapDispatchToProps = { appStart }export default connect(mapStateToProps, mapDispatchToProps)(App)

When appStart action creator will fire APP_START action the saga will be triggered (in src/sagas/app.js) that will initialize QuickBlox SDK with action payload:

export function* appStart(action = {}) {
const config = action.payload
try {
yield call(QB.settings.init, config)
yield put(appStartSuccess())
} catch (e) {
yield put(appStartFail(e.message))
}
}

Creating Login form

The first thing the user will see in this app will be the Login form. Let’s create a component for it:

export default class Login extends React.Component { LOGIN_HINT = ‘Use your email or alphanumeric characters in a range from 3 to 50. First character must be a letter.’
USERNAME_HINT = ‘Use alphanumeric characters and spaces in a range from 3 to 20. Cannot contain more than one space in a row.’
static navigationOptions = ({ navigation }) => ({
title: ‘Enter to videochat’,
headerRight: (
<HeaderButton
imageSource={images.INFO}
onPress={() => navigation.navigate(‘Info’)}
/>
)
})
validate = (values) => {
const errors = []
if (values.login) {
if (values.login.indexOf(‘@’) > -1) {
if (!emailRegex.test(values.login)) {
errors.login = this.LOGIN_HINT
}
} else {
if (!/^[a-zA-Z][\w\-\.]{1,48}\w$/.test(values.login)) {
errors.login = this.LOGIN_HINT
}
}
} else {
errors.login = this.LOGIN_HINT
}
if (values.username) {
if (!/^(?=.{3,20}$)(?!.*([\s])\1{2})[\w\s]+$/.test(values.username)) {
errors.username = this.USERNAME_HINT
}
} else {
errors.username = this.USERNAME_HINT
}
return errors
}
submit = ({ login, username }) => {
const { createUser, signIn } = this.props
new Promise((resolve, reject) => {
signIn({ login, resolve, reject })
}).then(action => {
this.checkIfUsernameMatch(username, action.payload.user)
}).catch(action => {
const { error } = action
if (error.toLowerCase().indexOf(‘unauthorized’) > -1) {
new Promise((resolve, reject) => {
createUser({
fullName: username,
login,
password: ‘quickblox’,
resolve,
reject,
})
}).then(() => {
this.submit({ login, username })
}).catch(userCreateAction => {
const { error } = userCreateAction
if (error) {
showError(‘Failed to create user account’, error)
}
})
} else {
showError(‘Failed to sign in’, error)
}
})
}
checkIfUsernameMatch = (username, user) => {
const { updateUser } = this.props
const update = user.fullName !== username ?
new Promise((resolve, reject) => updateUser({
fullName: username,
login: user.login,
resolve,
reject,
})) :
Promise.resolve()
update
.then(this.connectAndRedirect)
.catch(action => {
if (action && action.error) {
showError(‘Failed to update user’, action.error)
}
})
}
connectAndRedirect = () => {
const { connectAndSubscribe, navigation } = this.props
connectAndSubscribe()
navigation.navigate(‘Users’)
}
renderForm = (formProps) => {
const { handleSubmit, invalid, pristine, submitError } = formProps
const { loading } = this.props
const submitDisabled = pristine || invalid || loading
const submitStyles = submitDisabled ?
[styles.submitBtn, styles.submitBtnDisabled] :
styles.submitBtn
return (
<KeyboardAvoidingView
behavior={Platform.select({ ios: ‘padding’ })}
style={styles.topView}
>
<ScrollView
contentContainerStyle={{ alignItems: ‘center’ }}
style={styles.scrollView}
>
<View style={{ width: ‘50%’ }}>
<Header>Please enter your login and username</Header>
</View>
<View style={styles.formControlView}>
<Label>Login</Label>
<Field
activeStyle={styles.textInputActive}
autoCapitalize=”none”
blurOnSubmit={false}
component={FormTextInput}
editable={!loading}
name=”login”
onSubmitEditing={() => this.usernameRef.focus()}
returnKeyType=”next”
style={styles.textInput}
textContentType=”username”
underlineColorAndroid={colors.transparent}
/>
</View>
<View style={styles.formControlView}>
<Label>Username</Label>
<Field
activeStyle={styles.textInputActive}
autoCapitalize=”none”
component={FormTextInput}
editable={!loading}
inputRef={_ref => this.usernameRef = _ref}
name=”username”
onSubmitEditing={handleSubmit}
returnKeyType=”done”
style={styles.textInput}
underlineColorAndroid={colors.transparent}
/>
</View>
{submitError ? (
<Label style={{ alignSelf: ‘center’, color: colors.error }}>
{submitError}
</Label>
) : null}
<TouchableOpacity
disabled={submitDisabled}
onPress={handleSubmit}
style={submitStyles}
>
{loading ? (
<ActivityIndicator color={colors.white} size={20} />
) : (
<Text style={styles.submitBtnText}>Login</Text>
)}
</TouchableOpacity>
</ScrollView>
</KeyboardAvoidingView>
)
}
render() {
return (
<Form
onSubmit={this.submit}
render={this.renderForm}
validate={this.validate}
/>
)
}
}

See the full code with container component and helper functions in the repository.

In this code, we have validation of provided login and username, and if validation passed — trying to sign in. After successful sign-in, the “CHAT_CONNECT_AND_SUBSCRIBE” action is successfully dispatched, which in turn triggers connectAndSubscribe saga:

export function* connectAndSubscribe() {
const { user } = yield select(state => state.auth)
if (!user) return
const connected = yield call(isChatConnected)
const loading = yield select(({ chat }) => chat.loading)
if (!connected && !loading) {
yield call(chatConnect, {
payload: {
userId: user.id,
password: user.password,
}
})
}
yield call(setupQBSettings)
yield put(webrtcInit())
}

Find the source code of these sagas in the repository.

Creating Users list

React Native provides several APIs to render lists and we not building here something extraordinary, so to render list of users we are going to use FlatList:

export default class UsersList extends React.PureComponent { componentDidMount() {
this.props.getUsers()
}
loadNextPage = () => {
const {
filter,
getUsers,
loading,
page,
perPage,
total,
} = this.props
const hasMore = page * perPage < total
if (loading || !hasMore) {
return
}
const query = {
append: true,
page: page + 1,
perPage,
}
if (filter && filter.trim().length) {
query.filter = {
field: QB.users.USERS_FILTER.FIELD.FULL_NAME,
operator: QB.users.USERS_FILTER.OPERATOR.IN,
type: QB.users.USERS_FILTER.TYPE.STRING,
value: filter
}
}
getUsers(query)
}
onUserSelect = (user) => {
const { selectUser, selected = [] } = this.props
const index = selected.findIndex(item => item.id === user.id)
if (index > -1 || selected.length < 3) {
const username = user.fullName || user.login || user.email
selectUser({ id: user.id, name: username })
} else {
showError(
‘Failed to select user’,
‘You can select no more than 3 users’
)
}
}
renderUser = ({ item }) => {
const { selected = [] } = this.props
const userSelected = selected.some(record => record.id === item.id)
return (
<User
isSelected={userSelected}
onSelect={this.onUserSelect}
selectable
user={item}
/>
)
}
renderNoUsers = () => {
const { filter, loading } = this.props
if (loading || !filter) {
return null
} else return (
<View style={styles.noUsersView}>
<Text style={styles.noUsersText}>
No user with that name
</Text>
</View>
)
}
render() {
const { data, getUsers, loading } = this.props
return (
<FlatList
data={data}
keyExtractor={({ id }) => `${id}`}
ListEmptyComponent={this.renderNoUsers}
onEndReached={this.loadNextPage}
onEndReachedThreshold={0.85}
refreshControl={(
<RefreshControl
colors={[colors.primary]}
refreshing={loading}
tintColor={colors.primary}
onRefresh={getUsers}
/>
)}
renderItem={this.renderUser}
style={{ backgroundColor: colors.whiteBackground }}
/>
)
}
}

When the component will mount, it will dispatch the action that will trigger users saga which in turn will call QuickBlox SDK to load users:

const defautQuery = {
sort: {
ascending: false,
field: QB.users.USERS_FILTER.FIELD.UPDATED_AT,
type: QB.users.USERS_FILTER.TYPE.DATE
}
}
try {
const query = { …defaultQuery, …action.payload }
const response = yield call(QB.users.getUsers, query)
yield put(usersGetSuccess(response))
} catch (e) {
yield put(usersGetFail(e.message))
showError(‘Failed to get users’, e.message)
}

Creating call screen

Call screen should work in case a user initiates a call, but also when the user receives a call request. Also, there can be an audio or video call. Let’s say that for audio call we will display circles for opponents (excluding current user) showing opponent name and peer connection status:

const PeerStateText = {
[QB.webrtc.RTC_PEER_CONNECTION_STATE.NEW]: ‘Calling…’,
[QB.webrtc.RTC_PEER_CONNECTION_STATE.CONNECTED]: ‘Connected’,
[QB.webrtc.RTC_PEER_CONNECTION_STATE.DISCONNECTED]: ‘Disconnected’,
[QB.webrtc.RTC_PEER_CONNECTION_STATE.FAILED]: ‘Failed to connect’,
[QB.webrtc.RTC_PEER_CONNECTION_STATE.CLOSED]: ‘Connection closed’,
}
const getOpponentsCircles = () => {
const { currentUser, peers, session, users } = this.props
const userIds = session
.opponentsIds
.concat(session.initiatorId)
.filter(userId => userId !== currentUser.id)
return (
<View style={styles.opponentsContainer}>
{userIds.map(userId => {
const user = users.find(user => user.id === userId)
const username = user ?
(user.fullName || user.login || user.email) :
‘’
const backgroundColor = user && user.color ?
user.color :
colors.primaryDisabled
const peerState = peers[userId] || 0
return (
<View key={userId} style={styles.opponentView}>
<View style={[styles.circleView, { backgroundColor }]}>
<Text style={styles.circleText}>
{username.charAt(0)}
</Text>
</View>
<Text style={styles.usernameText}>
{username}
</Text>
<Text style={styles.statusText}>
{PeerStateText[peerState]}
</Text>
</View>
)
})}
</View>
)
}

And for the video call, we may show WebRTCView from QuickBlox React Native SDK:

import WebRTCView from ‘quickblox-react-native-sdk/RTCView’const getVideoViews = () => {
const { currentUser, opponentsLeftCall, session } = this.props
const opponentsIds = session ? session
.opponentsIds
.filter(id => opponentsLeftCall.indexOf(id) === -1) :
[]
const videoStyle = opponentsIds.length > 1 ?
{ height: ‘50%’, width: ‘50%’ } :
{ height: ‘50%’, width: ‘100%’ }
const initiatorVideoStyle = opponentsIds.length > 2 ?
{ height: ‘50%’, width: ‘50%’ } :
{ height: ‘50%’, width: ‘100%’ }
return (
<React.Fragment>
{opponentsIds.map(userId => (
<WebRTCView
key={userId}
mirror={userId === currentUser.id}
sessionId={session.id}
style={videoStyle}
userId={userId}
/>
))}
<WebRTCView
key={session.initiatorId}
mirror={session.initiatorId === currentUser.id}
sessionId={session.id}
style={initiatorVideoStyle}
userId={session.initiatorId}
/>
</React.Fragment>
)
}

You can find the full code of this component (and container, and other parts) in the repository.

Select users and initiate a call

To start a call, we should select the users whom we will call. This is up to you how to implement the mechanism of user selecting, we will focus here on initiating a call when the users are already selected.


import QB from ‘quickblox-react-native-sdk’
const styles = StyleSheet.create({

})
export default class SelectedUsersAndCallButtons extends React.PureComponent { audioCall = () => {
const { call, users } = this.props
const opponentsIds = users.map(user => user.id)
try {
call({ opponentsIds, type: QB.webrtc.RTC_SESSION_TYPE.AUDIO })
} catch (e) {
showError(‘Error’, e.message)
}
}
videoCall = () => {
const { call, users } = this.props
const opponentsIds = users.map(user => user.id)
try {
call({ opponentsIds, type: QB.webrtc.RTC_SESSION_TYPE.VIDEO })
} catch (e) {
showError(‘Error’, e.message)
}
}
render() {
const { users } = this.props
return (
<View style={styles.view}>
<SelectedUsers users={users} />
<Button
disabled={users.length < 1 || users.length > 3}
onPress={this.audioCall}
style={styles.button}
>
<Image source={CALL} style={styles.icon} />
</Button>
<Button
disabled={users.length < 1 || users.length > 3}
onPress={this.videoCall}
style={styles.button}
>
<Image source={VIDEO_CALL} style={styles.icon} />
</Button>
</View>
)
}

}

In this example, we are showing separate buttons for audio and video call which are disabled if there is less than 1 or more than 3 selected users. When pressing a button, the action is dispatched with opponents-IDs (array containing IDs of selected users) and type of call to start. Here is the saga listening for this action:

export function* callSaga(action) {
try {
const { payload } = action
const session = yield call(QB.webrtc.call, payload)
yield put(webrtcCallSuccess(session))
} catch (e) {
yield put(webrtcCallFail(e.message))
}
}

Once we created a WebRTC session (started a call), we can navigate to a call screen and wait for opponents’ responses.

Listening to QuickBlox SDK events

QuickBlox SDK sends events from native code to JS when something happens. In order to receive these events, we should create an emitter from QuickBlox SDK module. Net every module of QuickBlox SDK sends events. To find out if the module is sending events you can check if that module has EVENT_TYPE property. For example check output of following code in React Native app:

console.log(QB.chat.EVENT_TYPE)
console.log(QB.auth.EVENT_TYPE)
console.log(QB.webrtc.EVENT_TYPE)

Once we create an emitter from QuickBlox SDK module we can assign an event handler(s) to listen and process events. With redux-saga we can use eventChannel factory to create a channel for events.

import { NativeEventEmitter } from ‘react-native’
import { eventChannel } from ‘redux-saga’
import QB from ‘quickblox-react-native-sdk’
import {
CHAT_CONNECT_AND_SUBSCRIBE,
CHAT_DISCONNECT_REQUEST,
} from ‘../constants’
import { webrtcReject } from ‘../actionCreators’
import Navigation from ‘../NavigationService’
function* createChatConnectionChannel() {
return eventChannel(emitter => {
const chatEmitter = new NativeEventEmitter(QB.chat)
const QBConnectionEvents = [
QB.chat.EVENT_TYPE.CONNECTED,
QB.chat.EVENT_TYPE.CONNECTION_CLOSED,
QB.chat.EVENT_TYPE.CONNECTION_CLOSED_ON_ERROR,
QB.chat.EVENT_TYPE.RECONNECTION_FAILED,
QB.chat.EVENT_TYPE.RECONNECTION_SUCCESSFUL,
]
const subscriptions = QBConnectionEvents.map(eventName =>
chatEmitter.addListener(eventName, emitter)
)
return () => {
while (subscriptions.length) {
const subscription = subscriptions.pop()
subscription.remove()
}
}
})
}
function* createWebRTCChannel() {
return eventChannel(emitter => {
const webRtcEmitter = new NativeEventEmitter(QB.webrtc)
const QBWebRTCEvents = Object
.keys(QB.webrtc.EVENT_TYPE)
.map(key => QB.webrtc.EVENT_TYPE[key])
const subscriptions = QBWebRTCEvents.map(eventName =>
webRtcEmitter.addListener(eventName, emitter)
)
return () => {
while (subscriptions.length) {
const subscription = subscriptions.pop()
subscription.remove()
}
}
})
}
function* readConnectionEvents() {
const channel = yield call(createChatConnectionChannel)
while (true) {
try {
const event = yield take(channel)
yield put(event)
} catch (e) {
yield put({ type: ‘ERROR’, error: e.message })
} finally {
if (yield cancelled()) {
channel.close()
}
}
}
}
function* readWebRTCEvents() {
const channel = yield call(createWebRTCChannel)
while (true) {
try {
const event = yield take(channel)
yield put(event)
} catch (e) {
yield put({ type: ‘ERROR’, error: e.message })
} finally {
if (yield cancelled()) {
channel.close()
}
}
}
}

To start reading events from chatConnection channel and webrtc channel, we can use sagas that will create channels upon login and will close channel(s) upon logout:

function* QBconnectionEventsSaga() {
try {
const channel = yield call(createChatConnectionChannel)
while (true) {
const event = yield take(channel)
yield put(event)
}
} catch (e) {
yield put({ type: ‘QB_CONNECTION_CHANNEL_ERROR’, error: e.message })
}
}
function* QBWebRTCEventsSaga() {
try {
const channel = yield call(createWebRTCChannel)
while (true) {
const event = yield take(channel)
yield call(handleWebRTCEvent, event)
}
} catch (e) {
yield put({ type: ‘QB_WEBRTC_CHANNEL_ERROR’, error: e.message })
}
}

You can find full code at the sample repository.

So if we want to add special hander for some specific event(s) we can either put that logic in readWebRTCEvents saga or create separate event channel for that event(s).

Let’s add some more logic into existing saga:

function* readWebRTCEvents() {
const channel = yield call(createWebRTCChannel)
while (true) {
try {
const event = yield take(channel)
// if we received incoming call request
if (event.type === QB.webrtc.EVENT_TYPE.CALL) {
const { session, user } = yield select(({ auth, webrtc }) => ({
session: webrtc.session,
user: auth.user
}))
// if we already have session (either incoming or outgoing call)
if (session) {
// received call request is not for the session we have
if (session.id !== event.payload.session.id) {
const username = user ?
(user.fullName || user.login || user.email) :
‘User’
// reject call request with explanation message
yield put(webrtcReject({
sessionId: event.payload.session.id,
userInfo: { reason: `${username} is busy` }
}))
}
// if we don’t have session
} else {
// dispatch event that we have call request
// to add this session in store
yield put(event)
// navigate to call screen to provide user with
// controls to accept or reject call
Navigation.navigate({ routeName: ‘CallScreen’ })
}
} else {
yield put(event)
}
} catch (e) {
yield put({ type: ‘ERROR’, error: e.message })
} finally {
if (yield cancelled()) {
channel.close()
}
}
}
}

Operating call

There isn’t a lot of possible actions we can perform with a call, but let’s go through them to make it clear:

  1. Accept — can be applied to incoming call requests. To accept a call (session) we should specify the session ID we want to accept
// action creator
export function acceptCall(payload) {
return { type: WEBRTC_ACCEPT_REQUEST, payload }
}
// dispatch action to accept a call
acceptCall({ sessionId: this.props.session.id })
export function* accept(action) {
try {
const { payload } = action
// call Quickblox SDK to accept a call
const session = yield call(QB.webrtc.accept, payload)
// dispatch action to indicate that session accepted successfully
// session returned is the representation of our active call
yield put(acceptCallSuccess(session))
} catch (e) {
yield put(acceptCallFail(e.message))
}
}
  1. Reject — can be applied to incoming call request. To reject (decline) call request we should specify the session ID we want to reject
// action creator
export function rejectCall(payload) {
return { type: WEBRTC_REJECT_REQUEST, payload }
}
// dispatch action to reject a call
rejectCall({ sessionId: this.props.session.id })
export function* reject(action) {
try {
const { payload } = action
// call Quickblox SDK to reject a call
const session = yield call(QB.webrtc.reject, payload)
// dispatch action to indicate that session rejected successfully
yield put(rejectCallSuccess(session))
} catch (e) {
yield put(rejectCallFail(e.message))
}
}
  1. Hang up — end a call. The same as for examples above — we should specify the session ID we want to end
// action creator
export function hangUpCall(payload) {
return { type: WEBRTC_HANGUP_REQUEST, payload }
}
// dispatch action to end a session (call)
hangUpCall({ sessionId: this.props.session.id })
export function* hangUp(action) {
try {
const { payload } = action
const session = yield call(QB.webrtc.hangUp, payload)
// dispatch an action to indicate that hung up successfully
yield put(hangUpCallSuccess(session))
} catch (e) {
yield put(hangUpCallFail(e.message))
}
}

Conclusion

QuickBlox React Native SDK helps you to integrate high-quality video chat connection into any type of software saving a good amount of development time. To learn more about QuickBlox React Native SDK, have a look at the official documentation.

--

--