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();