Phoenix Channel Flutter Package

A Flutter package for connecting to Phoenix Channels via WebSocket. This package provides a complete implementation of the Phoenix Channels client protocol, allowing you to connect to Phoenix servers and interact with channels.

Features

  • ✅ Full WebSocket connection management
  • ✅ Channel join/leave functionality
  • ✅ Automatic heartbeat mechanism
  • ✅ Message reference tracking
  • ✅ Event-based API with callbacks
  • ✅ Automatic reconnection support
  • ✅ Type-safe message handling
  • ✅ Custom serializers for JSON, MessagePack, or other Phoenix-compatible formats
  • ✅ Push timeouts, channel rejoin, lifecycle events, and Presence helpers

Installation

Add this to your package's pubspec.yaml file:

dependencies:
  phoenix_channel: ^1.0.0

Usage

Basic Example

import 'package:phoenix_channel/phoenix_channel.dart';

void main() async {
  // Create a socket connection
  final socket = Socket(
    url: 'ws://localhost:4000',
    path: '/mobile/websocket',
  );

  // Set up connection callbacks
  socket.onStateChange = (state) {
    print('Socket state: $state');
  };

  socket.onError = (error) {
    print('Socket error: $error');
  };

  // Connect to the server
  await socket.connect();

  // Create a channel
  final channel = Channel(
    socket: socket,
    topic: 'miami:weather',
    params: {'some': 'param'},
  );

  // Set up channel callbacks
  channel.onStateChange = (state) {
    print('Channel state: $state');
  };

  channel.onJoin = (payload) {
    print('Joined channel: $payload');
  };

  channel.onError = (payload) {
    print('Channel error: $payload');
  };

  // Join the channel
  await channel.join();

  // Listen for events
  channel.on('report_emergency', (payload) {
    print('Emergency reported: $payload');
  });

  // Send an event
  await channel.push(
    event: 'report_emergency',
    payload: {'category': 'sharknado'},
  );

  // Leave the channel when done
  await channel.leave();

  // Disconnect the socket
  socket.disconnect();
}

Advanced Example

import 'package:phoenix_channel/phoenix_channel.dart';

class ChatService {
  late Socket _socket;
  late Channel _channel;

  Future<void> initialize() async {
    _socket = Socket(
      url: 'ws://localhost:4000',
      path: '/socket/websocket',
      params: {'token': 'user_token'},
      headers: {'authorization': 'Bearer user_token'},
      reconnectAfterMs: 5000,
      maxReconnectAttempts: 10,
    );

    // Configure heartbeat and push timeouts
    _socket.heartbeatIntervalMs = 30000;
    _socket.timeoutMs = 10000;

    _socket.onStateChange = (state) {
      print('Socket: $state');
    };

    await _socket.connect();

    _channel = Channel(
      socket: _socket,
      topic: 'room:lobby',
    );

    _channel.onJoin = (payload) {
      print('Joined room: $payload');
    };

    _channel.onError = (payload) {
      print('Channel error: $payload');
    };

    _channel.onClose = (payload) {
      print('Channel closed: $payload');
    };

    await _channel.join();
  }

  void sendMessage(String text) {
    _channel.push(
      event: 'new_msg',
      payload: {'body': text, 'user': 'John'},
    );
  }

  void listenForMessages(Function(Map<String, dynamic>) callback) {
    _channel.on('new_msg', callback);
  }

  Future<void> disconnect() async {
    await _channel.leave();
    _socket.disconnect();
  }
}

Custom Serializer

Socket uses JSON by default, but you can provide a custom PhoenixSerializer to use another transport format such as MessagePack. Add your preferred MessagePack package to your app, then encode and decode Phoenix's [joinRef, ref, topic, event, payload] message list.

import 'package:phoenix_channel/phoenix_channel.dart';

class MessagePackPhoenixSerializer implements PhoenixSerializer {
  const MessagePackPhoenixSerializer();

  @override
  Object? encode(List<Object?> message) {
    // Return the binary payload from your MessagePack package.
    return messagePackEncode(message);
  }

