Call Kit
  • iOS
  • Android
  • Web
  • Flutter
  • React Native : JavaScript
  • Overview
  • Quick start
    • Quick start
    • Quick start (with call invitation)
  • Customize the call
    • Overview
    • Set avatar for users
    • Add custom components to the call
    • Configure layouts
    • Hide the label on the user view
    • Implement an audio-only call
    • Customize the menu bar
    • Set a hangup confirmation dialog
    • Call invitation config
    • Calculate call duration
  • Enhance the call
    • Minimize video call window
    • Use Tokens for authentication
    • Adaptive mobile rotation
  • API Reference
    • API
    • Event
  • Migration guide
  • Documentation
  • Call Kit
  • Quick start
  • Quick start (with call invitation)

Quick start (with call invitation)

Last updated:2024-02-19 10:58

You can refer to this document to understand the effects of the offline call invitation (system-calling UI) and complete the basic integration.

  1. If your project needs Firebase integration or customization of features like ringtone and UI, complete the basic integration first and then refer to Customize the call and Enhance the call for further configuration.

  2. Offline call invitation configuration is complex. If you only require online call invitations, please skip the steps related to firebase console and apple certificate.

UI Implementation Effects

Recorded on Xiaomi and iPhone, the outcome may differ on different devices.

Online call online call (Android App background) offline call (Android App killed) offline call (iOS Background/Killed)

Integration Guide for Common Components

Prerequisites

​If you don't know how to create a project and obtain an app ID, please refer to this guide.

Add dependencies.

  1. Run the following code in your project root directory, to install @zegocloud/zego-uikit-prebuilt-call-rn.
yarn npm
yarn add @zegocloud/zego-uikit-prebuilt-call-rn
npm install @zegocloud/zego-uikit-prebuilt-call-rn
  • If you are using Expo, please note that we only support Expo bare workflow. (We have only done basic testing on expo. If possible, it is recommended to integrate call kit with a standard react native project.)

  • call invitation feature is not compatible with "expo-notifications" and cannot be used together. If you are using "expo-notifications", it is recommended to remove this dependency or migrate to "@react-native-firebase/messaging".

  1. Add other dependencies.

Run the following command to install other dependencies for making sure the @zegocloud/zego-uikit-prebuilt-call-rn can work properly.

yarn npm
yarn add @zegocloud/zego-uikit-rn react-delegate-component @react-navigation/native @react-navigation/native-stack react-native-screens react-native-safe-area-context react-native-sound @notifee/react-native@5.3.0 react-native-encrypted-storage zego-express-engine-reactnative@3.2.0 zego-zim-react-native@2.12.1 zego-zpns-react-native@2.5.0-alpha @zegocloud/react-native-callkeep react-native-keep-awake@4.0.0
npm install @zegocloud/zego-uikit-rn react-delegate-component @react-navigation/native @react-navigation/native-stack react-native-screens react-native-safe-area-context react-native-sound @notifee/react-native@5.3.0 react-native-encrypted-storage zego-express-engine-reactnative@3.2.0 zego-zim-react-native@2.12.1 zego-zpns-react-native@2.5.0-alpha @zegocloud/react-native-callkeep react-native-keep-awake@4.0.0
When your React Native version is below 0.60, please follow these steps

If your react-native version is below 0.60, you need to run the following command in your project root directory to ensure that @zegocloud/react-native-callkeep and react-native-encrypted-storage can work properly.

react-native link @zegocloud/react-native-callkeep
react-native link react-native-encrypted-storage
Check here if you encounter the issue "undefined is not an object (evaluating 'RNSound.IsAndroid')"

Internally, the react-native-sound plugin is used to play ringtones. If you encounter this error:

undefined is not an object (evaluating 'RNSound.IsAndroid')

Then, you may additionally need to fully clear your build caches for Android. You can do this using:

cd android
./gradlew cleanBuildCache

