Skip to content

Channels

View raw .md

Subscribe to public, private and presence channels through the Echo facade and react to the events they deliver.

On this page

Basic Usage#

dart
import 'package:magic_socket/magic_socket.dart';

void main() {
  Echo.channel('orders').listen('OrderShipped', (event) {
    print('Order ${event.data['id']} has shipped');
  });

  Echo.leave('orders');
}

Echo is a static facade over the BroadcastManager bound at broadcasting. It follows the shape of the Laravel Echo client (channel, private, join, listen and leave) with differences, the first being event names. With the null driver the calls succeed and no event ever arrives, so the same code runs in development without a server.

Quick Reference#

Member Returns Meaning
Echo.channel(name) BroadcastChannel A public channel; no auth.
Echo.private(name) BroadcastChannel A private channel; the driver adds private- and authorises over HTTP.
Echo.join(name) BroadcastPresenceChannel A presence channel; the driver adds presence- and tracks members.
Echo.listen(channel, event, callback) BroadcastChannel Shorthand for Echo.channel(channel).listen(event, callback).
Echo.leave(name) void Unsubscribes name and its private- and presence- forms, as Laravel Echo does: leave('chat.1') leaves private('chat.1'), join('chat.1') and a public chat.1. No effect when not subscribed.
Echo.connect(), Echo.disconnect() Future<void> Opens or closes the socket, see Connection.
channel.listen(event, callback) BroadcastChannel Registers a handler and returns the channel for chaining.
channel.stopListening(event) void Removes the handler for that event.
channel.events Stream<BroadcastEvent> Every event on the channel, whether or not a handler is registered.

Event Names#

listen compares the name you pass with the event name in the frame, exactly. Echo JS prepends the event's namespace for you; this driver does not. Laravel names a frame by broadcastAs() when the event has one and by its full class name when it has none, so a handler registered for 'OrderShipped' never runs for a default App\Events\OrderShipped.

Name the event on the server and pass the same string to listen:

php
use Illuminate\Broadcasting\Channel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;

class OrderShipped implements ShouldBroadcast
{
    public function broadcastOn(): Channel
    {
        return new Channel('orders');
    }

    public function broadcastAs(): string
    {
        return 'OrderShipped';
    }
}
dart
import 'package:magic_socket/magic_socket.dart';

void main() {
  Echo.channel('orders').listen('OrderShipped', (event) => print(event.data));

  // Without broadcastAs() on the server, pass the full class name instead.
  Echo.channel('orders').listen(r'App\Events\OrderShipped', (event) => print(event.data));
}

Echo's leading-dot form (.OrderShipped) is not needed and not recognised: pass the name as the frame carries it. Every other sample on this page assumes the event has a broadcastAs() equal to the name shown.

Public Channels#

Any connected client may subscribe to a public channel.

dart
import 'package:magic_socket/magic_socket.dart';

void main() {
  void onShipped(BroadcastEvent event) => print(event.data);
  void onCancelled(BroadcastEvent event) => print(event.data);

  Echo.channel('orders')
      .listen('OrderShipped', onShipped)
      .listen('OrderCancelled', onCancelled);

  Echo.channel('orders').stopListening('OrderShipped');
}

A second listen for the same event on the same channel replaces the first handler. To fan one event out to several handlers, use BroadcastListeners.

Private Channels#

A private channel performs an HTTP handshake before it subscribes: the driver posts socket_id and channel_name to the auth_endpoint (/broadcasting/auth by default) through Http, and expects {"auth": "..."} back.

dart
import 'package:magic_socket/magic_socket.dart';

void main() {
  Echo.private('user.1').listen('ProfileUpdated', (event) {
    print('Profile updated: ${event.data}');
  });
}

The server decides who may join, in Laravel's routes/channels.php:

php
Broadcast::channel('user.{id}', function ($user, $id) {
    return (int) $user->id === (int) $id;
});

The request goes through your Http stack, so the user's bearer token reaches the auth route like any other call. A refusal (401, 403) is logged and not retried; a missing response or a 5xx is retried with the reconnect backoff, see Connection.

Presence Channels#

A presence channel is a private channel that also tracks who is in it.

dart
import 'package:magic_socket/magic_socket.dart';

void main() {
  final room = Echo.join('room.1');

  print('Online: ${room.members.length}');

  room.onJoin.listen((member) => print('${member['name']} joined'));
  room.onLeave.listen((member) => print('${member['name']} left'));
  room.listen('MessagePosted', (event) => print(event.data['body']));
}
Member Type Meaning
members List<Map<String, dynamic>> The current members, an immutable snapshot.
onJoin Stream<Map<String, dynamic>> Emits the member payload on each join.
onLeave Stream<Map<String, dynamic>> Emits the member payload on each leave.

The BroadcastEvent Envelope#

Every handler receives a BroadcastEvent.

Property Type Meaning
event String The event name as the server sent it: the event's broadcastAs(), or its full class name (App\Events\OrderShipped) when it has none.
channel String The channel the event arrived on, with its private- or presence- prefix.
data Map<String, dynamic> The decoded JSON payload.
receivedAt DateTime The local time of receipt.

Every frame the server sends reaches your handler, including one whose payload repeats an earlier frame. The server can also deliver an event twice when a queued broadcast job is retried. Write handlers so a second delivery is harmless: refetch, or upsert by an id in the payload, rather than counting or showing a toast per frame.

Subscription State#

A connected driver does not mean a subscribed channel: a private channel still waits on its auth call, and a refusal leaves it unsubscribed. Each channel reports the server's confirmation itself.

dart
import 'package:magic_socket/magic_socket.dart';

void main() {
  final channel = Echo.private('team.1');

  print(channel.isSubscribed); // true once the server sent subscription_succeeded

  channel.onSubscriptionChanged.listen((subscribed) {
    print(subscribed ? 'stop polling' : 'start polling');
  });
}

onSubscriptionChanged emits only on a change and never replays, so read isSubscribed when you attach. Channels of the null driver are never subscribed.

Gotchas#

Mistake Fix
Listening for 'OrderShipped' when the server sends App\Events\OrderShipped The names must match exactly; add broadcastAs() to the event or pass the full class name, see Event Names.
Using Echo.listen for a private channel It is shorthand for a public channel; call Echo.private(name).listen(...).
Registering two handlers for one event on one channel The second replaces the first. Use BroadcastListeners to share the event.
Treating onReconnect as "the channel is back" It fires after the subscribe was sent, not confirmed. Refetch on onSubscriptionChanged turning true.
Counting frames as unique events The server can repeat a frame; make the handler idempotent.
Calling Echo before Magic.init The broadcasting binding does not exist yet; await Magic.init in main.
Expecting events from the null driver It drops everything; set BROADCAST_CONNECTION=reverb.
  • Configuration: connections, environment keys and the socket id header.
  • Listeners: sharing a channel between controllers.
  • Connection: lifecycle, reconnects and auth failures.
  • Testing: faking channels and delivering events in a test.
esc

Search index did not load.

No match for "".

Try a class like rounded-lg or a widget like WButton.