Flutter SDK
seenn_flutter — Reactive streams with RxDart and iOS Live Activity support
#Installation
flutter pub add seenn_flutter
Or add to your pubspec.yaml:
dependencies:
seenn_flutter: ^0.1.0
#Initialization
Seenn.init({appId, userToken, config?})
Initialize the SDK once at app startup. Must be called before using any other SDK features.
Parameters
appId
String
Your Seenn app IDrequired
userToken
String
User token from your backendrequired
config
SeennConfig?
Optional configuration options
import 'package:seenn_flutter/seenn_flutter.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize Seenn SDK
await Seenn.init(
appId: 'app_xxx',
userToken: userToken, // From your backend
);
runApp(const MyApp());
}
#Configuration Options
await Seenn.init(
appId: 'app_xxx',
userToken: userToken,
config: SeennConfig(
apiUrl: 'https://api.seenn.io',
sseUrl: 'https://sse.seenn.io',
timeout: Duration(seconds: 30),
debug: true,
),
);
// Or use development config for local testing
await Seenn.init(
appId: 'test_app',
userToken: 'test_token',
config: SeennConfig.development(
apiUrl: 'http://localhost:3001',
sseUrl: 'http://localhost:3000',
),
);
#jobs.subscribe()
Seenn.instance.jobs.subscribe(jobId) → JobTracker
Subscribe to updates for a specific job. Returns a JobTracker with convenient streams.
final tracker = Seenn.instance.jobs.subscribe('job_123');
// Listen to all updates
tracker.onUpdate.listen((job) {
print('Job updated: \${job.status}');
});
// Listen to progress changes only
tracker.onProgress.listen((update) {
print('Progress: \${update.progress}%');
print('Message: \${update.message}');
if (update.stage != null) {
print('Stage: \${update.stage.current}/\${update.stage.total}');
}
});
// Listen to completion
tracker.onComplete.listen((job) {
print('Completed! URL: \${job.resultUrl}');
});
// Listen to failures
tracker.onFailed.listen((job) {
print('Failed: \${job.errorMessage}');
});
// Listen to terminal state (completed or failed)
tracker.onTerminal.listen((job) {
print('Job finished with status: \${job.status}');
});
#JobTracker
The JobTracker class provides multiple streams and properties for tracking a job:
| Property / Method | Type | Description |
|---|---|---|
onUpdate |
Stream<SeennJob> |
All job updates |
onProgress |
Stream<ProgressUpdate> |
Progress changes only (deduplicated) |
onChildProgress |
Stream<ChildProgressUpdate> |
Child job progress (for parent jobs) |
onComplete |
Stream<SeennJob> |
Emits when job completes |
onFailed |
Stream<SeennJob> |
Emits when job fails |
onTerminal |
Stream<SeennJob> |
Emits on completion or failure |
current |
SeennJob? |
Current job state (sync) |
isCompleted |
bool |
Whether job is completed |
isFailed |
bool |
Whether job has failed |
isTerminal |
bool |
Whether job is in terminal state |
#Using with StreamBuilder
The SDK integrates naturally with Flutter's StreamBuilder:
class JobProgressWidget extends StatelessWidget {
final String jobId;
const JobProgressWidget({required this.jobId});
@override
Widget build(BuildContext context) {
return StreamBuilder<SeennJob?>(
stream: Seenn.instance.jobs.stream(jobId),
builder: (context, snapshot) {
final job = snapshot.data;
if (job == null) {
return const CircularProgressIndicator();
}
return Column(
children: [
Text(job.title),
LinearProgressIndicator(value: job.progress / 100),
Text('\${job.progress}%'),
if (job.message != null)
Text(job.message!),
if (job.status == JobStatus.completed)
ElevatedButton(
onPressed: () => openUrl(job.resultUrl!),
child: const Text('Download'),
),
],
);
},
);
}
}
#jobs.all$
Seenn.instance.jobs.all$ → Stream<Map<String, SeennJob>>
Stream of all jobs for the current user. Updates whenever any job changes.
StreamBuilder<Map<String, SeennJob>>(
stream: Seenn.instance.jobs.all$,
builder: (context, snapshot) {
final jobs = snapshot.data?.values.toList() ?? [];
return ListView.builder(
itemCount: jobs.length,
itemBuilder: (context, index) {
final job = jobs[index];
return ListTile(
title: Text(job.title),
subtitle: Text('\${job.progress}% - \${job.status}'),
);
},
);
},
);
#Connection State
Monitor the SSE connection status:
// Check if connected (sync)
if (Seenn.instance.isConnected) {
print('Connected to Seenn');
}
// Stream of connection state changes
StreamBuilder<SeennConnectionState>(
stream: Seenn.instance.connectionState$,
builder: (context, snapshot) {
final state = snapshot.data ?? SeennConnectionState.disconnected;
return Icon(
state.isConnected ? Icons.cloud_done : Icons.cloud_off,
color: state.isConnected ? Colors.green : Colors.red,
);
},
);
#Connection States
| State | Description |
|---|---|
connected |
SSE connection is active and receiving events |
connecting |
Attempting to establish connection |
reconnecting |
Reconnecting after connection loss |
disconnected |
Not connected (initial state or after dispose) |
#SeennJob Model
The job model contains all job data:
| Property | Type | Description |
|---|---|---|
jobId |
String |
Unique job identifier |
userId |
String |
User who owns this job |
status |
JobStatus |
pending | queued | running | completed | failed |
title |
String |
Human-readable title |
progress |
int |
Progress percentage (0-100) |
message |
String? |
Current status message |
stage |
StageInfo? |
Current stage (index, total, label) |
queue |
QueueInfo? |
Queue position info |
eta |
int? |
Estimated seconds to completion |
resultUrl |
String? |
Result URL (on completion) |
errorMessage |
String? |
Error message (on failure) |
isTerminal |
bool |
Helper: true if completed or failed |
#Cleanup
// Call when user logs out or app closes
await Seenn.dispose();
// Update user token (e.g., after token refresh)
await Seenn.setUserToken(newToken);
// Force reconnect
await Seenn.instance.reconnect();