CometChat Calls SDK v5 — Custom UI (Flutter)
Overview
Build custom call controls by hiding the default SDK UI and using CallSession methods programmatically. Overlay your own Flutter widgets on top of the call view.
Key Imports
import 'package:cometchat_calls_sdk/cometchat_calls_sdk.dart';
Implementation
Hide Default UI
final settings = SessionSettingsBuilder()
.hideControlPanel(true) // Hide bottom control bar
.hideHeaderPanel(true) // Hide top header
.build();
Custom Control Panel
class CustomCallScreen extends StatefulWidget {
final String sessionId;
const CustomCallScreen({super.key, required this.sessionId});
@override
State<CustomCallScreen> createState() => _CustomCallScreenState();
}
class _CustomCallScreenState extends State<CustomCallScreen>
implements SessionStatusListeners {
Widget? _callWidget;
bool _isMuted = false;
bool _isVideoOff = false;
@override
void initState() {
super.initState();
_joinSession();
}
void _joinSession() {
final settings = SessionSettingsBuilder()
.hideControlPanel(true)
.hideHeaderPanel(true)
.build();
CometChatCalls.joinSession(
sessionId: widget.sessionId,
sessionSettings: settings,
onSuccess: (Widget? widget) {
setState(() => _callWidget = widget);
CallSession.getInstance()?.addSessionStatusListener(this);
},
onError: (e) => Navigator.pop(context),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
// Call video view
if (_callWidget != null) _callWidget!,
// Custom controls overlay
Positioned(
bottom: 40,
left: 0,
right: 0,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
// Mute button
FloatingActionButton(
onPressed: _toggleMute,
backgroundColor: _isMuted ? Colors.red : Colors.white,
child: Icon(_isMuted ? Icons.mic_off : Icons.mic),
),
// End call button
FloatingActionButton(
onPressed: _endCall,
backgroundColor: Colors.red,
child: const Icon(Icons.call_end, color: Colors.white),
),
// Video toggle button
FloatingActionButton(
onPressed: _toggleVideo,
backgroundColor: _isVideoOff ? Colors.red : Colors.white,
child: Icon(_isVideoOff ? Icons.videocam_off : Icons.videocam),
),
],
),
),
],
),
);
}
void _toggleMute() async {
final session = CallSession.getInstance();
await session?.toggleMuteAudio();
setState(() => _isMuted = session?.isAudioMuted ?? false);
}
void _toggleVideo() async {
final session = CallSession.getInstance();
await session?.togglePauseVideo();
setState(() => _isVideoOff = session?.isVideoPaused ?? false);
}
void _endCall() async {
await CallSession.getInstance()?.leaveSession();
if (mounted) Navigator.pop(context);
}
@override
void onSessionLeft() {
if (mounted) Navigator.pop(context);
}
@override
void onConnectionClosed() {
if (mounted) Navigator.pop(context);
}
}
Selectively Hide Buttons
// Hide specific buttons while keeping the control panel
final settings = SessionSettingsBuilder()
.hideRaiseHandButton(true)
.hideShareInviteButton(true)
.hideRecordingButton(true)
.hideChangeLayoutButton(true)
.hideChatButton(true)
// Keep these visible:
// hideToggleAudioButton(false)
// hideToggleVideoButton(false)
// hideLeaveSessionButton(false)
.build();
Gotchas
- The call
Widgetmust remain in the widget tree for the call to stay active - Use
Stackto overlay custom controls on top of the call widget CallSessionmethods work regardless of whether the default UI is visible- Always handle
onSessionLeftandonConnectionClosedto clean up - Custom UI state (muted, video off) should be synced with
CallSessionstate
Sample App Reference
lib/screens/call_screen.dart— Default call screen (can be customized)