After clearing your build cache, you should execute a new react-native build.

If you still experience issues, know that this is the most common build issue. And check it here and the several issues linked to it for possible resolution.

Config React Navigation

To let users transition between multiple screens, you will need to install a navigation library. In React Native, there are several different types of navigation libraries you can use to create a navigation structure. For now, we use the React navigation.

  1. Place the ZegoCallInvitationDialog component on the top level of the NavigationContainer.
<ZegoCallInvitationDialog />
  1. Add two routes (ZegoUIKitPrebuiltCallWaitingScreen and ZegoUIKitPrebuiltCallInCallScreen) to your stack navigator.
<Stack.Screen
    options={{ headerShown: false }}
    // DO NOT change the name 
    name="ZegoUIKitPrebuiltCallWaitingScreen"
    component={ZegoUIKitPrebuiltCallWaitingScreen}
/>
<Stack.Screen
    options={{ headerShown: false }}
    // DO NOT change the name
    name="ZegoUIKitPrebuiltCallInCallScreen"
    component={ZegoUIKitPrebuiltCallInCallScreen}
/>

Initialize the call invitation service

  1. Call the useSystemCallingUI method in the index.js file.
import {AppRegistry} from 'react-native';
import App from './App';
import {name as appName} from './app.json';

// Add these lines \/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/
import ZegoUIKitPrebuiltCallService from '@zegocloud/zego-uikit-prebuilt-call-rn'
import * as ZIM from 'zego-zim-react-native';
import * as ZPNs from 'zego-zpns-react-native';

ZegoUIKitPrebuiltCallService.useSystemCallingUI([ZIM, ZPNs]);
// Add these lines /\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\/\

AppRegistry.registerComponent(appName, () => App);
  1. Call the ZegoUIKitPrebuiltCallService.init method.

We recommend calling this method immediately after the user logs into your app.

  1. After the user logs in, it is necessary to Initialize the ZegoUIKitPrebuiltCallInvitationService to ensure that it is initialized only once, avoiding errors caused by repeated initialization.

  2. When the user logs out, it is ​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​important to perform Deinitialize to clear the previous login records, preventing any impact on the next login.

import * as ZIM from 'zego-zim-react-native';
import * as ZPNs from 'zego-zpns-react-native';

const onUserLogin = async (userID, userName, props) => {
  return ZegoUIKitPrebuiltCallService.init(
    yourAppID, // You can get it from ZEGOCLOUD's console
    yourAppSign, // You can get it from ZEGOCLOUD's console
    userID, // It can be any valid characters, but we recommend using a phone number.
    userName,
    [ZIM, ZPNs],
    {
        ringtoneConfig: {
            incomingCallFileName: 'zego_incoming.mp3',
            outgoingCallFileName: 'zego_outgoing.mp3',
        },
        androidNotificationConfig: {
            channelID: "ZegoUIKit",
            channelName: "ZegoUIKit",
        },
    });
}

const onUserLogout = async () => {
  return ZegoUIKitPrebuiltCallService.uninit()
}
The parameters of the init method The prototype of init method: init(appID, appSign, userInfo, plugins, config = {})
Parameter Type Required Description
appID Number Yes You can get the App ID from ZEGOCLOUD Admin Console.
appSign String Yes You can get the App Sign from ZEGOCLOUD Admin Console.
userID String Yes userID can be something like a phone number or the user ID on your own user system. userID can only contain numbers, letters, and underlines (_).
userName String Yes userName can be any character or the user name on your own user system.
plugins Array No Set it to [ZIM, ZPNs] if you want to use the invitation functionality
config Object Yes You can set it to empty object {} if you don't want to change any configuration
config.ringtoneConfig Object No ringtoneConfig.incomingCallFileName and ringtoneConfig.outgoingCallFileName is the name of the ringtone file, which requires you a manual import. To know how to import, refer to the following chapter: Configure your project.
config.requireConfig Function No This method is called when you receive a call invitation. You can control the SDK behaviors by returning the required config based on the data parameter. For more details, see Custom prebuilt UI.
config.androidNotificationConfig Object No androidNotificationConfig.channelID must be the same as the FCM Channel ID in ZEGOCLOUD Admin Console, and the androidNotificationConfig.channelName can be an arbitrary value.
config.innerText Object No To modify the UI text, use this property. For more details, see Custom prebuilt UI.

