Package selection guidance compiled from public package information, community data, and project usage.
A Dart implementation of the Pusher Channels client, enabling real-time communication in Flutter applications. It allows developers to connect to Pusher's real-time messaging service, subscribe to channels, and receive live updates. Ideal for building interactive features like chat, notifications, live dashboards, and collaborative tools. The package supports all major platforms including Android, iOS, Web, macOS, Windows, and Linux, ensuring cross-platform consistency. It follows the Pusher Channels protocol with a clean, asynchronous API for event handling and connection management.
Use cases
Real-time chat applications
Live notification systems
Collaborative editing tools
Live data dashboards
Multiplayer game synchronization
Key features
Cross-platform support
Event-driven architecture
Secure channel subscriptions
Automatic reconnection
Lightweight and efficient
Easy integration with Pusher backend
Suitable for
Developers building real-time apps
Teams using Pusher Channels
Apps requiring live data updates
Full-stack Flutter developers
Projects needing low-latency communication
Considerations
Requires a Pusher account and app credentials
Depends on network connectivity
May need additional error handling for unstable connections
Not suitable for offline-first applications
Limited to Pusher Channels ecosystem
Original README
Original README snapshot from the linked source repository. Visit the repository for the latest content.
Introducing dart_pusher_channels - a client library of Pusher Channels implemented in pure Dart. Structure of the package is built according to the official documentation of the protocol.
Starting from version 1.0.0 the package has become more canonical, enhanced and extendable for further updates and features.
The project will continue to grow and will be maintained. Your support is appreciated and will highly motivate the author to improve the package. If you've found this library helpful and want to support the author, please, consider the donation by clicking the button below or following the link to buymeacoffee.com.
Using the legacy version
Starting from the version 1.0.0 the package offers more canonical client API of Pusher Channels. If you have projects powered by the old versions - consider setting the dependency in pubspec.yaml as following:
dart_pusher_channels:0.3.1+1
Milestones
Starting from the version 1.1.0 the main milestones were implemented:
Presence channels ✓
Private encrypted channels ✓
Triggering the client events ✓
Tested on all the 6 platforms. ✓
Issues on this package are monitored and processed.
In order to connect to a server with Pusher Channels protocol, a client must provide some metadata as a url. Use one of the constructors of PusherChannelsOptions according to your use-case.
PusherChannelsOptions.fromCluster
Use this constructor if your url has a pattern like:{scheme}://ws-{cluster_name}.{host}:{port}/app/{key}. Example:
const clusterOptions = PusherChannelsOptions.fromCluster(
// usually: ws or wss
scheme: 'wss',
// your app's cluster
cluster: 'mt1',
// your app's key
key: 'a0173cd5499b34d93109',
// provide custom host if needed
host: 'pusher.com',
// decide if to send additional metadata in query parameters of the url
shouldSupplyMetadataQueries: true,
// apply custom metadata if needed
metadata: PusherChannelsOptionsMetadata.byDefault(),
port: 443,
);
// prints wss://ws-mt1.pusher.com:443/app/a0173cd5499b34d93109?client=dart&version=0.8.0&protocol=7print(clusterOptions.uri.toString());
PusherChannelsOptions.fromHost
Use this constructor if you have the Pusher Channels installed to your server under your own domain host and the url pattern looks like:{scheme}://{host}:{port}/app/{key}. Example:
const hostOptions = PusherChannelsOptions.fromHost(
// usually: ws or wss
scheme: 'wss',
host: 'my.domain.com',
key: 'my_key',
// decide if to send additional metadata in query parameters of the url
shouldSupplyMetadataQueries: true,
// apply custom metadata if needed
metadata: PusherChannelsOptionsMetadata.byDefault(),
port: 443,
);
// prints wss://my.domain.com:443/app/my_key?client=dart&version=0.8.0&protocol=7print(hostOptions.uri.toString());
PusherChannelsOptions.custom
Use this constructor if the others above don't fit your use-case. Example:
final customOptions = PusherChannelsOptions.custom(
// You may also apply the given metadata in your custom uri
uriResolver: (metadata) =>
Uri.parse('wss://my.custom.domain/my/custom/path'),
);
// prints wss://my.custom.domain/my/custom/pathprint(customOptions.uri.toString());
Client
When you've decided with the options that fit your use-case, it's time to apply them to an instance of PusherChannelsClient.
PusherChannelsClient.websocket
Using this constructor you can build a client supporting connection through the web sockets. This package depends on web_socket_channel to support the feature. Example:
const testOptions = PusherChannelsOptions.fromCluster(
scheme: 'wss',
cluster: 'mt1',
key: 'a0173cd5499b34d93109',
host: 'pusher.com',
shouldSupplyMetadataQueries: true,
metadata: PusherChannelsOptionsMetadata.byDefault(),
port: 443,
);
final client = PusherChannelsClient.websocket(
options: testOptions,
connectionErrorHandler: (exception, trace, refresh) {
// here you can handle connection errors.// refresh callback enables to reconnect the clientrefresh();
},
// [OPTIONAL]// A delay applied when using method .reconnect between// of these lifecycles PusherChannelsClientLifeCycleState.reconnecting,// PusherChannelsClientLifeCycleState.pendingConnection// Basically, the client puts delay between attempts to reconnect
minimumReconnectDelayDuration: constDuration(
seconds: 1,
),
// [OPTIONAL]// Default timeout of the activity after which the client will ping the server.// It is needed in case if the server does not provide one.
defaultActivityDuration: constDuration(
seconds: 120,
),
// [OPTIONAL]// Overrides both defaultActivityDuration and the one that the server provides.
activityDurationOverride: constDuration(
seconds: 120,
),
// [OPTIONAL]// Timeout duration that is applied while the client is waiting for the pong// message from the server after pinging it.
waitForPongDuration: constDuration(
seconds: 30,
));
Custom connection implementation
As it was mentioned above, the package supports connection through the web sockets using ## PusherChannelsClient.websocket. You may implemet your custom PusherChannelsConnection. See the implementation of PusherChannelsWebSocketConnection as an example.
And use it with PusherChannelsClient.custom:
final myClient = PusherChannelsClient.custom(
// It's important to return a new instance in // the delegate function because the client// closes (disposes) its internal instance each time// it connects/reconnects/disconnects.
connectionDelegate: () => MyConnection(),
connectionErrorHandler: ((exception, trace, refresh) {
refresh();
}),
);
Public channels
Public channels should be used for publicly accessible data as they do not require any form of authorization in order to be subscribed to. This is how you create a public channel:
// Use an instance of PusherChannelsClient as was mentioned in the previous sections
client.publicChannel('public-MyChannel');
Private channels
Private channels should be used when access to the channel needs to be restricted in some way. In order for a user to subscribe to a private channel permission must be authorized.
The authorization occurs via a HTTP Request to a configurable authorization url when the subscribe method is called with a private- channel name.
Unlike public channels, this kind of channel requires users to be authorized. So you need to provide an instance of EndpointAuthorizableChannelAuthorizationDelegate within creating the channel. You may consider either creating your own implementation of the delegate or using the implementation provided by this package through http library - EndpointAuthorizableChannelTokenAuthorizationDelegate. Here is an example:
End-to-end encrypted channels provide the same subscription restrictions as private channels.
In addition, the data field of events published to end-to-end encrypted channels is encrypted using an implementation of the Secretbox encryption standard defined in NaCl before it leaves your server.
Only authorized subscribers have access to the channel specific decryption key.
Note: These channels do not support triggering the client events. Ensure that a server library that you use in the back end supports the encrypted channels.
Usage example:
PrivateEncryptedChannel myEncryptedChannel = client.privateEncryptedChannel(
'private-encrypted-channel',
// [OPTIONAL] you may want to provide your own way to// encode the decrypted bytes of the event's data into the plain text type of `String`
eventDataEncodeDelegate: (bytes) => utf8.decode(bytes),
authorizationDelegate: EndpointAuthorizableChannelTokenAuthorizationDelegate
.forPrivateEncryptedChannel(
authorizationEndpoint: Uri.parse('https://test.pusher.com/pusher/auth'),
headers: const {},
),
);
Presence channels
Presence channels build on the security of Private channels and expose the additional feature of an awareness of who is subscribed to that channel.This makes it extremely easy to build chat room and “who’s online” type functionality to your application.Think chat rooms, collaborators on a document, people viewing the same web page, competitors in a game, that kind of thing.
It also required an instance of EndpointAuthorizableChannelAuthorizationDelegate to perform the authorization process before subscribing. The example is similiar to the one in the previous section:
Here is an example of how to connect the client and to subscribe to channels.
// Organizing all channels for readibilityfinal allChannels = <Channel>[
myPresenceChannel,
myPrivateChannel,
myPublicChannel,
];
// Highly recommended to subscribe to the channels when the clients'// .onConnectionEstablished Stream fires an event because it enables// to resubscribe, for example, when the client reconnects due to// a connection errorfinalStreamSubscription connectionSubs =client.onConnectionEstablished.listen((_) {
for (final channel in allChannels) {
// Subscribes to the channel if didn't unsubscribe from it intentionally
channel.subscribeIfNotUnsubscribed();
}
});
// Connect with the client
client.connect();
// If you no longer need the client - cancel the connection subscription and dispose it.// Somewhere in futureawaitFuture.delayed(constDuration(seconds: 5));
connectionSubs.cancel();
client.dispose();
Using .subscribeIfNotUnsubscribed of Channel instances is recommended if you had unsubscribed from it before intentionally and you don't want to subscribe to it unexpectedly when re-establishing connection. .subscribe method, on other hand, forces an instance of a channel to subscribe no matter of its current state.
Unsubscribing from a channel
// You also will be able to subscribe again if needed
myChannel.unsubscribe();
Binding to events
Unlike other SDKs, dart_pusher_channels offers binding to events via Dart streams, so it's recommended to create StreamSubscription for each event you want to subscribe for.
Keep in mind: those StreamSubscription instances will contintue receiving events
unless it gets canceled or while the channel is subscribed. The statement means: if you cancel an instance of StreamSubscription - events won't be received, if you unsubscribe from a channel - the stream won't be closed but prevented from receiving events unless you subscribe to the channel again.
// In order to bind to an event, use .bind method of an instance of ChannelStreamSubscription<ChannelReadEvent> somePrivateChannelEventSubs = myPrivateChannel.bind('private-MyEvent').listen((event) {
print('Event from the private channel fired!');
});
...
// If you want to unbind it - simply cancel the subscription
somePrivateChannelEventSubs.cancel();
Using the extension shortcuts to bind
You can bind to the predefined events (especially with presence channels) by using the extension shortcut methods on instances of Channel:
.whenSubscriptionSucceeded()
Binding to pusher:subscription_succeeded
.whenSubscriptionCount()
Binding to pusher:subscription_count
.whenMemberAdded()
Binding to pusher:member_added
.whenMemberRemoved()
Binding to pusher:member_removed
.onSubscriptionError({String? errorType})
Binding to pusher:subscription_error filtered with errorType.
.onAuthenticationSubscriptionFailed()
Binding to pusher:subscription_error with errorType of AuthError.
Listening for all events of a channel
StreamSubscription<ChannelReadEvent> allEventsSubs = myChannel.bindToAll().listen((event) {
// do something
});
Listening for all events coming from a client
StreamSubscription<PusherChannelsReadEvent> allEventsSubs = client.eventStream.listen((event) {
// do something
});
Triggering events
Private channels and presence channels support triggering the client events: