seenn_flutter 0.8.5 copy "seenn_flutter: ^0.8.5" to clipboard
seenn_flutter: ^0.8.5 copied to clipboard

Flutter SDK for Seenn - Real-time job progress tracking with Polling and Live Activity support.

seenn_flutter #

Flutter SDK for Seenn - Real-time job progress tracking with Live Activity (iOS), and Ongoing Notification (Android) support.

pub package pub downloads License: MIT

Features #

  • Real-time Updates - Polling with automatic reconnection
  • iOS Live Activity - Lock Screen and Dynamic Island progress
  • Android Ongoing Notification - Persistent notification with progress bar
  • Provisional Push - iOS 12+ quiet notifications without permission prompt
  • ETA Countdown - Smart time estimates with confidence scores
  • Parent-Child Jobs - Hierarchical job relationships
  • Reactive Streams - RxDart-powered state management
  • Error Codes - Standardized error handling with SeennErrorCode
  • Input Validation - Client-side validation before native calls

Installation #

dependencies:
  seenn_flutter: ^0.8.4

Quick Start #

import 'package:seenn_flutter/seenn_flutter.dart';

// Initialize
final seenn = Seenn(SeennConfig(
  publicKey: 'pk_live_xxx',
  baseUrl: 'https://api.seenn.io',
));

// Connect for a user
await seenn.connect(userId: 'user_123');

// Subscribe to a job
final tracker = seenn.jobs.subscribe('job_abc');

tracker.onProgress.listen((update) {
  print('Progress: ${update.progress}%');
  print('ETA: ${update.etaFormatted}');
});

tracker.onComplete.listen((job) {
  print('Job completed!');
});

iOS Live Activity #

// Start Live Activity
await seenn.liveActivity.startActivity(
  jobId: 'job_abc',
  title: 'Processing video...',
);

// Auto-sync with job updates
seenn.jobs.stream('job_abc').listen((job) {
  if (job != null) {
    seenn.liveActivity.updateActivity(
      jobId: job.id,
      progress: job.progress,
      message: job.message,
    );
  }
});

iOS Setup #

Add to ios/Runner/Info.plist:

<key>NSSupportsLiveActivities</key>
<true/>

Provisional Push (iOS 12+) #

Request push notifications without showing a permission prompt:

import 'package:seenn_flutter/seenn_flutter.dart';

// Check current status
final status = await LiveActivity.getPushAuthorizationStatus();
print(status.status);        // PushAuthorizationStatus.provisional
print(status.isProvisional); // true if quiet notifications

// Request provisional push (no prompt!)
final granted = await LiveActivity.requestProvisionalPushAuthorization();
if (granted) {
  print('Provisional push enabled');
}

// Later: upgrade to full push when ready
if (status.canRequestFullAuthorization) {
  await LiveActivity.upgradeToStandardPush(); // Shows prompt
}

Note: Provisional notifications appear silently in Notification Center only. Users can "Keep" or "Turn Off" from their first notification.

Error Handling #

All Live Activity operations return results with error codes for programmatic handling:

import 'package:seenn_flutter/seenn_flutter.dart';

print('SDK Version: $sdkVersion'); // '0.8.4'

final result = await LiveActivity.start(
  jobId: 'job_123',
  title: 'Processing...',
  jobType: 'video',
);

if (!result.success) {
  switch (result.code) {
    case SeennErrorCode.platformNotSupported:
      print('Not on iOS');
      break;
    case SeennErrorCode.invalidJobId:
      print('Invalid job ID');
      break;
    case SeennErrorCode.bridgeNotRegistered:
      print('Native setup incomplete');
      break;
    default:
      print('Error [${result.code}]: ${result.error}');
  }
}

Android Ongoing Notification #

// Start notification
await seenn.ongoingNotification.startNotification(
  jobId: 'job_abc',
  title: 'Processing',
  message: 'Starting...',
);

// Update progress
await seenn.ongoingNotification.updateNotification(
  jobId: 'job_abc',
  progress: 50,
  message: 'Halfway there...',
);

// End notification
await seenn.ongoingNotification.endNotification(
  jobId: 'job_abc',
  title: 'Complete',
  message: 'Your video is ready!',
);

Cross-Platform Notifications #

Use JobNotificationService for unified iOS + Android handling:

// Automatically uses Live Activity on iOS, Ongoing Notification on Android
await seenn.jobNotification.startNotification(
  jobId: 'job_abc',
  title: 'Processing',
  message: 'Starting...',
);

// Sync with job updates
await seenn.jobNotification.syncWithJob(job);

ETA Countdown #

// Get countdown stream
final countdown = etaCountdownStream(
  job: job,
  intervalMs: 1000,
);

