> ## Documentation Index
> Fetch the complete documentation index at: https://docs.portalhq.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Subscribe to Portal events

> Listen to Portal SDK events on Flutter via the idiomatic Dart Stream<PortalEventData> API.

The Portal Flutter SDK exposes events emitted by the underlying native SDK as a single Dart broadcast `Stream<PortalEventData>`.

## Overview

Portal emits events for connection state, chain changes, signing requests, dApp sessions, and more. Use these events to:

* Drive UI for `autoApprove: false` flows — show approval prompts when the SDK requests a signature.
* React to chain switches initiated by a connected dApp.
* Track WalletConnect / Portal Connect session lifecycle.
* Build progress and audit logging across native MPC operations.

The Flutter SDK delivers these events as a [broadcast `Stream`](https://api.flutter.dev/flutter/dart-async/Stream-class.html) on the Portal singleton:

```dart theme={null}
// Getter on Portal:
Stream<PortalEventData> get events;
```

```dart theme={null}
// Usage:
portal.events.listen((e) => print('${e.name}: ${e.data}'));
```

Filter with `.where(...)`, listen with `.listen(...)`, await one-shot with `.firstWhere(...)`, and stop listening by cancelling the returned `StreamSubscription`. Listeners are kept across `Portal.initialize()` calls so apps can subscribe at startup.

## Subscribing to events

### Listen to all events

```dart theme={null}
import 'package:portal_flutter/portal_flutter.dart';

final portal = Portal();

final subscription = portal.events.listen((event) {
  print('${event.name}: ${event.data}');
});

// Later, when you no longer need the subscription:
await subscription.cancel();
```

### Filter by event type

Use the `PortalEvent` enum to filter the stream to a single event:

```dart theme={null}
portal.events
    .where((e) => e.event == PortalEvent.chainChanged)
    .listen((e) {
  print('Chain changed: ${e.data}');
});
```

### One-shot (await a single event)

```dart theme={null}
final connectEvent = await portal.events
    .firstWhere((e) => e.event == PortalEvent.connect);

print('Connected with payload: ${connectEvent.data}');
```

### Multiple subscribers

Because `portal.events` is a broadcast stream, you can attach as many subscribers as you need without one consuming the events for the others:

```dart theme={null}
final logSubscription = portal.events.listen((e) => print('[event] $e'));

final signingSubscription = portal.events
    .where((e) => e.event == PortalEvent.portalSigningRequested)
    .listen((e) => showApprovalUi(e.data));
```

## Event types

`portal.events` delivers `PortalEventData` — a small wrapper that carries the raw event name, the matching `PortalEvent` enum value (when known), and the JSON-decoded payload from the native side.

```dart theme={null}
class PortalEventData {
  const PortalEventData({
    required this.name,
    required this.event,
    required this.data,
  });

  /// Raw event name as emitted by native.
  final String name;

  /// Null if the SDK does not yet recognize the name.
  final PortalEvent? event;

  /// Map, List, String, num, bool, or null — decoded from the native payload.
  final dynamic data;
}
```

The `data` field's shape depends on the event — most events carry a `Map<String, dynamic>` decoded from the native JSON payload.

### PortalEvent enum

| `PortalEvent`                   | Raw name                         | Description                                                        |
| ------------------------------- | -------------------------------- | ------------------------------------------------------------------ |
| `connect`                       | `connect`                        | A WalletConnect / Portal Connect session connected.                |
| `disconnect`                    | `disconnect`                     | A connected session disconnected.                                  |
| `chainChanged`                  | `chainChanged`                   | The active chain changed.                                          |
| `portalConnectChainChanged`     | `portalConnect_chainChanged`     | A Portal Connect peer requested a chain switch.                    |
| `connectError`                  | `portal_connectError`            | A connection attempt errored.                                      |
| `portalSigningRequested`        | `portal_signingRequested`        | The SDK is requesting a signature (use with `autoApprove: false`). |
| `portalSigningApproved`         | `portal_signingApproved`         | A signing request was approved.                                    |
| `portalSigningRejected`         | `portal_signingRejected`         | A signing request was rejected.                                    |
| `portalSignatureReceived`       | `portal_signatureReceived`       | A signature was produced and is now available.                     |
| `portalConnectSigningRequested` | `portalConnect_signingRequested` | A Portal Connect peer requested a signature.                       |
| `portalDappSessionRequested`    | `portal_dappSessionRequested`    | A dApp is requesting a session.                                    |
| `portalDappSessionApproved`     | `portal_dappSessionApproved`     | The host approved a dApp session.                                  |
| `portalDappSessionRejected`     | `portal_dappSessionRejected`     | The host rejected a dApp session.                                  |

If the native SDK ever dispatches an event the Flutter SDK does not yet recognize, `PortalEventData.event` will be `null` while `name` and `data` are still populated — listeners can still log or react to the raw name without breaking.

<Warning>
  **Platform availability.** On **Android**,the only events that are currently exposed are `chainChanged`, `portalSignatureReceived`, `portalSigningApproved`, `portalSigningRejected`, and `portalSigningRequested`. The other app-session events are emitted on **iOS** only. Avoid relying on these events for cross-platform behavior until Android support lands.
</Warning>

## Emitting events back to the native SDK

For bidirectional flows — most importantly approving or rejecting a pending signing request when `autoApprove: false` — use `portal.emit(...)`:

```dart theme={null}
Future<void> emit(PortalEvent event, [dynamic data]);
```

The SDK fires `PortalEvent.portalSigningRequested` when user approval is needed; the host inspects the request, surfaces UI, and emits `portalSigningApproved` (or `portalSigningRejected`) to let the original signing call resolve.

```dart theme={null}
portal.events
    .where((e) => e.event == PortalEvent.portalSigningRequested)
    .listen((e) async {
  final approved = await showApprovalUi(e.data);
  if (approved) {
    await portal.emit(PortalEvent.portalSigningApproved, e.data);
  } else {
    await portal.emit(PortalEvent.portalSigningRejected, e.data);
  }
});
```

`data` is JSON-encoded before being sent across the platform channel — pass primitives, `Map`s, or `List`s (anything `jsonEncode` accepts).

## End-to-end example

```dart theme={null}
import 'dart:async';
import 'package:portal_flutter/portal_flutter.dart';

class WalletEventLogger {
  WalletEventLogger(this._portal);

  final Portal _portal;
  final List<StreamSubscription<PortalEventData>> _subs = [];

  void start() {
    _subs.add(
      _portal.events.listen((e) {
        print('[portal] ${e.name}: ${e.data}');
      }),
    );

    _subs.add(
      _portal.events
          .where((e) => e.event == PortalEvent.chainChanged)
          .listen((e) {
        print('Chain changed to: ${e.data}');
      }),
    );

    _subs.add(
      _portal.events
          .where((e) => e.event == PortalEvent.portalSigningRequested)
          .listen((e) async {
        final approved = await _showApproval(e.data);
        await _portal.emit(
          approved
              ? PortalEvent.portalSigningApproved
              : PortalEvent.portalSigningRejected,
          e.data,
        );
      }),
    );
  }

  Future<void> stop() async {
    for (final sub in _subs) {
      await sub.cancel();
    }
    _subs.clear();
  }

  Future<bool> _showApproval(dynamic request) async {
    // Render an approval sheet, return true/false based on user choice.
    return true;
  }
}
```

## Related

* [Sign a transaction guide](./sign-a-transaction)
* [Evaluate a transaction guide](./evaluate-a-transaction)
