Call Kit
  • iOS
  • Android
  • Web
  • Flutter : Dart
  • React Native
  • Overview
  • Quick start
  • Custom prebuilt UI
  • Advanced features
  • Migrating to v3.0

Custom prebuilt UI

Last updated:2023-03-23 19:16

Call Kit (ZegoUIKitPrebuiltCall) provides a set of default behaviors and styles. If those don't fully meet your design or business needs, we allow you to flexibly customize the behavior and styles through configuration, as well as add components with custom business logic.

After getting started quickly, you'll know that when you create the ZegoUIKitPrebuiltCall, in addition to the required authentication parameter, there is also a ZegoUIKitPrebuiltCallConfig, which is used for custom configuration.

In this doc, we will describe these parameters in detail to help you do further customization.

Customize prebuilt UI components

Configure layouts

Call Kit (ZegoUIKitPrebuiltCall) currently supports picture-in-picture and gallery layout, each layout has its own configurations. To select and configure the layout you want, use the layout parameter in the ZegoUIKitPrebuiltCallConfig:

Picture-in-picture layout

The configurations supported by the picture-in-picture layout are:

  1. isSmallViewDraggable: Whether the position of the small view in the picture-in-picture layout can be changed by dragging. It’s allowed by default.
  2. switchLargeOrSmallViewByClick: Whether to allow users to click on the small view for switching between large view and small view. It’s allowed by default.

The effect is as follows:

Display my view when my camera is off Hide my view when my camera is off Dragging Switching

Here is the reference code:

Basic call With call invitation
class CallPage extends StatelessWidget {
  const CallPage({Key? key, required this.callID}) : super(key: key);
  final String callID;

  @override
  Widget build(BuildContext context) {
    return ZegoUIKitPrebuiltCall (
      appID: YourAppID,
      appSign: YourAppSign,
      userID: userID,
      userName: userName,
      callID: callID,

      // Modify your custom configurations here.
      config: ZegoUIKitPrebuiltCallConfig.oneOnOneVideoCall()
        ..layout = ZegoLayout.pictureInPicture(
          isSmallViewDraggable: true,
          switchLargeOrSmallViewByClick: true,
        ),
    );
  }
}
ZegoUIKitPrebuiltCallInvitationService().init(
  appID: YourAppID,
  appSign: YourAppSign,
  userID: userID,
  userName: userName,
  plugins: [ZegoUIKitSignalingPlugin()],
  requireConfig: (ZegoCallInvitationData data) {
    var config = (data.invitees.length > 1)
        ? ZegoCallType.videoCall == data.type
            ? ZegoUIKitPrebuiltCallConfig.groupVideoCall()
            : ZegoUIKitPrebuiltCallConfig.groupVoiceCall()
        : ZegoCallType.videoCall == data.type
            ? ZegoUIKitPrebuiltCallConfig.oneOnOneVideoCall()
            : ZegoUIKitPrebuiltCallConfig.oneOnOneVoiceCall();

    // Modify your custom configurations here.
    config.layout = ZegoLayout.pictureInPicture(
      isSmallViewDraggable: true,
      switchLargeOrSmallViewByClick: true,
    );
    return config;
  },
);

The configuration supported by the gallery layout is:

addBorderRadiusAndSpacingBetweenView: In gallery layout, this can be used to add border radius and spacing between speaker views. true: enabled (by default). false: disabled.

The effect is as follows:

Adding border radius and spacing Without border radius and spacing
Basic call With call invitation
class CallPage extends StatelessWidget {
  const CallPage({Key? key, required this.callID}) : super(key: key);
  final String callID;

  @override
  Widget build(BuildContext context) {
    return ZegoUIKitPrebuiltCall(
      appID: YourAppID,
      appSign: YourAppSign,
      userID: userID,
      userName: userName,
      callID: callID,

      // Modify your custom configurations here.
      config: ZegoUIKitPrebuiltCallConfig.groupVideoCall()
        ..layout = ZegoLayout.gallery(
          addBorderRadiusAndSpacingBetweenView: false,
        ),
    );
  }
}
ZegoUIKitPrebuiltCallInvitationService().init(
  appID: YourAppID,
  appSign: YourAppSign,
  userID: userID,
  userName: userName,
  plugins: [ZegoUIKitSignalingPlugin()],
  requireConfig: (ZegoCallInvitationData data) {
    var config = (data.invitees.length > 1)
        ? ZegoCallType.videoCall == data.type
            ? ZegoUIKitPrebuiltCallConfig.groupVideoCall()
            : ZegoUIKitPrebuiltCallConfig.groupVoiceCall()
        : ZegoCallType.videoCall == data.type
            ? ZegoUIKitPrebuiltCallConfig.oneOnOneVideoCall()
            : ZegoUIKitPrebuiltCallConfig.oneOnOneVoiceCall();

    // Modify your custom configurations here.
    config.layout = ZegoLayout.gallery(
      addBorderRadiusAndSpacingBetweenView: false,
    );
    return config;
  },
);

