DEV Community

kanta13jp1
kanta13jp1

Posted on

Supabase Realtime Advanced: Presence, Broadcast, and Channel Management

Supabase Realtime Advanced: Presence, Broadcast, and Channel Management

DB Changes is only one third of Supabase Realtime. Presence tracks who's online; Broadcast enables serverless real-time messaging between clients.

The Three Realtime Features

DB Changes  → detect INSERT/UPDATE/DELETE on tables in real time
Broadcast   → send messages directly between clients (no DB required)
Presence    → share online user state in real time
Enter fullscreen mode Exit fullscreen mode

Broadcast: Serverless Real-Time Messaging

final channel = supabase.channel('room:$roomId');

// Receive
channel.onBroadcast(
  event: 'cursor-move',
  callback: (payload) {
    setState(() {
      _cursors[payload['userId']] = Offset(
        payload['x'].toDouble(),
        payload['y'].toDouble(),
      );
    });
  },
);

await channel.subscribe();

// Send (not persisted to DB — ephemeral, low latency)
Future<void> sendCursorPosition(Offset position) async {
  await channel.sendBroadcastMessage(
    event: 'cursor-move',
    payload: {
      'userId': supabase.auth.currentUser!.id,
      'x': position.dx,
      'y': position.dy,
    },
  );
}
Enter fullscreen mode Exit fullscreen mode

Best for:

✅ Real-time cursor sharing
✅ Game state sync
✅ Collaborative whiteboard strokes
✅ Live poll intermediate results
Enter fullscreen mode Exit fullscreen mode

Presence: Online User Management

final channel = supabase.channel('online-users');

channel.onPresenceSync(callback: (_) {
  final onlineUsers = channel.presenceState().values
    .expand((p) => p)
    .map((p) => p.payload)
    .toList();
  setState(() => _onlineUsers = onlineUsers);
});

channel.onPresenceJoin(callback: (payload) {
  debugPrint('Joined: ${payload.newPresences.first.payload}');
});

channel.onPresenceLeave(callback: (payload) {
  debugPrint('Left: ${payload.leftPresences.first.payload}');
});

await channel.subscribe();

// Announce your own presence
await channel.track({
  'userId': supabase.auth.currentUser!.id,
  'userName': _currentUser.name,
  'avatarUrl': _currentUser.avatarUrl,
  'status': 'active',
  'lastSeen': DateTime.now().toIso8601String(),
});
Enter fullscreen mode Exit fullscreen mode

Display online avatars:

Widget buildOnlineUsers() {
  return Row(
    children: [
      ..._onlineUsers.take(5).map((user) => CircleAvatar(
        backgroundImage: NetworkImage(user['avatarUrl']),
        radius: 16,
      )),
      if (_onlineUsers.length > 5)
        Text('+${_onlineUsers.length - 5}'),
    ],
  );
}
Enter fullscreen mode Exit fullscreen mode

Combining DB Changes + Broadcast

// Rule: important data → DB Changes (persisted, queryable later)
//       transient data → Broadcast (no DB, lowest latency)

final channel = supabase.channel('document:$docId')
  ..onPostgresChanges(
    event: PostgresChangeEvent.all,
    schema: 'public',
    table: 'documents',
    filter: PostgresChangeFilter(
      type: PostgresChangeFilterType.eq,
      column: 'id',
      value: docId,
    ),
    callback: (payload) => _handleDocumentChange(payload),
  )
  ..onBroadcast(
    event: 'cursor',
    callback: (payload) => _updateCursor(payload),
  )
  ..onPresenceSync(callback: (_) => _updateOnlineUsers());

await channel.subscribe();
Enter fullscreen mode Exit fullscreen mode

Channel Lifecycle Management

class _RealtimePageState extends State<RealtimePage> {
  RealtimeChannel? _channel;

  @override
  void initState() {
    super.initState();
    _setupChannel();
  }

  Future<void> _setupChannel() async {
    _channel = supabase.channel('room:${widget.roomId}');
    // ... register events ...
    await _channel!.subscribe();
  }

  @override
  void dispose() {
    supabase.removeChannel(_channel!); // always clean up
    super.dispose();
  }
}
Enter fullscreen mode Exit fullscreen mode

Summary

DB Changes → persist and detect changes (chat, comments, orders)
Broadcast  → ephemeral real-time comms (cursors, games, polls)
Presence   → who's online right now
Combine    → DB for durability + Broadcast for real-time feel
Enter fullscreen mode Exit fullscreen mode

All three features work on a single channel. Pick the right tool for each data type — ephemeral vs. persistent.

Top comments (0)