Ably is the platform that powers synchronized digital experiences in realtime. Whether attending an event in a virtual venue, receiving realtime financial information, or monitoring live car performance data – consumers simply expect realtime digital experiences as standard. Ably provides a suite of APIs to build, extend, and deliver powerful digital experiences in realtime for more than 250 million devices across 80 countries each month. Organizations like Bloomberg, HubSpot, Verizon, and Hopin depend on Ably’s platform to offload the growing complexity of business-critical realtime data synchronization at global scale. For more information, see the Ably documentation.
A Flutter plugin for Ably, built on top of Ably's iOS and Android SDKs.
There are two different ways the example application can be configured to use Ably services:
-
Without the Ably SDK key: the application will request a sandbox key provision from Ably server at startup, but be aware that:
- provisioned key may not support all features available in Ably SDK.
- provisioned keys aren't able to use Ably push notifications. This feature requires APNS and FCM identifiers to be registered for Ably instance, which can't be done with sandbox applications
- provisioned key will change on application restart
-
With the Ably SDK key: you can create a free account on ably.com and then use your API key from there in the example app. This approach will give you much more control over the API capabilities and grant access to development console, where API communication can be conveniently inspected.
Under the run/ debug configuration drop down menu, click Edit Configurations...
. Duplicate the Example App (Duplicate and modify)
configuration. Leave the "Store as project file" unchecked to avoid committing your Ably API key into a repository. Update this new run configuration's additional run args
with your ably API key. Run or debug the your new run/ debug configuration.
- Under
Run and Debug
,- Select the gear icon to view launch.json
- Find
Example App
launch configuration - Add your Ably API key to the
configurations.args
, i.e. replacereplace_with_your_api_key
with your own Ably API key. - Choose a device to launch the app:
- to launch on a device, make sure it is the only device plugged in.
- to run on a specific device when you have multiple plugged in, add another element to the
configuration.args
value, with--device-id=replace_with_device_id
. Make sure to replacereplace_with_your_device
with your device ID fromflutter devices
.
- From
Run and Debug
select theExample App
configuration and run it
- Change into the example app directory:
cd example
- Install dependencies:
flutter pub get
- Launch the application:
flutter run --dart-define ABLY_API_KEY=put_your_ably_api_key_here
, remembering to replaceput_your_ably_api_key_here
with your own API key.- To choose a specific device when more than one are connected: get your device ID using
flutter devices
, and then runningflutter run --dart-define=ABLY_API_KEY=put_your_ably_api_key_here --device-id replace_with_device_id
- To choose a specific device when more than one are connected: get your device ID using
By default, push-related components in the sample app won't work on Android, because of a dummy google-services.json file. In order to use push messaging features of Ably SDK, additional FCM/APNS configuration is required.
See PushNotifications.md for detailed information on getting PN working with the example app.
See requirements for a list of prerequisites
In pubspec.yaml
file:
dependencies:
ably_flutter: ^1.2.20
import 'package:ably_flutter/ably_flutter.dart' as ably;
Also available in examples for pub.dev examples page
// Create an instance of ClientOptions with Ably key
final clientOptions = ably.ClientOptions(key: '<KEY>');
// Use ClientOptions to create Realtime or REST instance
ably.Realtime realtime = ably.Realtime(options: clientOptions);
ably.Rest rest = ably.Rest(options: clientOptions);
Also see docs: Auth and Security: Basic authentication
Supplying a TokenCallback
:
// Create an instance of ClientOptions with Ably token and authCallback
ably.ClientOptions clientOptions = ably.ClientOptions(
key: '<TOKEN>',
clientId: '<CLIENT>', // Optional
authCallback: (ably.TokenParams tokenParams) async {
// `createTokenRequest` should be implemented to communicate with user server
ably.TokenRequest tokenRequest = await createTokenRequest(tokenParams);
// `authCallback` has to return an instance of TokenRequest
return tokenRequest;
}
);
// Use ClientOptions to create Realtime or REST instance
ably.Realtime realtime = ably.Realtime(options: clientOptions);
ably.Rest rest = ably.Rest(options: clientOptions);
Also see docs: Auth and Security: Token authentication
ably.Realtime realtime = ably.Realtime(options: clientOptions);
DateTime time = realtime.time()
realtime.connection
.on()
.listen((ably.ConnectionStateChange stateChange) async {
// Handle connection state change events
});
realtime.connection
.on(ably.ConnectionEvent.connected) // Any type of `ConnectionEvent` can be specified
.listen((ably.ConnectionStateChange stateChange) async {
// Handle connection state change events
});
ably.RealtimeChannel channel = realtime.channels.get('channel-name');
await channel.attach()
await channel.detach()
channel
.on()
.listen((ably.ChannelStateChange stateChange) async {
// Handle channel state change events
});
channel
.on(ably.ChannelEvent.failed) // Any type of `ConnectionEvent` can be specified
.listen((ably.ChannelStateChange stateChange) async {
// Handle channel state change events
});
StreamSubscription<ably.Message> subscription =
channel
.subscribe()
.listen((ably.Message message) {
// Handle channel message
});
StreamSubscription<ably.Message> subscription =
channel
.subscribe(name: 'event1')
.listen((ably.Message message) {
// Handle channel messages with name 'event1'
});
StreamSubscription<ably.Message> subscription =
channel
.subscribe(names: ['event1', 'event2'])
.listen((ably.Message message) {
// Handle channel messages with name 'event1' or `event2`
});
await subscription.cancel()
// Publish simple message
await channel.publish(
name: "event1",
data: "hello world",
);
// Publish message data as json-encodable object
await channel.publish(
name: "event1",
data: {
"hello": "world",
"hey": "ably",
},
);
// Publish message as array of json-encodable objects
await channel.publish(
name: "event1",
data: [
{
"hello": {
"world": true,
},
"ably": {
"serious": "realtime",
},
],
);
// Publish message as an `ably.Message` object
await channel.publish(
message: ably.Message(
name: "event1",
data: {
"hello": "world",
}
),
);
await channel.publish(
messages: [
ably.Message(
name: "event1",
data: {
"hello": "world",
}
),
ably.Message(
name: "event1",
data: {
"hello": "ably",
}
),
],
);
// Get channel history with default parameters
ably.PaginatedResult<ably.Message> history = await channel.history()
// Get channel history with custom parameters
ably.PaginatedResult<ably.Message> filteredHistory = await channel.history(
ably.RealtimeHistoryParams(
direction: 'forwards',
limit: 10,
)
)
// Enter using client ID from `ClientOptions`
await channel.presence.enter();
// Enter using client ID from `ClientOptions` with additional data
await channel.presence.enter("hello");
await channel.presence.enter([1, 2, 3]);
await channel.presence.enter({"key": "value"});
// Enter with specified client ID
await channel.presence.enterClient("user1");
// Enter with specified client ID and additional data
await channel.presence.enterClient("user1", "hello");
await channel.presence.enterClient("user1", [1, 2, 3]);
await channel.presence.enterClient("user1", {"key": "value"});
// Update using client ID from `ClientOptions`
await channel.presence.update();
// Update using client ID from `ClientOptions` with additional data
await channel.presence.update("hello");
await channel.presence.update([1, 2, 3]);
await channel.presence.update({"key": "value"});
// Update with specified client ID
await channel.presence.updateClient("user1");
// Update with specified client ID and additional data
await channel.presence.updateClient("user1", "hello");
await channel.presence.updateClient("user1", [1, 2, 3]);
await channel.presence.updateClient("user1", {"key": "value"});
// Leave using client ID from `ClientOptions`
await channel.presence.leave();
// Leave using client ID from `ClientOptions` with additional data
await channel.presence.leave("hello");
await channel.presence.leave([1, 2, 3]);
await channel.presence.leave({"key": "value"});
// Leave with specified client ID
await channel.presence.leaveClient("user1");
// Leave with specified client ID and additional data
await channel.presence.leaveClient("user1", "hello");
await channel.presence.leaveClient("user1", [1, 2, 3]);
await channel.presence.leaveClient("user1", {"key": "value"});
// Get all presence messages
List<ably.PresenceMessage> presenceMessages = await channel.presence.get();
// Get presence messages with specific Client ID
presenceMessages = await channel.presence.get(
ably.RealtimePresenceParams(
clientId: 'clientId',
),
);
// Get presence messages with specific Connection ID
presenceMessages = await channel.presence.get(
ably.RealtimePresenceParams(
connectionId: 'connectionId',
),
);
// Get presence history with default parameters
ably.PaginatedResult<ably.PresenceMessage> history = await channel.presence.history()
// Get presence history with custom parameters
ably.PaginatedResult<ably.PresenceMessage> filteredHistory = await channel.presence.history(
ably.RealtimeHistoryParams(
direction: 'forwards',
limit: 10,
)
)
StreamSubscription<ably.PresenceMessage> subscription =
channel
.presence
.subscribe()
.listen((presenceMessage) {
// Handle presence message
},
);
StreamSubscription<ably.PresenceMessage> subscription =
channel
.presence
.subscribe(action: PresenceAction.enter)
.listen((presenceMessage) {
// Handle `enter` presence message
},
);
StreamSubscription<ably.PresenceMessage> subscription =
channel
.presence
.subscribe(actions: [
PresenceAction.enter,
PresenceAction.update,
])
.listen((presenceMessage) {
// Handle `enter` and `update` presence message
},
);
ably.Rest rest = ably.Rest(options: clientOptions);
DateTime time = rest.time()
ably.RestChannel channel = rest.channels.get('channel-name');
// Publish simple message
await channel.publish(
name: "event1",
data: "hello world",
);
// Publish message data as json-encodable object
await channel.publish(
name: "event1",
data: {
"hello": "world",
"hey": "ably",
},
);
// Publish message as array of json-encodable objects
await channel.publish(
name: "event1",
data: [
{
"hello": {
"world": true,
},
"ably": {
"serious": "realtime",
},
],
);
// Publish message as an `ably.Message` object
await channel.publish(
message: ably.Message(
name: "event1",
data: {
"hello": "world",
}
),
);
await channel.publish(
messages: [
ably.Message(
name: "event1",
data: {
"hello": "world",
}
),
ably.Message(
name: "event1",
data: {
"hello": "ably",
}
),
],
);
// Get channel history with default parameters
ably.PaginatedResult<ably.Message> history = await channel.history()
// Get channel history with custom parameters
ably.PaginatedResult<ably.Message> filteredHistory = await channel.history(
ably.RestHistoryParams(
direction: 'forwards',
limit: 10,
)
)
// Get all presence messages
List<ably.PresenceMessage> presenceMessages = await channel.presence.get();
// Get presence messages with specific Client ID
presenceMessages = await channel.presence.get(
ably.RestPresenceParams(
clientId: 'clientId',
),
);
// Get presence messages with specific Connection ID
presenceMessages = await channel.presence.get(
ably.RestPresenceParams(
connectionId: 'connectionId',
),
);
// Get presence history with default parameters
ably.PaginatedResult<ably.PresenceMessage> history = await channel.presence.history();
// Get presence history with custom parameters
ably.PaginatedResult<ably.PresenceMessage> filteredHistory = await channel.presence.history(
ably.RestHistoryParams(
direction: 'forwards',
limit: 10,
)
);
// Example PaginatedResult returned from channel history
ably.PaginatedResult<ably.Message> paginatedResult = await channel.history(params);
// Get list of items from result
List<ably.Message> items = paginatedResult.items;
// Example PaginatedResult returned from channel history
ably.PaginatedResult<ably.Message> paginatedResult = await channel.history(params);
// Check if next page is available
bool hasNextPage = paginatedResult.hasNext();
// Fetch next page if it's available
if (hasNextPage) {
paginatedResult = await paginatedResult.next();
}
String key = 'base64EncodedKey'; // Can also be an UInt8List
CipherParams cipherParams = ably.Crypto.getDefaultParams(key: key);
// For Realtime
RealtimeChannelOptions realtimeChannelOptions = ably.RealtimeChannelOptions(cipherParams: cipherParams);
RealtimeChannel channel = realtime.channels.get("channel-name");
channel.setOptions(realtimeChannelOptions)
// For REST
RestChannelOptions restChannelOptions = ably.RestChannelOptions(cipherParams: cipherParams);
RestChannel channel = rest.channels.get("channel-name");
channel.setOptions(restChannelOptions)
See PushNotifications.md for detailed information on using PN with this plugin.
- Quickstart Guide
- Introducing the Ably Flutter plugin by Srushtika (Developer Advocate)
- Building a Realtime Cryptocurrency App with Flutter by pr-Mais and escamoteur
- Building realtime apps with Flutter and WebSockets: client-side considerations
Flutter 2.5.0 or higher is required.
iOS 10 or newer.
API Level 19 (Android 4.4, KitKat) or newer.
This project uses Java 8 language features, utilizing Desugaring to support lower versions of the Android runtime (i.e. API Levels prior to 24)
If your project needs support for SDK Version lower than 24, Android Gradle Plugin 4.0.0+ must be used. You might also need to upgrade gradle distribution accordingly.
When increasing the version of ably_flutter
in your pubspec.yaml
, there may be breaking changes. To migrate code across these breaking changes, follow the updating / migration guide.
See Ably feature support matrix for a list of features supported by this SDK.
Features that we do not currently support, but we do plan to add in the future:
Using the Streams based approach doesn't fully conform with RTE6a from our client library features specification.
StreamSubscription subscriptionToBeCancelled;
// Listener registered 1st
realtime.connection.on().listen((ably.ConnectionStateChange stateChange) async {
if (stateChange.event == ably.ConnectionEvent.connected) {
await subscriptionToBeCancelled.cancel(); // Cancelling 2nd listener
}
});
// Listener registered 2nd
subscriptionToBeCancelled = realtime.connection.on().listen((ably.ConnectionStateChange stateChange) async {
print('State changed');
});
In the example above, the 2nd listener is cancelled when the 1st listener is notified about the "connected" event. As per RTE6a, the 2nd listener should also be triggered. It will not be as the 2nd listener was registered after the 1st listener and stream subscription is cancelled immediately after 1st listener is triggered.
This wouldn't have happened if the 2nd listener had been registered before the 1st was.
Instead of await subscriptionToBeCancelled.cancel();
, use
Future<void>.delayed(Duration.zero, () {
subscriptionToBeCancelled.cancel();
});
Please visit ably.com/support for access to our knowledge base and to ask for any assistance.
To see what has changed in recent versions, see the CHANGELOG.
For guidance on how to contribute to this project, see CONTRIBUTING.md.