Customize user avatars

User avatars are generally stored in the server. Call Kit (ZegoUIKitPrebuiltCall) does not know the real profile picture of each user, so it uses the first letter of the username to draw the user avatars by default, as shown below:

When a user in silence When a user speaks

To configure the custom user avatars, you can use the avatarBuilder to set a custom builder method.

Here is the reference code:

Basic call With call invitation
class CallPage extends StatelessWidget {
  const CallPage({Key? key, required this.callID}) : super(key: key);

  final String callID;

  @override
  Widget build(BuildContext context) {
    return ZegoUIKitPrebuiltCall (
      appID: YourAppID,
      appSign: YourAppSign,
      userID: userID,
      userName: userName,
      callID: callID,

      // Modify your custom configurations here.
      config: ZegoUIKitPrebuiltCallConfig.oneOnOneVideoCall()
        ..avatarBuilder = (BuildContext context, Size size, ZegoUIKitUser? user, Map extraInfo) {
            return user != null
                ? Container(
                    decoration: BoxDecoration(
                      shape: BoxShape.circle,
                      image: DecorationImage(
                        image: NetworkImage(
                          'https://your_server/app/avatar/${user.id}.png',
                        ),
                      ),
                    ),
                  )
                : const SizedBox();
          },
    );
  }
}
ZegoUIKitPrebuiltCallInvitationService().init(
  appID: YourAppID,
  appSign: YourAppSign,
  userID: userID,
  userName: userName,
  plugins: [ZegoUIKitSignalingPlugin()],
  requireConfig: (ZegoCallInvitationData data) {
    var config = (data.invitees.length > 1)
        ? ZegoCallType.videoCall == data.type
            ? ZegoUIKitPrebuiltCallConfig.groupVideoCall()
            : ZegoUIKitPrebuiltCallConfig.groupVoiceCall()
        : ZegoCallType.videoCall == data.type
            ? ZegoUIKitPrebuiltCallConfig.oneOnOneVideoCall()
            : ZegoUIKitPrebuiltCallConfig.oneOnOneVoiceCall();

    config.avatarBuilder = (BuildContext context, Size size,
        ZegoUIKitUser? user, Map extraInfo) {
      return user != null
          ? Container(
              decoration: BoxDecoration(
                shape: BoxShape.circle,
                image: DecorationImage(
                  image: NetworkImage(
                    'https://your_server/app/avatar/${user.id}.png',
                  ),
                ),
              ),
            )
          : const SizedBox();
    };
    return config;
  },
);

When complete, the Call Kit (ZegoUIKitPrebuiltCall) displays the custom user avatar that you set.

Hide the prebuilt components

By default, the Call Kit (ZegoUIKitPrebuiltCall) displays UserNameLabel, MicrophoneStateIcon, and CameraStateIcon floating above the view. If these components are not needed, you can hide them using the following three configurations in audioVideoViewConfig.

Config Effects
1. showMicrophoneStateOnView: whether to display the microphone state on the view. Displayed by default.
2. showCameraStateOnView: Whether to display the camera state on the view. Displayed by default.
3. showUserNameOnView: Indicates whether to display the username on the view. Displayed by default.
/Pics/ZegoUIKit/Flutter/_normal_switch_30_label.png

Hide sound waves and user avatars

As shown in the rendering, the Call Kit (ZegoUIKitPrebuiltCall) displays the user's avatar and sound waves when the camera is turned off. If you are not satisfied with the user avatars and sound wave style, you can hide them using the following two configurations in audioVideoViewConfig:

Config Effects
1. showAvatarInAudioMode: Whether to display the user avatars in audio mode. Displayed by default.
2. showSoundWavesInAudioMode: in audio mode, whether to display the sound waves around the user avatar in audio mode. Displayed by default.
/Pics/ZegoUIKit/Flutter/_remote_close_switch_30_label.png

Customize the initial device state

When starting a call, the Call Kit (ZegoUIKitPrebuiltCall) turns on the camera, and microphone, and uses the speaker as the audio output device by default.

To change this default configuration, for example, turn off the camera when you start a call or don't use the speaker (If the speaker is not used, the system's default audio output device, such as ear speaker, headset, Bluetooth, etc., will be used.), you can modify the following configurations:

  1. turnOnCameraWhenJoining: Whether to turn on the camera when the call starts. true: turn on (by default). false: turn off.
  2. turnOnMicrophoneWhenJoining: Whether to turn on the microphone when the call starts. true: turn on (by default). false: turn off.
  3. useSpeakerWhenJoining: Whether to use the speaker when the call starts. true: use the speaker (by default). false: use the system's default audio output device, such as an ear speaker, headset, Bluetooth, etc.

Here is the reference code:

Basic call With call invitation
class CallPage extends StatelessWidget {
  const CallPage({Key? key, required this.callID}) : super(key: key);
  final String callID;