  @override
  List<Object?> decode(Object? message) {
    // Decode the binary payload from your MessagePack package.
    final decoded = messagePackDecode(message);
    return List<Object?>.from(decoded as List);
  }
}

final socket = Socket(
  url: 'ws://localhost:4000',
  path: '/socket/websocket',
  serializer: const MessagePackPhoenixSerializer(),
);

Push Timeouts

Pushes complete with the server reply or throw a TimeoutException when no reply arrives in time. The default is socket.timeoutMs, and each push can override it.

import 'dart:async';

try {
  final reply = await channel.push(
    event: 'new_msg',
    payload: {'body': 'hello'},
    timeoutMs: 5000,
  );

  final status = reply?['payload']?['status'];
  if (status == 'ok') {
    print('Message accepted');
  }
} on TimeoutException {
  print('No reply from the server');
}

Presence

Use Presence to track Phoenix Presence state and diffs from presence_state and presence_diff events.

final presence = Presence(channel);

presence.onSync(() {
  final users = presence.list((id, presence) {
    return {
      'id': id,
      'metas': presence['metas'],
    };
  });
  print('Online users: $users');
});

API Reference

Socket

The Socket class manages the WebSocket connection to the Phoenix server.

Constructor

Socket({
  required String url,
  required String path,
  Map<String, String>? params,
  Map<String, String>? headers,
  String vsn = "2.0.0",
  PhoenixSerializer serializer = const JsonPhoenixSerializer(),
  Backoff? backoff,
  Duration? connectionTimeout,
  int reconnectAfterMs = 5000,
  int maxReconnectAttempts = 5,
})

Properties

  • state - Current socket state (SocketState)
  • isConnected - Whether the socket is connected (bool)
  • heartbeatIntervalMs - Heartbeat interval in milliseconds (default: 30000)
  • timeoutMs - Default push timeout in milliseconds (default: 10000)
  • reconnectAfterMs - Reconnection delay in milliseconds (default: 5000)
  • maxReconnectAttempts - Maximum reconnection attempts (default: 5)

Methods

  • Future<void> connect() - Connect to the server
  • void disconnect() - Disconnect from the server
  • String makeRef() - Generate a Phoenix message reference
  • Future<Map<String, dynamic>>? push(...) - Send a message to the server, with an optional timeout
  • void on(String topic, String event, MessageCallback callback) - Register a callback
  • void off(String topic, String event, MessageCallback callback) - Remove a callback
  • void onOpen(void Function() callback) - Register an open/reopen callback
  • void onClose(void Function() callback) - Register a close callback

Callbacks

  • onStateChange - Called when socket state changes
  • onError - Called when an error occurs
  • onMessage - Called when any message is received

Channel

The Channel class represents a topic subscription on a Socket.

Constructor

Channel({
  required Socket socket,
  required String topic,
  Map<String, dynamic>? params,
})

Properties

  • topic - The topic name (String)
  • state - Current channel state (ChannelState)
  • isJoined - Whether the channel is joined (bool)

Methods

  • Future<Map<String, dynamic>?> join({Map<String, dynamic>? params}) - Join the channel
  • Future<Map<String, dynamic>?> leave() - Leave the channel
  • Future<Map<String, dynamic>?> push({required String event, required Map<String, dynamic> payload}) - Send an event
  • void on(String event, ChannelEventCallback callback) - Listen for an event
  • void off(String event, ChannelEventCallback callback) - Stop listening for an event

Callbacks

  • onStateChange - Called when channel state changes
  • onError - Called when a channel error occurs
  • onClose - Called when the server sends phx_close
  • onJoin - Called when successfully joined
  • onLeave - Called when successfully left

Message Format

The package follows the Phoenix Channels V2 protocol. Messages are sent as serializer-defined arrays in the format:

[join_reference, message_reference, topic_name, event_name, payload]

Special Events

The package handles these special events automatically:

  1. phx_join - Joins a channel
  2. phx_leave - Leaves a channel
  3. heartbeat - Maintains the WebSocket connection (automatic)
  4. phx_error - Marks the channel errored and schedules rejoin
  5. phx_close - Marks the channel closed and calls onClose

License

This package is licensed under the MIT License.

Libraries

phoenix_channel