countdown.listen((state) {
  print('Remaining: ${state.formatted}'); // "2:34"
  print('Past due: ${state.isPastDue}');
  print('Confidence: ${state.confidence}'); // 0.0 - 1.0
});

Parent-Child Jobs #

// Get parent jobs
final parents = seenn.jobs.parents;

// Get children of a parent
final children = seenn.jobs.childrenOf('parent_job_id');

// Stream child progress
tracker.onChildProgress.listen((update) {
  print('${update.completed}/${update.total} complete');
  print('${update.failed} failed');
});

Job Filtering #

// By status
final active = seenn.jobs.active;
final completed = seenn.jobs.byStatus(JobStatus.completed);

// Reactive streams
seenn.jobs.active$.listen((jobs) {
  print('Active jobs: ${jobs.length}');
});

Connection Management #

// Check connection state
seenn.connection$.listen((state) {
  print('Connected: ${state.isConnected}');
});

// Manual reconnect
await seenn.reconnect();

// Disconnect
await seenn.disconnect();

Rich Push Notifications (iOS) #

Display images (avatars, thumbnails) in push notifications. Requires a Notification Service Extension.

Setup #

  1. Create extension in Xcode:

    • Open ios/Runner.xcworkspace
    • File → New → Target → Notification Service Extension
    • Name: NotificationServiceExtension
    • Language: Swift
  2. Replace the generated code in NotificationServiceExtension/NotificationService.swift:

import UserNotifications

class NotificationService: UNNotificationServiceExtension {
    var contentHandler: ((UNNotificationContent) -> Void)?
    var bestAttemptContent: UNMutableNotificationContent?

    override func didReceive(
        _ request: UNNotificationRequest,
        withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void
    ) {
        self.contentHandler = contentHandler
        bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)

        guard let bestAttemptContent = bestAttemptContent else {
            contentHandler(request.content)
            return
        }

        // Look for image URL in payload
        let userInfo = request.content.userInfo
        let imageUrlString = userInfo["senderAvatar"] as? String
            ?? userInfo["imageUrl"] as? String
            ?? userInfo["image"] as? String

        guard let urlString = imageUrlString,
              let imageUrl = URL(string: urlString) else {
            contentHandler(bestAttemptContent)
            return
        }

        // Download and attach image
        downloadImage(from: imageUrl) { attachment in
            if let attachment = attachment {
                bestAttemptContent.attachments = [attachment]
            }
            contentHandler(bestAttemptContent)
        }
    }

    override func serviceExtensionTimeWillExpire() {
        if let contentHandler = contentHandler,
           let bestAttemptContent = bestAttemptContent {
            contentHandler(bestAttemptContent)
        }
    }

    private func downloadImage(
        from url: URL,
        completion: @escaping (UNNotificationAttachment?) -> Void
    ) {
        let task = URLSession.shared.downloadTask(with: url) { localUrl, response, error in
            guard let localUrl = localUrl, error == nil else {
                completion(nil)
                return
            }

            let fileManager = FileManager.default
            let tempDir = fileManager.temporaryDirectory
            let fileName = url.lastPathComponent
            let destUrl = tempDir.appendingPathComponent(fileName)

            try? fileManager.removeItem(at: destUrl)
            do {
                try fileManager.moveItem(at: localUrl, to: destUrl)
                let attachment = try UNNotificationAttachment(
                    identifier: "image",
                    url: destUrl,
                    options: nil
                )
                completion(attachment)
            } catch {
                completion(nil)
            }
        }
        task.resume()
    }
}
  1. Build and run your app

Payload Format #

Include mutable-content: 1 and an image URL:

{
  "aps": {
    "alert": { "title": "Message", "body": "Hello!" },
    "mutable-content": 1
  },
  "senderAvatar": "https://example.com/avatar.jpg"
}

Supported fields: senderAvatar, imageUrl, image

Documentation #

Requirements #

  • Flutter >= 3.10.0
  • Dart >= 3.0.0
  • iOS 16.2+ (for Live Activity with push updates)
  • Android API 21+ (for Ongoing Notification)

Why iOS 16.2? While Live Activities were introduced in iOS 16.1, the push token API (pushType: .token) and ActivityContent struct required for remote backend updates were added in iOS 16.2.

License #

MIT License - see LICENSE for details.

0
likes
145
points
45
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Flutter SDK for Seenn - Real-time job progress tracking with Polling and Live Activity support.

Homepage
Repository (GitHub)

License

MIT (license)

Dependencies

flutter, http, rxdart, shared_preferences

More

Packages that depend on seenn_flutter

Packages that implement seenn_flutter