  @override
  Widget build(BuildContext context) {
    return ZegoUIKitPrebuiltCall(
      appID: YourAppID,
      appSign: YourAppSign,
      userID: userID,
      userName: userName,
      callID: callID,

      // Modify your custom configurations here.
      config: ZegoUIKitPrebuiltCallConfig.oneOnOneVideoCall()
        ..turnOnCameraWhenJoining = false
        ..turnOnMicrophoneWhenJoining = false
        ..useSpeakerWhenJoining = true,
    );
  }
}
ZegoUIKitPrebuiltCallInvitationService().init(
  appID: YourAppID,
  appSign: YourAppSign,
  userID: userID,
  userName: userName,
  plugins: [ZegoUIKitSignalingPlugin()],
  requireConfig: (ZegoCallInvitationData data) {
    var config = (data.invitees.length > 1)
        ? ZegoCallType.videoCall == data.type
            ? ZegoUIKitPrebuiltCallConfig.groupVideoCall()
            : ZegoUIKitPrebuiltCallConfig.groupVoiceCall()
        : ZegoCallType.videoCall == data.type
            ? ZegoUIKitPrebuiltCallConfig.oneOnOneVideoCall()
            : ZegoUIKitPrebuiltCallConfig.oneOnOneVoiceCall();

    // Modify your custom configurations here.
    config
      ..turnOnCameraWhenJoining = false
      ..turnOnMicrophoneWhenJoining = false
      ..useSpeakerWhenJoining = true;
    return config;
  },
);

Customize the menu bar button list

The Call Kit (ZegoUIKitPrebuiltCall) allows you to configure the buttons of the menu bar. To remove the default buttons or add custom ones to the bottom menu bar, you can configure the bottomMenuBarConfig:

(Similarly, to configure top menu bar buttons or add custom buttons to the top menu bar, use the topMenuBarConfig.)

  1. buttons: Built-in buttons placed in the menu bar. By default, all buttons are displayed. If you need to hide some buttons, use this to configure them.
  2. maxCount: Maximum number of buttons that can be displayed by the menu bar. Value range [1 - 5], the default value is 5.
  3. extendButtons: Use this configuration to add your own custom buttons to menuBar.

If the total number of built-in buttons and custom buttons does not exceed 5, all buttons will be displayed. Otherwise, other buttons that cannot be displayed will be hidden in the three dots (⋮) button. Clicking this button will pop up the bottom sheet to display the remaining buttons.

The effect will be like this:

/Pics/ZegoUIKit/Flutter/menuBarLimit.gif

Here is the reference code:

Basic call With call invitation
class CallPage extends StatelessWidget {
  const CallPage({Key? key, required this.callID}) : super(key: key);
  final String callID;

  @override
  Widget build(BuildContext context) {
    List<IconData> customIcons = [
      Icons.phone,
      Icons.cookie,
      Icons.speaker,
      Icons.air,
      Icons.blender,
      Icons.file_copy,
      Icons.place,
      Icons.phone_android,
      Icons.phone_iphone,
    ];

    return ZegoUIKitPrebuiltCall(
      appID: YourAppID,
      appSign: YourAppSign,
      userID: userID,
      userName: userName,
      callID: callID,

      // Modify your custom configurations here.
      config: ZegoUIKitPrebuiltCallConfig.oneOnOneVideoCall()
        ..bottomMenuBarConfig = ZegoBottomMenuBarConfig(
          maxCount: 5,
          extendButtons: [
            for (int i = 0; i < customIcons.length; i++)
              ElevatedButton(
                style: ElevatedButton.styleFrom(
                  fixedSize: const Size(60, 60),
                  shape: const CircleBorder(),
                  primary: controlBarButtonCheckedBackgroundColor,
                ),
                onPressed: () {},
                child: Icon(customIcons[i]),
              ),
          ],
          buttons: [
            ZegoMenuBarButtonName.toggleCameraButton,
            ZegoMenuBarButtonName.toggleMicrophoneButton,
            ZegoMenuBarButtonName.switchAudioOutputButton,
            ZegoMenuBarButtonName.hangUpButton,
            ZegoMenuBarButtonName.switchCameraButton,
          ],
        ),
    );
  }
}
ZegoUIKitPrebuiltCallInvitationService().init(
  appID: YourAppID,
  appSign: YourAppSign,
  userID: userID,
  userName: userName,
  plugins: [ZegoUIKitSignalingPlugin()],
  requireConfig: (ZegoCallInvitationData data) {
    var config = (data.invitees.length > 1)
        ? ZegoCallType.videoCall == data.type
            ? ZegoUIKitPrebuiltCallConfig.groupVideoCall()
            : ZegoUIKitPrebuiltCallConfig.groupVoiceCall()
        : ZegoCallType.videoCall == data.type
            ? ZegoUIKitPrebuiltCallConfig.oneOnOneVideoCall()
            : ZegoUIKitPrebuiltCallConfig.oneOnOneVoiceCall();

    // Modify your custom configurations here.
    List<IconData> customIcons = [
      Icons.phone,
      Icons.cookie,
      Icons.speaker,
      Icons.air,
      Icons.blender,
      Icons.file_copy,
      Icons.place,
      Icons.phone_android,
      Icons.phone_iphone,
    ];
    config.bottomMenuBarConfig = ZegoBottomMenuBarConfig(
      maxCount: 5,
      extendButtons: [
        for (int i = 0; i < customIcons.length; i++)
          ElevatedButton(
            style: ElevatedButton.styleFrom(
              fixedSize: const Size(60, 60),
              shape: const CircleBorder(),
              primary: controlBarButtonCheckedBackgroundColor,
            ),
            onPressed: () {},
            child: Icon(customIcons[i]),
          ),
      ],
      buttons: [
        ZegoMenuBarButtonName.toggleCameraButton,
        ZegoMenuBarButtonName.toggleMicrophoneButton,
        ZegoMenuBarButtonName.switchAudioOutputButton,
        ZegoMenuBarButtonName.hangUpButton,
        ZegoMenuBarButtonName.switchCameraButton,
      ],
    );
    return config;
  },
);

