Monitor layout changes with LayoutListeners. This listener provides callbacks for call layout changes, participant list visibility, and Picture-in-Picture (PiP) mode state changes.
Prerequisites
Register Listener
Set the layoutListener on the CallSession to receive layout event callbacks:
final callSession = CallSession . getInstance ();
callSession ? .layoutListener = LayoutListeners (
onCallLayoutChanged : ( LayoutType layoutType) {
debugPrint ( "Layout changed to: $ layoutType " );
},
onParticipantListVisible : () {
debugPrint ( "Participant list is now visible" );
},
onParticipantListHidden : () {
debugPrint ( "Participant list is now hidden" );
},
onPictureInPictureLayoutEnabled : () {
debugPrint ( "PiP mode enabled" );
},
onPictureInPictureLayoutDisabled : () {
debugPrint ( "PiP mode disabled" );
},
);
Flutter listeners are not lifecycle-aware. You must manually remove the layout listener in your widget’s dispose() method to prevent memory leaks.
Callbacks
onCallLayoutChanged
Triggered when the call layout changes between Tile and Spotlight modes.
onCallLayoutChanged : ( LayoutType layoutType) {
debugPrint ( "Layout changed to: $ layoutType " );
switch (layoutType) {
case LayoutType .tile :
// Update UI for tile layout
setState (() => _currentLayout = LayoutType .tile);
break ;
case LayoutType .spotlight :
// Update UI for spotlight layout
setState (() => _currentLayout = LayoutType .spotlight);
break ;
}
}
LayoutType Values:
Value Description LayoutType.tileGrid layout showing all participants equally LayoutType.spotlightFocus on active speaker with others in sidebar
Use Cases:
Update layout toggle button icon
Adjust custom UI overlays
Log layout preference analytics
onParticipantListVisible
Triggered when the participant list panel becomes visible.
onParticipantListVisible : () {
debugPrint ( "Participant list opened" );
// Track analytics
// Adjust UI if needed
setState (() => _isParticipantListVisible = true );
}
Use Cases:
Log analytics events
Adjust custom UI elements
Pause other UI animations
onParticipantListHidden
Triggered when the participant list panel is hidden.
onParticipantListHidden : () {
debugPrint ( "Participant list closed" );
// Restore UI
setState (() => _isParticipantListVisible = false );
}
Use Cases:
Restore UI elements
Resume animations
Update button states
onPictureInPictureLayoutEnabled
Triggered when Picture-in-Picture (PiP) mode is enabled.
onPictureInPictureLayoutEnabled : () {
debugPrint ( "PiP mode enabled" );
// Hide non-essential UI elements
setState (() => _isPipEnabled = true );
}
Use Cases:
Hide call control buttons
Simplify UI for small window
Track PiP feature usage
onPictureInPictureLayoutDisabled
Triggered when Picture-in-Picture (PiP) mode is disabled.
onPictureInPictureLayoutDisabled : () {
debugPrint ( "PiP mode disabled" );
// Restore full UI
setState (() => _isPipEnabled = false );
}
Use Cases:
Restore call control buttons
Show full call UI
Resume normal layout
Complete Example
Here’s a complete example handling all layout events:
import 'package:cometchat_calls_sdk/cometchat_calls_sdk.dart' ;
import 'package:flutter/material.dart' ;
class CallScreen extends StatefulWidget {
const CallScreen ({ super .key});
@override
State < CallScreen > createState () => _CallScreenState ();
}
class _CallScreenState extends State < CallScreen > {
CallSession ? _callSession;
LayoutType _currentLayout = LayoutType .tile;
bool _isParticipantListVisible = false ;
bool _isPipEnabled = false ;
@override
void initState () {
super . initState ();
_callSession = CallSession . getInstance ();
_setupLayoutListener ();
}
void _setupLayoutListener () {
_callSession ? .layoutListener = LayoutListeners (
onCallLayoutChanged : ( LayoutType layoutType) {
if (mounted) {
setState (() => _currentLayout = layoutType);
}
},
onParticipantListVisible : () {
if (mounted) {
setState (() => _isParticipantListVisible = true );
debugPrint ( "Participant list visible" );
}
},
onParticipantListHidden : () {
if (mounted) {
setState (() => _isParticipantListVisible = false );
debugPrint ( "Participant list hidden" );
}
},
onPictureInPictureLayoutEnabled : () {
if (mounted) {
setState (() => _isPipEnabled = true );
debugPrint ( "PiP enabled" );
}
},
onPictureInPictureLayoutDisabled : () {
if (mounted) {
setState (() => _isPipEnabled = false );
debugPrint ( "PiP disabled" );
}
},
);
}
@override
void dispose () {
// Must manually remove listener to prevent memory leaks
_callSession ? .layoutListener = null ;
super . dispose ();
}
@override
Widget build ( BuildContext context) {
return Scaffold (
body : Stack (
children : [
// Call UI content here
if ( ! _isPipEnabled)
Positioned (
bottom : 16 ,
left : 0 ,
right : 0 ,
child : Row (
mainAxisAlignment : MainAxisAlignment .center,
children : [
// Layout toggle button
IconButton (
icon : Icon (
_currentLayout == LayoutType .tile
? Icons .grid_view
: Icons .person,
),
tooltip : _currentLayout == LayoutType .tile
? "Switch to spotlight"
: "Switch to tile" ,
onPressed : () async {
final newLayout = _currentLayout == LayoutType .tile
? LayoutType .spotlight
: LayoutType .tile;
await _callSession ? . setLayout (newLayout);
},
),
// PiP button
IconButton (
icon : const Icon ( Icons .picture_in_picture),
tooltip : "Enter PiP mode" ,
onPressed : () async {
await _callSession ? . enterPipMode ();
},
),
],
),
),
],
),
);
}
}
Remove Listener
Remove the layout listener in your widget’s dispose() method to prevent memory leaks:
@override
void dispose () {
_callSession ? .layoutListener = null ;
super . dispose ();
}
Controlling Layout Programmatically
You can change the layout and PiP state programmatically:
Change Layout
// Switch to tile layout
await CallSession . getInstance () ? . setLayout ( LayoutType .tile);
// Switch to spotlight layout
await CallSession . getInstance () ? . setLayout ( LayoutType .spotlight);
Enable/Disable PiP
// Enable Picture-in-Picture
await CallSession . getInstance () ? . enablePictureInPictureLayout ();
// Disable Picture-in-Picture
await CallSession . getInstance () ? . disablePictureInPictureLayout ();
Initial Layout Configuration
Set the initial layout when joining a session:
final sessionSettings = CometChatCalls . SessionSettingsBuilder ()
. setLayout ( LayoutType .tile) // or LayoutType.spotlight
. hideChangeLayoutButton ( false ) // Allow users to change layout
. build ();
Callbacks Summary
Callback Parameter Description onCallLayoutChangedLayoutTypeCall layout changed (tile/spotlight) onParticipantListVisible- Participant list panel opened onParticipantListHidden- Participant list panel closed onPictureInPictureLayoutEnabled- PiP mode was enabled onPictureInPictureLayoutDisabled- PiP mode was disabled
Next Steps
Layout & UI Control layout programmatically
Session Settings Configure initial layout settings