
EventFlux
A Server-Sent Events client for Dart and Flutter, with WHATWG-compliant parsing, automatic reconnection, and native and web support.
Building with an AI coding assistant? EventFlux bundles a package skill that teaches your agent how to integrate streams, manage connection lifecycles, and migrate from v2.
Installation Β· Examples Β· API reference Β· Migrating from v2
Features π
- π WHATWG SSE spec-compliant event stream parsing with persistent
lastEventIdandretry:field support - π Auto-reconnect with linear or exponential backoff, random jitter, and configurable
maxBackoffcap - π Interceptor chain β hook into request, response, and error lifecycle stages
- π Mid-flight abort via a
Future<void>trigger - β±οΈ Idle timeout detection β drops the connection if no data arrives within a configured duration
- π Web platform support with CORS, credentials, and caching configuration via
WebConfig - π Event filtering by type using
response.where() - π Multipart request support
- ποΈ Singleton (
EventFlux.instance) and multiple independent connections (EventFlux.spawn()) - π Pluggable HTTP clients via
HttpClientAdapter - π§ HTTP error classification β retries 5xx, 408, and 429 responses; other HTTP errors are not retried
Installation
Requires Dart SDK >=3.4.0 and Flutter >=3.0.0.
Agents
Install with your AI assistant
1. Add EventFlux to your app
flutter pub add 'eventflux:^3.0.2'
2. Install the package skill
From your app's root directory, run:
dart run skills@ get -p eventflux
Choose your coding assistant when prompted. For Codex, use dart run skills@ get --agent codex -p eventflux.
The bundled eventflux-usage skill covers connection ownership, stream subscriptions, reconnects, authentication headers, browser setup, and v2 migration. The skills CLI requires Dart 3.10 or later and installs agent guidance without adding a runtime dependency. See the Dart package skills guide for setup details.
3. Ask your assistant to build the integration
Replace <SSE_URL> with your endpoint, then use a prompt like:
Use the eventflux-usage skill to connect this app to <SSE_URL>.
Follow the app's existing state management. Handle incoming events,
errors, and reconnects, and clean up the connection and subscription
when their owner is disposed. Include WebConfig if the app targets web.
For an existing v2 integration, ask: βUse eventflux-usage to migrate this app to EventFlux v3 and check its connection lifecycle.β
Manual
To set up manually, add EventFlux to your pubspec.yaml, then run flutter pub get:
dependencies:
eventflux: ^3.0.2
Usage
On web, every connect() call requires webConfig: WebConfig() (or a custom configuration). See the Web Platform example below.
Basic Connection β Connect to an SSE endpoint in a few lines
import 'package:eventflux/eventflux.dart';
void main() {
EventFlux.instance.connect(
EventFluxConnectionType.get,
'https://example.com/events',
onSuccessCallback: (EventFluxResponse? response) {
response?.stream?.listen((EventFluxData data) {
print('Event: ${data.event}');
print('Data: ${data.data}');
});
},
onError: (EventFluxException error) {
print('Error: $error');
},
onConnectionClose: () {
print('Connection closed');
},
);
}
Auto-Reconnect β Exponential backoff with jitter and token refresh
Replace refreshAccessToken() with your app's token-refresh function. Auto-reconnect handles connection errors, stream closure, and idle timeouts; among HTTP error responses, only 5xx, 408, and 429 are retried.
import 'package:eventflux/eventflux.dart';
void main() {
EventFlux.instance.connect(
EventFluxConnectionType.get,
'https://example.com/events',
onSuccessCallback: (EventFluxResponse? response) {
response?.stream?.listen((data) {
print('Data: ${data.data}');
});
},
onError: (error) {
print('Error: $error');
},
autoReconnect: true,
reconnectConfig: ReconnectConfig(
mode: ReconnectMode.exponential,
interval: Duration(seconds: 2),
maxAttempts: 10,
maxBackoff: Duration(seconds: 30),
connectionTimeout: Duration(seconds: 60),
onReconnect: (int attempt, Duration delay) {
print('Reconnect attempt $attempt after $delay');
},
reconnectHeader: () async {
String newToken = await refreshAccessToken();
return {
'Authorization': 'Bearer $newToken',
'Accept': 'text/event-stream',
};
},
),
);
}
Interceptors β Inject auth headers, log responses, suppress errors
import 'package:eventflux/eventflux.dart';
import 'package:http/http.dart';
class AuthInterceptor extends EventFluxInterceptor {
@override
Future<BaseRequest> onRequest(BaseRequest request) async {
request.headers['Authorization'] = 'Bearer my-token';
return request;
}
@override
Future<StreamedResponse> onResponse(StreamedResponse response) async {
print('Response status: ${response.statusCode}');
return response;
}
@override
Future<EventFluxException?> onError(EventFluxException exception) async {
print('Intercepted error: $exception');
// Return null to suppress the error, or return exception to propagate it
return exception;
}
}
void main() {
EventFlux.instance.connect(
EventFluxConnectionType.get,
'https://example.com/events',
interceptors: [AuthInterceptor()],
onSuccessCallback: (EventFluxResponse? response) {
response?.stream?.listen((data) {
print('Data: ${data.data}');
});
},
onError: (error) {
print('Error: $error');
},
);
}
Abort a Connection β Cancel an in-flight request at any time
import 'dart:async';
import 'package:eventflux/eventflux.dart';
void main() {
final completer = Completer<void>();
EventFlux.instance.connect(
EventFluxConnectionType.get,
'https://example.com/events',
abortTrigger: completer.future,
onSuccessCallback: (EventFluxResponse? response) {
response?.stream?.listen((data) {
print('Data: ${data.data}');
});
},
onError: (error) {
print('Error: $error');
},
);
// Cancel the connection at any time
Future.delayed(Duration(seconds: 10), () {
completer.complete();
});
}
Web Platform β Configure CORS and credentials for browser SSE
import 'package:eventflux/eventflux.dart';
import 'package:flutter/foundation.dart' show kIsWeb;
void main() {
EventFlux.instance.connect(
EventFluxConnectionType.get,
'https://example.com/events',
webConfig: kIsWeb
? WebConfig(
mode: WebConfigRequestMode.cors,
credentials: WebConfigRequestCredentials.omit,
)
: null,
onSuccessCallback: (EventFluxResponse? response) {
response?.stream?.listen((data) {
print('Data: ${data.data}');
});
},
onError: (error) {
print('Error: $error');
},
);
}
Multiple Connections β Run independent SSE streams in parallel
import 'package:eventflux/eventflux.dart';
void main() {
EventFlux e1 = EventFlux.spawn();
EventFlux e2 = EventFlux.spawn();
e1.connect(
EventFluxConnectionType.get,
'https://example.com/stream-1',
tag: 'Stream 1',
onSuccessCallback: (EventFluxResponse? response) {
response?.stream?.listen((data) {
print('Stream 1: ${data.data}');
});
},
onError: (error) {
print('Stream 1 error: $error');
},
);
e2.connect(
EventFluxConnectionType.get,
'https://example.com/stream-2',
tag: 'Stream 2',
onSuccessCallback: (EventFluxResponse? response) {
response?.stream?.listen((data) {
print('Stream 2: ${data.data}');
});
},
onError: (error) {
print('Stream 2 error: $error');
},
);
// Disconnect both when done
// await e1.disconnect();
// await e2.disconnect();
}
Event Filtering β filter events by type using where():
response?.where('message').listen((data) {
print('Message event: ${data.data}');
});
API reference
Connection options, reconnect configuration, and public types
Connect
Connects to a server-sent event stream.
| Parameter | Type | Description | Default |
|---|---|---|---|
type |
EventFluxConnectionType |
HTTP method (get or post) |
β |
url |
String |
SSE stream URL | β |
onSuccessCallback |
Function(EventFluxResponse?) |
Callback on successful connection (required) | β |
header |
Map<String, String> |
HTTP headers; Cache-Control is removed on web in favor of WebConfig.cache |
{'Accept': 'text/event-stream', 'Cache-Control': 'no-store'} |
onConnectionClose |
Function()? |
Called when the connection closes | β |
autoReconnect |
bool |
Auto-reconnect on disconnection | false |
reconnectConfig |
ReconnectConfig? |
Reconnection settings (required if autoReconnect is true) |
β |
onError |
Function(EventFluxException)? |
Error callback | β |
body |
Map<String, dynamic>? |
Request body for POST | β |
files |
List<MultipartFile>? |
Files for multipart requests | β |
multipartRequest |
bool |
Send as multipart | false |
tag |
String? |
Debug tag (appears in logs) | β |
logReceivedData |
bool |
Log received SSE data | false |
httpClient |
HttpClientAdapter? |
Custom HTTP client | β |
webConfig |
WebConfig? |
Web platform configuration (required on web) | β |
interceptors |
List<EventFluxInterceptor>? |
Request/response/error interceptors | β |
abortTrigger |
Future<void>? |
Future that aborts the connection when completed | β |
ReconnectConfig
| Parameter | Type | Description | Default |
|---|---|---|---|
mode |
ReconnectMode |
linear or exponential (required) |
β |
interval |
Duration |
Base retry interval | Duration(seconds: 2) |
maxAttempts |
int |
Max reconnect attempts (-1 for unlimited) | 5 |
maxBackoff |
Duration |
Cap for exponential backoff | Duration(seconds: 30) |
connectionTimeout |
Duration? |
Idle timeout β drops connection if no data received | β |
onReconnect |
void Function(int attempt, Duration delay)? |
Called on each reconnect attempt | β |
reconnectHeader |
Future<Map<String, String>> Function()? |
Async header refresh for reconnect | β |
EventFluxInterceptor
Subclass EventFluxInterceptor and override any of the following methods:
| Method | Signature | Description |
|---|---|---|
onRequest |
Future<BaseRequest> onRequest(BaseRequest request) |
Modify the request before sending (e.g., inject auth headers). Throw EventFluxException to abort. |
onResponse |
Future<StreamedResponse> onResponse(StreamedResponse response) |
Inspect the response after receiving. Must not consume the stream body. |
onError |
Future<EventFluxException?> onError(EventFluxException exception) |
Handle or suppress errors. Return null to suppress the exception. |
EventFluxStatus
| Value | Description |
|---|---|
connectionInitiated |
Connection process has started |
connected |
Successfully connected to the event stream |
reconnecting |
Auto-reconnect is in progress |
disconnected |
Connection has been closed |
error |
An error occurred during connection or disconnection |
Disconnect
EventFluxStatus status = await EventFlux.instance.disconnect();
Returns a Future<EventFluxStatus> indicating the disconnection status.
Spawn
EventFlux instance = EventFlux.spawn();
Returns a new independent EventFlux instance for managing parallel SSE connections.
Migrating from v2
Breaking changes and additions in v3
If you're upgrading from v2, here's what changed:
Breaking
onReconnectcallback signature changed from()to(int attempt, Duration delay)- Default request headers now include
Cache-Control: no-storeon native platforms; browsers useWebConfig.cacheinstead - Minimum Dart SDK raised to
>=3.4.0, Flutter>=3.0.0 webConfigis now required when running on web
New in v3
EventFluxStatus.reconnectingstatus valueoriginalErrorandstackTracefields onEventFluxException- Request/response/error interceptors via
interceptorsparameter - Mid-flight abort support via
abortTriggerparameter - Idle timeout detection via
connectionTimeoutonReconnectConfig maxBackoffonReconnectConfigto cap exponential backoff- WHATWG-compliant SSE parser with persistent
lastEventId,retry:field support, and U+2028 sanitization
Contributors π
EventFlux wouldn't exist without these people who believed it could be better.
Contributing π€
Contributions are welcome β open an issue or submit a pull request. Every bit helps.
License
Licensed under MIT.
Libraries
- client
- Imports
- enum
- eventflux
- extensions/fetch_client_extension
- http_client_adapter
- models/base
- models/data
- models/exception
- models/interceptor
- models/reconnect
- models/response
- models/web_config/redirect_policy
- models/web_config/request_cache
- models/web_config/request_credentials
- models/web_config/request_mode
- models/web_config/request_referrer_policy
- models/web_config/web_config
- utils