Customize the hidden behavior of the menu bar

The Call Kit (ZegoUIKitPrebuiltCall) supports automatic or manual hiding of the menu bar. You can control this by using the following two configurations in the ZegoBottomMenuBarConfig:

  1. hideByClick: Whether the menu bar can be hidden by clicking on the unresponsive area, enabled by default.
  2. hideAutomatically: Whether the menu bar is automatically hidden after 5 seconds of user inactivity, enabled by default.

Set a hangup confirmation dialog

Call Kit (ZegoUIKitPrebuilt) ends a call by default when the user clicks the End Call button or the Android’s Back button.

If you want to add a confirmation dialog box to double confirm whether the user wants to hang up a call, you can use the hangup config:

  1. hangUpConfirmInfo: After configuring the hangUpConfirmInfo parameter, ZegoUIKitPrebuilt will pop up a confirmation dialog box with the default style before ending the call, showing the confirmation info you set.

The effect will be like this:

/Pics/ZegoUIKit/Flutter/hangup_confirm.gif

Here is the reference code:

Basic call With call invitation
class CallPage extends StatelessWidget {
  const CallPage({Key? key, required this.callID}) : super(key: key);
  final String callID;

  @override
  Widget build(BuildContext context) {
    return ZegoUIKitPrebuiltCall(
      appID: YourAppID,
      appSign: YourAppSign,
      userID: userID,
      userName: userName,
      callID: callID,

      // Modify your custom configurations here.
      config: ZegoUIKitPrebuiltCallConfig.oneOnOneVideoCall()
        ..hangUpConfirmDialogInfo = ZegoHangUpConfirmDialogInfo(
          title: "Hangup confirm",
          message: "Do you want to hangup?",
          cancelButtonName: "Cancel",
          confirmButtonName: "Confirm",
        ),
    );
  }
}
ZegoUIKitPrebuiltCallInvitationService().init(
  appID: YourAppID,
  appSign: YourAppSign,
  userID: userID,
  userName: userName,
  plugins: [ZegoUIKitSignalingPlugin()],
  requireConfig: (ZegoCallInvitationData data) {
    var config = (data.invitees.length > 1)
        ? ZegoCallType.videoCall == data.type
            ? ZegoUIKitPrebuiltCallConfig.groupVideoCall()
            : ZegoUIKitPrebuiltCallConfig.groupVoiceCall()
        : ZegoCallType.videoCall == data.type
            ? ZegoUIKitPrebuiltCallConfig.oneOnOneVideoCall()
            : ZegoUIKitPrebuiltCallConfig.oneOnOneVoiceCall();

    // Modify your custom configurations here.
    config.hangUpConfirmDialogInfo = ZegoHangUpConfirmDialogInfo(
      title: "Hangup confirm",
      message: "Do you want to hangup?",
      cancelButtonName: "Cancel",
      confirmButtonName: "Confirm",
    );
    return config;
  },
);

If the default dialog style can’t meet your needs, or you want to pop up a more complex dialog, then you can use the onHangUpConfirmation parameter. The onHangUpConfirmation is a callback that can be used together with the showDialog of Flutter. And sure, you can also implement the logic that decides whether to end the call or not, or any other business logic in this callback as wanted.

Here is the reference code:

Basic call With call invitation
class CallPage extends StatelessWidget {
  const CallPage({Key? key, required this.callID}) : super(key: key);
  final String callID;

