Skip to main content
Intercept UI button clicks with ButtonClickListeners. This listener provides callbacks when users tap buttons in the call UI, allowing you to implement custom behavior or show confirmation dialogs.

Prerequisites

Register Listener

Register a ButtonClickListeners to receive button click callbacks:
final callSession = CallSession.getInstance();

callSession?.addButtonClickListener(ButtonClickListeners(
  onLeaveSessionButtonClicked: () {
    debugPrint("Leave button clicked");
  },
  onToggleAudioButtonClicked: () {
    debugPrint("Audio toggle button clicked");
  },
  onToggleVideoButtonClicked: () {
    debugPrint("Video toggle button clicked");
  },
  // Additional callbacks...
));
Flutter listeners are not lifecycle-aware. You must manually remove listeners in your widget’s dispose() method to prevent memory leaks.

Callbacks

onLeaveSessionButtonClicked

Triggered when the user taps the leave session button.
onLeaveSessionButtonClicked: () {
  debugPrint("Leave button clicked");
  // Show confirmation dialog before leaving
  showDialog(
    context: context,
    builder: (context) => AlertDialog(
      title: const Text("Leave Call"),
      content: const Text("Are you sure you want to leave this call?"),
      actions: [
        TextButton(
          onPressed: () => Navigator.pop(context),
          child: const Text("Cancel"),
        ),
        TextButton(
          onPressed: () async {
            Navigator.pop(context);
            await CallSession.getInstance()?.leaveSession();
          },
          child: const Text("Leave"),
        ),
      ],
    ),
  );
}
Use Cases:
  • Show confirmation dialog before leaving
  • Log analytics event
  • Perform cleanup before leaving

onToggleAudioButtonClicked

Triggered when the user taps the audio mute/unmute button.
onToggleAudioButtonClicked: () {
  debugPrint("Audio toggle clicked");
  // Track audio toggle analytics
}
Use Cases:
  • Log analytics events
  • Show tooltip on first use
  • Implement custom audio toggle logic

onToggleVideoButtonClicked

Triggered when the user taps the video on/off button.
onToggleVideoButtonClicked: () {
  debugPrint("Video toggle clicked");
  // Track video toggle analytics
}
Use Cases:
  • Log analytics events
  • Check camera permissions
  • Implement custom video toggle logic

onSwitchCameraButtonClicked

Triggered when the user taps the switch camera button.
onSwitchCameraButtonClicked: () {
  debugPrint("Switch camera clicked");
  // Track camera switch analytics
}
Use Cases:
  • Log analytics events
  • Show camera switching animation
  • Track front/back camera usage

onRaiseHandButtonClicked

Triggered when the user taps the raise hand button.
onRaiseHandButtonClicked: () {
  debugPrint("Raise hand clicked");
  // Show hand raised confirmation
  ScaffoldMessenger.of(context).showSnackBar(
    const SnackBar(content: Text("Hand raised")),
  );
}
Use Cases:
  • Show confirmation feedback
  • Log analytics events
  • Implement custom hand raise behavior

onShareInviteButtonClicked

Triggered when the user taps the share invite button.
onShareInviteButtonClicked: () {
  debugPrint("Share invite clicked");
  // Show custom share dialog
  Share.share("Join my call: https://example.com/call/$sessionId");
}
Use Cases:
  • Show custom share sheet
  • Generate and share invite link
  • Copy link to clipboard

onChangeLayoutButtonClicked

Triggered when the user taps the change layout button.
onChangeLayoutButtonClicked: () {
  debugPrint("Change layout clicked");
  // Show layout options dialog
  showDialog(
    context: context,
    builder: (context) => SimpleDialog(
      title: const Text("Select Layout"),
      children: [
        SimpleDialogOption(
          onPressed: () async {
            Navigator.pop(context);
            await CallSession.getInstance()?.setLayout(LayoutType.tile);
          },
          child: const Text("Tile"),
        ),
        SimpleDialogOption(
          onPressed: () async {
            Navigator.pop(context);
            await CallSession.getInstance()?.setLayout(LayoutType.spotlight);
          },
          child: const Text("Spotlight"),
        ),
      ],
    ),
  );
}
Use Cases:
  • Show custom layout picker
  • Log layout change analytics
  • Implement custom layout switching

onParticipantListButtonClicked

Triggered when the user taps the participant list button.
onParticipantListButtonClicked: () {
  debugPrint("Participant list clicked");
  // Track participant list views
}
Use Cases:
  • Log analytics events
  • Show custom participant list UI
  • Track feature usage

onChatButtonClicked

Triggered when the user taps the chat button.
onChatButtonClicked: () {
  debugPrint("Chat button clicked");
  // Open custom chat UI
  Navigator.push(
    context,
    MaterialPageRoute(
      builder: (context) => ChatScreen(sessionId: sessionId),
    ),
  );
}
Use Cases:
  • Open custom chat interface
  • Show in-call messaging overlay
  • Navigate to chat screen

onRecordingToggleButtonClicked

Triggered when the user taps the recording button.
onRecordingToggleButtonClicked: () {
  debugPrint("Recording toggle clicked");
  // Show recording consent dialog
  showDialog(
    context: context,
    builder: (context) => AlertDialog(
      title: const Text("Start Recording"),
      content: const Text(
        "All participants will be notified that this call is being recorded.",
      ),
      actions: [
        TextButton(
          onPressed: () => Navigator.pop(context),
          child: const Text("Cancel"),
        ),
        TextButton(
          onPressed: () async {
            Navigator.pop(context);
            await CallSession.getInstance()?.startRecording();
          },
          child: const Text("Start"),
        ),
      ],
    ),
  );
}
Use Cases:
  • Show recording consent dialog
  • Check recording permissions
  • Log recording analytics

