> ## Documentation Index
> Fetch the complete documentation index at: https://cometchat-22654f5b-docs-v6-beta2-flutter-uikit.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Layout Listener

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

* An active [call session](/calls/flutter/join-session)
* Access to the `CallSession` instance

## Register Listener

Set the `layoutListener` on the `CallSession` to receive layout event callbacks:

```dart theme={null}
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");
  },
);
```

<Warning>
  Flutter listeners are **not** lifecycle-aware. You must manually remove the layout listener in your widget's `dispose()` method to prevent memory leaks.
</Warning>

***

## Callbacks

### onCallLayoutChanged

Triggered when the call layout changes between Tile and Spotlight modes.

```dart theme={null}
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.tile`      | Grid layout showing all participants equally   |
| `LayoutType.spotlight` | Focus 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.

```dart theme={null}
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.

```dart theme={null}
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.

```dart theme={null}
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.

```dart theme={null}
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:

```dart theme={null}
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:

```dart theme={null}
@override
void dispose() {
  _callSession?.layoutListener = null;
  super.dispose();
}
```

***

## Controlling Layout Programmatically

You can change the layout and PiP state programmatically:

### Change Layout

```dart theme={null}
// Switch to tile layout
await CallSession.getInstance()?.setLayout(LayoutType.tile);

// Switch to spotlight layout
await CallSession.getInstance()?.setLayout(LayoutType.spotlight);
```

### Enable/Disable PiP

```dart theme={null}
// 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:

```dart theme={null}
final sessionSettings = CometChatCalls.SessionSettingsBuilder()
    .setLayout(LayoutType.tile)  // or LayoutType.spotlight
    .hideChangeLayoutButton(false)  // Allow users to change layout
    .build();
```

***

## Callbacks Summary

| Callback                           | Parameter    | Description                          |
| ---------------------------------- | ------------ | ------------------------------------ |
| `onCallLayoutChanged`              | `LayoutType` | Call 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

<CardGroup cols={2}>
  <Card title="Layout & UI" icon="table-cells" href="/calls/flutter/layout-ui">
    Control layout programmatically
  </Card>

  <Card title="Session Settings" icon="gear" href="/calls/flutter/session-settings">
    Configure initial layout settings
  </Card>
</CardGroup>