  @override
  Widget build(BuildContext context) {
    return ZegoUIKitPrebuiltCall(
      appID: YourAppID,
      appSign: YourAppSign,
      userID: userID,
      userName: userName,
      callID: callID,

      // Modify your custom configurations here.
      config: ZegoUIKitPrebuiltCallConfig.oneOnOneVideoCall()
        ..onHangUpConfirmation = (BuildContext context) async {
          return await showDialog(
            context: context,
            barrierDismissible: false,
            builder: (BuildContext context) {
              return AlertDialog(
                backgroundColor: Colors.blue[900]!.withOpacity(0.9),
                title: const Text("This is your custom dialog",
                    style: TextStyle(color: Colors.white70)),
                content: const Text(
                    "You can customize this dialog however you like",
                    style: TextStyle(color: Colors.white70)),
                actions: [
                  ElevatedButton(
                    child: const Text("Cancel",
                        style: TextStyle(color: Colors.white70)),
                    onPressed: () => Navigator.of(context).pop(false),
                  ),
                  ElevatedButton(
                    child: const Text("Exit"),
                    onPressed: () => Navigator.of(context).pop(true),
                  ),
                ],
              );
            },
          );
        },
    );
  }
}
ZegoUIKitPrebuiltCallInvitationService().init(
  appID: YourAppID,
  appSign: YourAppSign,
  userID: userID,
  userName: userName,
  plugins: [ZegoUIKitSignalingPlugin()],
  requireConfig: (ZegoCallInvitationData data) {
    var config = (data.invitees.length > 1)
        ? ZegoCallType.videoCall == data.type
            ? ZegoUIKitPrebuiltCallConfig.groupVideoCall()
            : ZegoUIKitPrebuiltCallConfig.groupVoiceCall()
        : ZegoCallType.videoCall == data.type
            ? ZegoUIKitPrebuiltCallConfig.oneOnOneVideoCall()
            : ZegoUIKitPrebuiltCallConfig.oneOnOneVoiceCall();

    // Modify your custom configurations here.
    config.onHangUpConfirmation = (BuildContext context) async {
      return await showDialog(
        context: context,
        barrierDismissible: false,
        builder: (BuildContext context) {
          return AlertDialog(
            backgroundColor: Colors.blue[900]!.withOpacity(0.9),
            title: const Text("This is your custom dialog",
                style: TextStyle(color: Colors.white70)),
            content: const Text(
                "You can customize this dialog however you like",
                style: TextStyle(color: Colors.white70)),
            actions: [
              ElevatedButton(
                child: const Text("Cancel",
                    style: TextStyle(color: Colors.white70)),
                onPressed: () => Navigator.of(context).pop(false),
              ),
              ElevatedButton(
                child: const Text("Exit"),
                onPressed: () => Navigator.of(context).pop(true),
              ),
            ],
          );
        },
      );
    };
    return config;
  },
);

The effect of a custom dialog will be like this:

/Pics/ZegoUIKit/Flutter/hangup_custom.gif

If you want to listen for hang-up events, for example, to save the call recording when ending the call, ZegoUIKitPrebuiltCall provides an onHangUp callback that will be triggered when the call ends. And sure, you can also implement custom business logic in the onHangUp.

Customize the view in audio mode

If you need to customize the user's view in audio mode, for example, setting the background image, you can use backgroundBuilder in audioVideoViewConfig. This callback, similar to other Flutter’s Builder callbacks, requires you (the developer) to return a custom Widget that will be placed in the view in audio mode.

This config is only valid when the user turns off the camera (because the video view will be displayed automatically when the camera is on). The position of the Widget can be specified by using the Flutter Positioned.

Here shows [How to use a Gaussian Blur user image as the background image in audio mode]:

Basic call With call invitation
class CallPage extends StatelessWidget {
  const CallPage({Key? key, required this.callID}) : super(key: key);
  final String callID;

  @override
  Widget build(BuildContext context) {
    return ZegoUIKitPrebuiltCall(
      appID: YourAppID,
      appSign: YourAppSign,
      userID: userID,
      userName: userName,
      callID: callID,

      // Modify your custom configurations here.
      config: ZegoUIKitPrebuiltCallConfig.oneOnOneVideoCall()
        ..audioVideoViewConfig = ZegoPrebuiltAudioVideoViewConfig(
          backgroundBuilder: (BuildContext context, Size size, ZegoUIKitUser? user, Map extraInfo) {
            return user != null
                ? ImageFiltered(
                    imageFilter: ImageFilter.blur(sigmaX: 5, sigmaY: 5),
                    child: Image(
                      image: NetworkImage(
                        'https://your_server/app/user_image/${user.id}.png',
                      ),
                    ),
                  )
                : const SizedBox();
          },
        ),
    );
  }
}
ZegoUIKitPrebuiltCallInvitationService().init(
  appID: YourAppID,
  appSign: YourAppSign,
  userID: userID,
  userName: userName,
  plugins: [ZegoUIKitSignalingPlugin()],
  requireConfig: (ZegoCallInvitationData data) {
    var config = (data.invitees.length > 1)
        ? ZegoCallType.videoCall == data.type
            ? ZegoUIKitPrebuiltCallConfig.groupVideoCall()
            : ZegoUIKitPrebuiltCallConfig.groupVoiceCall()
        : ZegoCallType.videoCall == data.type
            ? ZegoUIKitPrebuiltCallConfig.oneOnOneVideoCall()
            : ZegoUIKitPrebuiltCallConfig.oneOnOneVoiceCall();

    // Modify your custom configurations here.
    config.audioVideoViewConfig = ZegoPrebuiltAudioVideoViewConfig(
      backgroundBuilder: (BuildContext context, Size size,
          ZegoUIKitUser? user, Map extraInfo) {
        return user != null
            ? ImageFiltered(
                imageFilter: ImageFilter.blur(sigmaX: 5, sigmaY: 5),
                child: Image(
                  image: NetworkImage(
                    'https://your_server/app/user_image/${user.id}.png',
                  ),
                ),
              )
            : const SizedBox();
      },
    );
    return config;
  },
);