Add a call invitation button

Configure the "ZegoSendCallInvitationButton" to enable making calls.

One-on-one Group call
<ZegoSendCallInvitationButton
    invitees={[{userID: varUserID, userName: varUserName'}]}
    isVideoCall={true}
    resourceID={"zego_call"} // Please fill in the resource ID name that has been configured in the ZEGOCLOUD's console here.
/>
<ZegoSendCallInvitationButton
    invitees={invitees.map((inviteeID) => {
        return { userID: inviteeID, userName: 'user_' + inviteeID };
    })}
    isVideoCall={true}
    resourceID={"zego_call"} // Please fill in the resource ID name that has been configured in the ZEGOCLOUD's console here.
/>
Props of ZegoSendCallInvitationButton
Property   Type Required Description
invitees
Array
Yes
The information of the callee. userID and userName are required. For example: [{ userID: inviteeID, userName: inviteeName }]
isVideoCall 
Boolean
Yes
If true, a video call is made when the button is pressed. Otherwise, a voice call is made.
resourceID 
Boolean
No
resourceID can be used to specify the ringtone of an offline call invitation, which must be set to the same value as the Push Resource ID in ZEGOCLOUD Admin Console.
timeout 
Number
No
The timeout duration. It's 60 seconds by default.
onPressed 
Function
No
Callback method after pressing the button and sending an invitation.
onWillPressed 
Function
No
This method will be triggered before pressing the send invitation button. You need to return a Promise in this method. If the value of the Promise is false, the invitation will be cancelled.

For more parameters, go to Custom prebuilt UI.

Complete sample code

The example code uses react-native-device-info and @react-native-async-storage/async-storage to generate userID and userName. If you also need to use them, please execute the following command to install dependencies:

yarn npm
yarn add react-native-device-info @react-native-async-storage/async-storage
npm install react-native-device-info @react-native-async-storage/async-storage
import React, { useState, useRef, useEffect } from 'react';
import { View, Text, TextInput, Button, StyleSheet, TouchableWithoutFeedback, } from 'react-native';
import { NavigationContainer, useNavigation } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { getFirstInstallTime } from 'react-native-device-info'
import AsyncStorage from '@react-native-async-storage/async-storage';

import * as ZIM from 'zego-zim-react-native';
import * as ZPNs from 'zego-zpns-react-native';
import ZegoUIKitPrebuiltCallService, {
  ZegoCallInvitationDialog, ZegoUIKitPrebuiltCallWaitingScreen, ZegoUIKitPrebuiltCallInCallScreen, ZegoSendCallInvitationButton,
} from '@zegocloud/zego-uikit-prebuilt-call-rn';

const Stack = createNativeStackNavigator();


const storeUserInfo = async (info) => {
  await AsyncStorage.setItem("userID", info.userID)
  await AsyncStorage.setItem("userName", info.userName)
}
const getUserInfo = async () => {
  try {
    const userID = await AsyncStorage.getItem("userID")
    const userName = await AsyncStorage.getItem("userName")
    if (userID == undefined) {
      return undefined
    } else {
      return { userID, userName }
    }
  } catch (e) {
    return undefined
  }
}

const onUserLogin = async (userID, userName) => {
  return ZegoUIKitPrebuiltCallService.init(
    yourAppID, // You can get it from ZEGOCLOUD's console
    yourAppSign, // You can get it from ZEGOCLOUD's console
    userID,
    userName,
    [ZIM, ZPNs],
    {
      ringtoneConfig: {
        incomingCallFileName: 'zego_incoming.mp3',
        outgoingCallFileName: 'zego_outgoing.mp3',
      },
      androidNotificationConfig: {
        channelID: "ZegoUIKit",
        channelName: "ZegoUIKit",
      },
    });
}

// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Step 1: Config React Navigation
export default function App() {
  return (
    <NavigationContainer >

      <ZegoCallInvitationDialog />

      <Stack.Navigator initialRouteName="HomeScreen">
        <Stack.Screen
          name="LoginScreen"
          component={LoginScreen}
        />
        <Stack.Screen
          name="HomeScreen"
          component={HomeScreen}
        />

        <Stack.Screen
          options={{ headerShown: false }}
          // DO NOT change the name 
          name="ZegoUIKitPrebuiltCallWaitingScreen"
          component={ZegoUIKitPrebuiltCallWaitingScreen}
        />
        <Stack.Screen
          options={{ headerShown: false }}
          // DO NOT change the name
          name="ZegoUIKitPrebuiltCallInCallScreen"
          component={ZegoUIKitPrebuiltCallInCallScreen}
        />

      </Stack.Navigator>
    </NavigationContainer>);
}

// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Step 2: Call the "ZegoUIKitPrebuiltCallService.init" method after the user login.
function LoginScreen(props) {
  const navigation = useNavigation();
  const [userID, setUserID] = useState('');
  const [userName, setUserName] = useState('');

  const loginHandler = () => {
    // Simulated login successful

    // Store user info to auto login
    storeUserInfo({ userID, userName })

    // Init the call service
    onUserLogin(userID, userName).then(() => {
      // Jump to HomeScreen to make new call
      navigation.navigate('HomeScreen', { userID });
    })
  }

  useEffect(() => {
    getFirstInstallTime().then(firstInstallTime => {
      const id = String(firstInstallTime).slice(-5);
      setUserID(id);
      const name = 'user_' + id
      setUserName(name);
    });
  }, [])

  return <View style={styles.container}>
    <View style={{ marginBottom: 30 }}>
      <Text>appID: {yourAppID}</Text>
      <Text>userID: {userID}</Text>
      <Text>userName: {userName}</Text>
    </View>
    <View style={{ width: 160 }}>
      <Button title='Login' onPress={loginHandler}></Button>
    </View>
  </View>;
}

// <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Step 3: Configure the "ZegoSendCallInvitationButton" to enable making calls. 
function HomeScreen({ route, navigation }) {
  const [userID, setUserID] = useState('')
  const [invitees, setInvitees] = useState([]);
  const viewRef = useRef(null);
  const blankPressedHandle = () => {
    viewRef.current.blur();
  };
  const changeTextHandle = value => {
    setInvitees(value ? value.split(',') : []);
  };


  useEffect(() => {
    // Simulated auto login if there is login info cache
    getUserInfo().then((info) => {
      if (info) {
        setUserID(info.userID)
        onUserLogin(info.userID, info.userName)
      } else {
        //  Back to the login screen if not login before
        navigation.navigate('LoginScreen');
      }
    })
  }, [])

  return (
    <TouchableWithoutFeedback onPress={blankPressedHandle}>
      <View style={styles.container}>
        <Text>Your user id: {userID}</Text>
        <View style={styles.inputContainer}>
          <TextInput
            ref={viewRef}
            style={styles.input}
            onChangeText={changeTextHandle}
            placeholder="Invitees ID, Separate ids by ','"
          />
          <ZegoSendCallInvitationButton
            invitees={invitees.map((inviteeID) => {
              return { userID: inviteeID, userName: 'user_' + inviteeID };
            })}
            isVideoCall={false}
            resourceID={"zego_call"}
          />
          <ZegoSendCallInvitationButton
            invitees={invitees.map((inviteeID) => {
              return { userID: inviteeID, userName: 'user_' + inviteeID };
            })}
            isVideoCall={true}
            resourceID={"zego_call"}
          />
        </View>
        <View style={{ width: 220, marginTop: 100 }}>
          <Button title='Back To Login Screen' onPress={() => { navigation.navigate('LoginScreen') }}></Button>
        </View>
      </View>
    </TouchableWithoutFeedback>
  );
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        alignItems: 'center',
        justifyContent: 'center',
        backgroundColor: "gray"
    },
    inputContainer: {
        flexDirection: 'row',
        alignItems: 'center',
        justifyContent: 'space-between',
    },
    input: {
        borderBottomWidth: 1,
        borderBottomColor: '#dddddd',
    },
});

Configure your project (Android)

1. Firebase Console and ZEGO Console Configuration

  • step1. In the Firebase console: Create a project. (Resource may help: Firebase Console)
  • step2. In the ZegoCloud console: Add FCM certificate, create a resource ID;

​In the create resource ID popup dialog, you should switch to the VoIP option for APNs, and switch to Data messages for FCM.

When you have completed the configuration, you will obtain the resourceID. You can refer to the image below for comparison.

After the above is completed, the resourceID property value of ZegoSendCallInvitationButton needs to be replaced with the resource ID you get.

One-on-one Group call
<ZegoSendCallInvitationButton
    invitees={[{userID: varUserID, userName: varUserName'}]}
    isVideoCall={true}
    resourceID={"zego_call"} // Please fill in the resource ID name that has been configured in the ZEGOCLOUD's console here.
/>
<ZegoSendCallInvitationButton
    invitees={invitees.map((inviteeID) => {
        return { userID: inviteeID, userName: 'user_' + inviteeID };
    })}
    isVideoCall={true}
    resourceID={"zego_call"} // Please fill in the resource ID name that has been configured in the ZEGOCLOUD's console here.
/>
  • step3. In the Firebase console: Create an Android application and modify your code;

2. Add app permissions

  1. Open the my_project/android/app/src/main/AndroidManifest.xml file and add the following:
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.VIBRATE"/>

<uses-permission android:name="android.permission.GET_TASKS" />
<uses-permission android:name="android.permission.BIND_TELECOM_CONNECTION_SERVICE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>
  1. Add a service tag with the property android:name="io.wazo.callkeep.VoiceConnectionService inside the application tag and add the following permissions.
<application
  android:name=".MainApplication"
  android:label="@string/app_name"
  android:icon="@mipmap/ic_launcher"
  android:roundIcon="@mipmap/ic_launcher_round"
  android:allowBackup="false"
  android:theme="@style/AppTheme">

  <service android:name="io.wazo.callkeep.VoiceConnectionService"
      android:label="Wazo"
      android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE"
      android:foregroundServiceType="camera|microphone"
      android:exported="true"
  >

      <intent-filter>
          <action android:name="android.telecom.ConnectionService" />
      </intent-filter>
  </service>
  ...
</application>

3. Prevent code obfuscation

Open the my_project/android/app/proguard-rules.pro file and add the following:

-keep class **.zego.**  { *; }
-keep class **.**.zego_zpns.** { *; }

4. Add firebase-messaging dependency

Add this line to your project's my_project/android/app/build.gradle file as instructed.

implementation 'com.google.firebase:firebase-messaging:21.1.0'

5. Set show when locked

To enable your app to be shown on the locked screen, call the setShowWhenLocked(true) in MainActivity.java:

// //////////////////////////
import android.os.Bundle;
import android.os.Build;
// //////////////////////////
...

public class MainActivity extends ReactActivity {
    // ...

  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // /////////////////////////
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O_MR1) {
      setShowWhenLocked(true);
    }
    // /////////////////////////
  }
}

6. Check whether the local config is set up properly.

python3 zego_check_android_offline_notification.py
  • You will see the following if everything goes well:
    ✅ The google-service.json is in the right location.
    ✅ The package name matches google-service.json.
    ✅ The project level gradle file is ready.
    ✅ The plugin config in the app-level gradle file is correct.
    ✅ Firebase dependencies config in the app-level gradle file is correct.
    ✅ Firebase-Messaging dependencies config in the app-level gradle file is correct.

7. Guide your users to set app permissions (Android only)

Some devices require special permissions to be enabled in order for your app to automatically display in the foreground when receiving a phone call (such as Xiaomi). Therefore, you need to guide your app users to enable the necessary app permissions to make the system-calling UI effective.

Here are the permissions that an app needs on Xiaomi devices for reference:

  • Make phone calls: Select the Always allow option

  • Show on locked screen: Switch to Enable

  • Display pop-up windows while running in the background: Switch to Enable

  • Display pop-up window: Switch to Enable

Configure your project (iOS)

1. Apple Developer Center and ZEGOCLOUD Console Configuration

  • step1. You need to refer to Create VoIP services certificates to create the VoIP service certificate, and ​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​export a .p12 file on your Mac.
  • step2. Add the voip service certificate .p12 file. Then, create a resource ID;

​In the create resource ID popup dialog, you should switch to the VoIP option for APNs, and switch to Data messages for FCM.

When you have completed the configuration, you will obtain the resourceID. You can refer to the image below for comparison.

2. Add app permissions

Open the my_project/ios/my_project/Info.plist file and add the following:

<key>NSCameraUsageDescription</key>
<string>We need to use the camera</string>
<key>NSMicrophoneUsageDescription</key>
<string>We need to use the microphone</string>

3. Add Push Notifications configuration

Open the project with Xcode, and click the+ Capability on the Signing & Capabilities page.

And double-click on Push Notifications to add this feature.

4. Add the Background Modes capability.

Open the project with Xcode, and click the+ Capability on the Signing & Capabilities page again.

And double-click on Background Modes in the pop-up window. This will allow you to see the Background Modes configuration in the Signing & Capabilities.

5. Check and Make sure the following features are enabled

6. Import the PushKit and CallKit libraries.

Run & Test

If your device is not performing well or you found a UI stuttering, run in Release mode for a smoother experience.

  • Run on an iOS device:
yarn npm
yarn ios
npm run ios
  • Run on an Android device:
yarn npm
yarn android
npm run android

FAQ

1. How to configure offline push certificates two APPs

I have two different apps that need to support offline calling with each other. How should I configure them?

  1. First, you need to configure the index 2 certificate for the second app in the ZEGOCLOUD Admin Console.
  1. Then, you need to set the certificateIndex of the second app to secondCertificate in the place where you initialize the ZegoUIKitPrebuiltCallService on the client side.
import * as ZIM from 'zego-zim-react-native';
import * as ZPNs from 'zego-zpns-react-native';
import ZegoUIKitPrebuiltCallService, { ZegoMultiCertificate } from '@zegocloud/zego-uikit-prebuilt-call-rn';

const onUserLogin = async (userID, userName, props) => {
  return ZegoUIKitPrebuiltCallService.init(
    yourAppID, // You can get it from ZEGOCLOUD's console
    yourAppSign, // You can get it from ZEGOCLOUD's console
    userID, // It can be any valid characters, but we recommend using a phone number.
    userName,
    [ZIM, ZPNs],
    {
        ringtoneConfig: {
            incomingCallFileName: 'zego_incoming.mp3',
            outgoingCallFileName: 'zego_outgoing.mp3',
        },
        androidNotificationConfig: {
            channelID: "ZegoUIKit",
            channelName: "ZegoUIKit",
        },
        certificateIndex: ZegoMultiCertificate.second,
    });
}

Please ensure that the certificates on the console correspond one-to-one with the certificateIndex in your code.

Resources

Page Directory