Complete Example

Here’s a complete example handling all button click events:
import 'package:cometchat_calls_sdk/cometchat_calls_sdk.dart';
import 'package:flutter/material.dart';

class CallScreen extends StatefulWidget {
  final String sessionId;

  const CallScreen({super.key, required this.sessionId});

  @override
  State<CallScreen> createState() => _CallScreenState();
}

class _CallScreenState extends State<CallScreen> {
  CallSession? _callSession;
  ButtonClickListeners? _buttonClickListener;

  @override
  void initState() {
    super.initState();
    _callSession = CallSession.getInstance();
    _setupButtonClickListener();
  }

  void _setupButtonClickListener() {
    _buttonClickListener = ButtonClickListeners(
      onLeaveSessionButtonClicked: () {
        _showLeaveConfirmationDialog();
      },
      onToggleAudioButtonClicked: () {
        debugPrint("Audio toggle clicked");
      },
      onToggleVideoButtonClicked: () {
        debugPrint("Video toggle clicked");
      },
      onSwitchCameraButtonClicked: () {
        debugPrint("Switch camera clicked");
      },
      onRaiseHandButtonClicked: () {
        if (mounted) {
          ScaffoldMessenger.of(context).showSnackBar(
            const SnackBar(content: Text("Hand raised")),
          );
        }
      },
      onShareInviteButtonClicked: () {
        _shareCallLink();
      },
      onChangeLayoutButtonClicked: () {
        _showLayoutOptionsDialog();
      },
      onParticipantListButtonClicked: () {
        debugPrint("Participant list opened");
      },
      onChatButtonClicked: () {
        _openChatScreen();
      },
      onRecordingToggleButtonClicked: () {
        _showRecordingConsentDialog();
      },
    );

    _callSession?.addButtonClickListener(_buttonClickListener!);
  }

  void _showLeaveConfirmationDialog() {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text("Leave Call"),
        content: const Text("Are you sure you want to leave?"),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context),
            child: const Text("Cancel"),
          ),
          TextButton(
            onPressed: () async {
              Navigator.pop(context);
              await _callSession?.leaveSession();
            },
            child: const Text("Leave"),
          ),
        ],
      ),
    );
  }

  void _shareCallLink() {
    // Use share_plus or similar package
    debugPrint("Share call link: ${widget.sessionId}");
  }

  void _showLayoutOptionsDialog() {
    showDialog(
      context: context,
      builder: (context) => SimpleDialog(
        title: const Text("Select Layout"),
        children: [
          SimpleDialogOption(
            onPressed: () async {
              Navigator.pop(context);
              await _callSession?.setLayout(LayoutType.tile);
            },
            child: const Text("Tile"),
          ),
          SimpleDialogOption(
            onPressed: () async {
              Navigator.pop(context);
              await _callSession?.setLayout(LayoutType.spotlight);
            },
            child: const Text("Spotlight"),
          ),
        ],
      ),
    );
  }

  void _openChatScreen() {
    // Navigate to chat screen
    debugPrint("Open chat for session: ${widget.sessionId}");
  }

  void _showRecordingConsentDialog() {
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text("Start Recording"),
        content: const Text("All participants will be notified."),
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context),
            child: const Text("Cancel"),
          ),
          TextButton(
            onPressed: () async {
              Navigator.pop(context);
              await _callSession?.startRecording();
            },
            child: const Text("Start"),
          ),
        ],
      ),
    );
  }

  @override
  void dispose() {
    // Must manually remove listener to prevent memory leaks
    if (_buttonClickListener != null) {
      _callSession?.removeButtonClickListener(_buttonClickListener!);
    }
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return const Scaffold(
      body: Center(
        child: Text("Call UI"),
      ),
    );
  }
}

Remove Listener

Remove the listener in your widget’s dispose() method to prevent memory leaks:
@override
void dispose() {
  if (_buttonClickListener != null) {
    _callSession?.removeButtonClickListener(_buttonClickListener!);
  }
  super.dispose();
}

Callbacks Summary

CallbackDescription
onLeaveSessionButtonClickedLeave session button was tapped
onToggleAudioButtonClickedAudio mute/unmute button was tapped
onToggleVideoButtonClickedVideo on/off button was tapped
onSwitchCameraButtonClickedSwitch camera button was tapped
onRaiseHandButtonClickedRaise hand button was tapped
onShareInviteButtonClickedShare invite button was tapped
onChangeLayoutButtonClickedChange layout button was tapped
onParticipantListButtonClickedParticipant list button was tapped
onChatButtonClickedChat button was tapped
onRecordingToggleButtonClickedRecording toggle button was tapped

Hide Buttons

You can hide specific buttons using SessionSettings:
final sessionSettings = CometChatCalls.SessionSettingsBuilder()
    .hideLeaveSessionButton(false)
    .hideToggleAudioButton(false)
    .hideToggleVideoButton(false)
    .hideSwitchCameraButton(false)
    .hideRaiseHandButton(false)
    .hideShareInviteButton(true)
    .hideChangeLayoutButton(false)
    .hideParticipantListButton(false)
    .hideChatButton(true)
    .hideRecordingButton(true)
    .build();

Next Steps

Layout Listener

Handle layout change events

Session Settings

Configure button visibility