The effect will be like this:

/Pics/ZegoUIKit/Flutter/blur.gif

Customize the foreground view

If you want to add some custom components at the top level of the view, such as, you want to display the user avatars when the video view is displayed, add user-level icons, etc., then you can use foregroundBuilder in audioVideoViewConfig. This callback, similar to other Flutter’s Builder callbacks, requires you (the developer) to return a custom Widget that will be placed at the top of the view.

The position of the Widget can be specified by using the Flutter Positioned.

Here shows [How to add a user avatar to the lower left corner during a video call]:

Basic call With call invitation
class CallPage extends StatelessWidget {
  const CallPage({Key? key, required this.callID}) : super(key: key);
  final String callID;

  @override
  Widget build(BuildContext context) {
    return ZegoUIKitPrebuiltCall(
      appID: YourAppID,
      appSign: YourAppSign,
      userID: userID,
      userName: userName,
      callID: callID,

      // Modify your custom configurations here.
      config: ZegoUIKitPrebuiltCallConfig.oneOnOneVideoCall()
        ..audioVideoViewConfig = ZegoPrebuiltAudioVideoViewConfig(
          foregroundBuilder: (BuildContext context, Size size, ZegoUIKitUser? user, Map extraInfo) {
            return user != null
                ? Positioned(
                    bottom: 5,
                    left: 5,
                    child: Container(
                      width: 30,
                      height: 30,
                      decoration: BoxDecoration(
                        shape: BoxShape.circle,
                        image: DecorationImage(
                          image: NetworkImage(
                            'https://your_server/app/avatar/${user.id}.png',
                          ),
                        ),
                      ),
                    ),
                  )
                : const SizedBox();
          },
        ),
    );
  }
}
ZegoUIKitPrebuiltCallInvitationService().init(
  appID: YourAppID,
  appSign: YourAppSign,
  userID: userID,
  userName: userName,
  plugins: [ZegoUIKitSignalingPlugin()],
  requireConfig: (ZegoCallInvitationData data) {
    var config = (data.invitees.length > 1)
        ? ZegoCallType.videoCall == data.type
            ? ZegoUIKitPrebuiltCallConfig.groupVideoCall()
            : ZegoUIKitPrebuiltCallConfig.groupVoiceCall()
        : ZegoCallType.videoCall == data.type
            ? ZegoUIKitPrebuiltCallConfig.oneOnOneVideoCall()
            : ZegoUIKitPrebuiltCallConfig.oneOnOneVoiceCall();

    // Modify your custom configurations here.
    config.audioVideoViewConfig = ZegoPrebuiltAudioVideoViewConfig(
      foregroundBuilder: (BuildContext context, Size size,
          ZegoUIKitUser? user, Map extraInfo) {
        return user != null
            ? Positioned(
                bottom: 5,
                left: 5,
                child: Container(
                  width: 30,
                  height: 30,
                  decoration: BoxDecoration(
                    shape: BoxShape.circle,
                    image: DecorationImage(
                      image: NetworkImage(
                        'https://your_server/app/avatar/${user.id}.png',
                      ),
                    ),
                  ),
                ),
              )
            : const SizedBox();
      },
    );
    return config;
  },
);

The effect is as follows:

When the camera is on When the camera is off

As you can see, the custom component is displayed at the top regardless of whether the camera is on or off. If you want to display custom widgets only when the camera is on, you'll need to build different views depending on the user's camera status. As it happens, the Call Kit provides an out-of-box ValueNotifier, including the CameraStateNotifier which can be used to monitor the user's camera status.

Try to customize the reference code with Flutter's ValueListenableBuilder:

Basic call With call invitation
class CallPage extends StatelessWidget {
  const CallPage({Key? key, required this.callID}) : super(key: key);
  final String callID;

  @override
  Widget build(BuildContext context) {
    return ZegoUIKitPrebuiltCall(
      appID: YourAppID,
      appSign: YourAppSign,
      userID: userID,
      userName: userName,
      callID: callID,

      // Modify your custom configurations here.
      config: ZegoUIKitPrebuiltCallConfig.oneOnOneVideoCall()
        ..audioVideoViewConfig = ZegoPrebuiltAudioVideoViewConfig(
          foregroundBuilder: (BuildContext context, Size size, ZegoUIKitUser? user, Map extraInfo) {
            return user != null
                ? ValueListenableBuilder<bool>(
                    valueListenable:
                        ZegoUIKit().getCameraStateNotifier(user.id),
                    builder: (context, isCameraOn, child) {
                      return isCameraOn ? child! : const SizedBox();
                    },
                    child: Positioned(
                      bottom: 5,
                      left: 5,
                      child: Container(
                        width: 30,
                        height: 30,
                        decoration: BoxDecoration(
                          shape: BoxShape.circle,
                          image: DecorationImage(
                            image: NetworkImage(
                              'https://your_server/app/avatar/${user.id}.png',
                            ),
                          ),
                        ),
                      ),
                    ),
                  )
                : const SizedBox();
          },
        ),
    );
  }
}
ZegoUIKitPrebuiltCallInvitationService().init(
  appID: YourAppID,
  appSign: YourAppSign,
  userID: userID,
  userName: userName,
  plugins: [ZegoUIKitSignalingPlugin()],
  requireConfig: (ZegoCallInvitationData data) {
    var config = (data.invitees.length > 1)
        ? ZegoCallType.videoCall == data.type
            ? ZegoUIKitPrebuiltCallConfig.groupVideoCall()
            : ZegoUIKitPrebuiltCallConfig.groupVoiceCall()
        : ZegoCallType.videoCall == data.type
            ? ZegoUIKitPrebuiltCallConfig.oneOnOneVideoCall()
            : ZegoUIKitPrebuiltCallConfig.oneOnOneVoiceCall();

    // Modify your custom configurations here.
    config.audioVideoViewConfig = ZegoPrebuiltAudioVideoViewConfig(
      foregroundBuilder: (BuildContext context, Size size,
          ZegoUIKitUser? user, Map extraInfo) {
        return user != null
            ? ValueListenableBuilder<bool>(
                valueListenable: ZegoUIKit().getCameraStateNotifier(user.id),
                builder: (context, isCameraOn, child) {
                  return isCameraOn ? child! : const SizedBox();
                },
                child: Positioned(
                  bottom: 5,
                  left: 5,
                  child: Container(
                    width: 30,
                    height: 30,
                    decoration: BoxDecoration(
                      shape: BoxShape.circle,
                      image: DecorationImage(
                        image: NetworkImage(
                          'https://your_server/app/avatar/${user.id}.png',
                        ),
                      ),
                    ),
                  ),
                ),
              )
            : const SizedBox();
      },
    );
    return config;
  },
);

The effect after modification is as follows:

When the camera is on When the camera is off

Call invitation config

Customize the call ringtone

Ringtone for call invitation

If you want to set the call ringtone for receiving incoming call invitations or sending outgoing call invitations, you can use the ringtoneConfig in the ZegoUIKitPrebuiltCallInvitationService's init function.

Here is the reference code:

With call invitation
ZegoUIKitPrebuiltCallInvitationService().init(
  appID: YourAppID,
  appSign: YourAppSign,
  userID: userID,
  userName: userName,
  plugins: [ZegoUIKitSignalingPlugin()],
  // Modify your custom configurations here.
  ringtoneConfig: const ZegoRingtoneConfig(
    incomingCallPath: "assets/ringtone/incomingCallRingtone.mp3",
    outgoingCallPath: "assets/ringtone/outgoingCallRingtone.mp3",
  ),
);

Ringtone for offline call invitation

To specify a ringtone for offline call invitations:

  1. Follow the steps in Quick start to enable and complete the congirations.
  2. Go to ZEGOCLOUD Admin Console to configure a Push Resource ID.
  3. Replace the resourceID parameter in the ZegoSendCallInvitationButton method with the Push Resource ID you get.

After the completion, the ringtone corresponding to the ID you set will be played when others receive offline call invitations.

Here is the reference code:

With call invitation

ZegoSendCallInvitationButton(
   isVideoCall: true,
   resourceID: "zegouikit_call",    // For offline call notification
   invitees: [
      ...
   ],
)

Hide the decline button

To hide the Decline button when receiving incoming call invitations, set the showDeclineButton to false.

With call invitation
ZegoUIKitPrebuiltCallInvitationService().init(
  appID: YourAppID,
  appSign: YourAppSign,
  userID: userID,
  userName: userName,
  plugins: [ZegoUIKitSignalingPlugin()],
  showDeclineButton: false,
);

Auto re-send call invitation after call timeout

To automatically re-send a call invite after the call times out, do the following:

  1. Listen to the onOutgoingCallTimeout callback of ZegoUIKitPrebuiltCallInvitationEvents. This callback will be triggered when the call times out.
  2. Call the sendCallInvitation method of ZegoUIKitPrebuiltCallController to call send a call invite.
With call invitation
  ///    Declare variables that you need to use, and make sure to manage their lifecycles
  const maxRetryCount = 2;
  final Map<String, int> calleesRetryCount = {};
  final callInvitationController = ZegoUIKitPrebuiltCallController();

  ///    Listen to the onOutgoingCallTimeout during initialization.
  ZegoUIKitPrebuiltCallInvitationService().init(
    appID: YourAppID,
    appSign: YourAppSign,
    userID: userID,
    userName: userName,
    notifyWhenAppRunningInBackgroundOrQuit: false,
    plugins: [ZegoUIKitSignalingPlugin()],
    controller: callInvitationController,
    events: ZegoUIKitPrebuiltCallInvitationEvents(
      onOutgoingCallTimeout: (
        String callID,
        List<ZegoCallUser> callees,
        bool isVideoCall,
      ) async {
        ///  Set the maximum retry count. If it exceeds, no more call invitations will be sent.
        final retryCount = calleesRetryCount[callID] ?? 0;
        if (retryCount >= maxRetryCount) {
          calleesRetryCount.remove(callID);
          return;
        }

        calleesRetryCount[callID] = retryCount + 1;

        ///  Re-send call invitation
        await callInvitationController.sendCallInvitation(
          callID: callID,
          invitees: callees
              .map((callee) => ZegoCallUser(callee.id, 'user_${callee.id}'))
              .toList(),
          isVideoCall: isVideoCall,
        );
      },
    ),
  );

Modify User Interface text

Call Kit (ZegoUIKitPrebuiltCallInvitationService)'s UI text provided by the internal components is editable, to modify those, use the innerText config.

Here is the reference code:

With call invitation
  ZegoUIKitPrebuiltCallInvitationService()
    ..innerText.incomingCallPageAcceptButton = "Accept"
    ..innerText.incomingCallPageDeclineButton = "Decline";

Listen for call invitation event callbacks

You can implement further business logic by listening for event callbacks related to the call invitation.

The following are supported event callbacks:

  • Function() onIncomingCallDeclineButtonPressed: This callback will be triggered when the Decline button is pressed (the callee declines the call invitation).
  • Function() onIncomingCallAcceptButtonPressed: This callback will be triggered when the Accept button is pressed (the callee accepts the call invitation).

  • Function(String callID, ZegoCallUser caller, ZegoCallType callType, List< ZegoCallUser > callees) onIncomingCallReceived: This callback will be triggered when receiving call invitations.

  • Function(String callID, ZegoCallUser caller) onIncomingCallCanceled: This callback will be triggered when the caller cancels the call invitation.

  • Function(String callID, ZegoCallUser caller) onIncomingCallTimeout: The callee will receive a notification through this callback when the callee doesn't respond to the call invitation after a timeout duration.

  • Function() onOutgoingCallCancelButtonPressed: This callback will be triggered when the Cancel button is pressed (the caller cancels the call invitation).

  • Function(String callID, ZegoCallUser callee) onOutgoingCallAccepted: The caller will receive a notification through this callback when the callee accepts the call invitation.

  • Function(String callID, ZegoCallUser callee) onOutgoingCallRejectedCauseBusy: The caller will receive a notification through this callback when the callee rejects the call invitation (the callee is busy).

  • Function(String callID, ZegoCallUser callee) onOutgoingCallDeclined: The caller will receive a notification through this callback when the callee declines the call invitation actively.

  • Function(String callID, List< ZegoCallUser > callees) onOutgoingCallTimeout: The caller will receive a notification through this callback when the call invitation didn't get responses after a timeout duration.

Here is the reference code:

With call invitation
ZegoUIKitPrebuiltCallInvitationService().init(
  appID: YourAppID,
  appSign: YourAppSign,
  userID: userID,
  userName: userName,
  events: ZegoUIKitPrebuiltCallInvitationEvents(
    onIncomingCallDeclineButtonPressed: () {
      ///  Add your custom logic here.
    },
    onIncomingCallAcceptButtonPressed: () {
      ///  Add your custom logic here.
    },
    onIncomingCallReceived: (String callID, ZegoCallUser caller,
        ZegoCallType callType, List<ZegoCallUser> callees) {
      ///  Add your custom logic here.
    },
    onIncomingCallCanceled: (String callID, ZegoCallUser caller) {
      ///  Add your custom logic here.
    },
    onIncomingCallTimeout: (String callID, ZegoCallUser caller) {
      ///  Add your custom logic here.
    },
    onOutgoingCallCancelButtonPressed: () {
      ///  Add your custom logic here.
    },
    onOutgoingCallAccepted: (String callID, ZegoCallUser callee) {
      ///  Add your custom logic here.
    },
    onOutgoingCallRejectedCauseBusy: (String callID, ZegoCallUser callee) {
      ///  Add your custom logic here.
    },
    onOutgoingCallDeclined: (String callID, ZegoCallUser callee) {
      ///  Add your custom logic here.
    },
    onOutgoingCallTimeout: (String callID, List<ZegoCallUser> callees) {
      ///  Add your custom logic here.
    },
  ),
  plugins: [ZegoUIKitSignalingPlugin()],
);