Skip to content

Flutter SDK

Screen recording and feedback collection SDK for Flutter apps. Capture user sessions, bug reports, and feedback with automatic video upload.

Features

  • In-app screen recording with audio
  • Automatic upload to your Horus project
  • User identification and custom metadata
  • Progress tracking and error handling
  • iOS Privacy Manifest compliant (iOS 17+)
  • Android 15 (API 35) compatible

Installation

Add horus_sdk to your pubspec.yaml:

yaml
dependencies:
  horus_sdk: ^1.0.0

Then run:

bash
flutter pub get

Platform Setup

iOS

Add the following to your Info.plist:

xml
<!-- Required for microphone audio -->
<key>NSMicrophoneUsageDescription</key>
<string>We need microphone access to record audio with your screen recording.</string>

Android

Add these permissions to your AndroidManifest.xml:

xml
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />

Quick Start

dart
import 'package:horus_sdk/horus_sdk.dart';

// 1. Initialize the SDK (typically in main() or app startup)
final sdk = HorusSdk();

await sdk.initialize(HorusConfig(
  embedToken: 'proj_your_token_here',
  userInfo: HorusUserInfo(
    email: 'user@example.com',
    name: 'John Doe',
  ),
));

// 2. Start recording
await sdk.startRecording();

// 3. Stop and upload
await sdk.stopRecording(
  description: 'Bug report: Login button not working',
  metadata: {
    'screen': 'LoginScreen',
    'appVersion': '1.2.3',
  },
);

Event Handling

Listen to SDK events for UI updates:

dart
final sdk = HorusSdk();

// Recording started
sdk.onRecordingStarted.listen((_) {
  setState(() => _isRecording = true);
});

// Recording stopped
sdk.onRecordingStopped.listen((duration) {
  print('Recorded ${duration.toStringAsFixed(1)} seconds');
});

// Upload progress
sdk.onUploadProgress.listen((progress) {
  setState(() => _uploadProgress = progress);
});

// Upload complete
sdk.onUploadComplete.listen((result) {
  setState(() => _isRecording = false);
  ScaffoldMessenger.of(context).showSnackBar(
    SnackBar(content: Text('Recording uploaded!')),
  );
});

// Error handling with pattern matching
sdk.onError.listen((error) {
  setState(() => _isRecording = false);

  switch (error) {
    case PermissionDenied(:final message):
      _showPermissionDialog();
    case UploadFailed(:final message):
      _showRetryButton(message);
    case RecordingFailed(:final message):
      _showErrorSnackbar(message);
    default:
      _showErrorSnackbar(error.message);
  }
});

Configuration Options

dart
HorusConfig(
  // Required: Your project's embed token (from Horus dashboard)
  embedToken: 'proj_...',

  // Optional: API URL (defaults to production)
  apiUrl: 'https://tryhorus.io',

  // Optional: User identification
  userInfo: HorusUserInfo(
    email: 'user@example.com',
    name: 'John Doe',
    userId: 'user_123',
  ),

  // Optional: Custom metadata attached to all recordings
  customMetadata: {
    'appVersion': '1.2.3',
    'environment': 'production',
  },
)

Updating User Info

If the user logs in after initialization:

dart
await sdk.setUserInfo(HorusUserInfo(
  email: user.email,
  name: user.displayName,
  userId: user.id,
));

API Reference

HorusSdk

MethodDescription
initialize(config)Initialize the SDK with configuration
startRecording({includeAudio})Start in-app screen recording
stopRecording({description, metadata})Stop recording and upload
cancelRecording()Cancel recording without uploading
setUserInfo(userInfo)Update user information
isInitialized()Check if SDK is initialized
isRecording()Check if recording is in progress
getRecordingDuration()Get current recording duration
reset()Reset SDK state

Events

StreamEmits
onRecordingStartedWhen recording starts
onRecordingStoppedRecording duration when stopped
onUploadProgressUpload progress (0.0 - 1.0)
onUploadCompleteUploadResult on success
onErrorHorusError on failure

Error Types

Use Dart 3 pattern matching for exhaustive error handling:

dart
switch (error) {
  case NotInitialized(:final message):
    // SDK not initialized
  case PermissionDenied(:final message):
    // User denied recording permission
  case RecordingFailed(:final message):
    // Recording capture failed
  case UploadFailed(:final message):
    // Upload to server failed
  case NetworkError(:final message):
    // Network connectivity issue
  case InvalidConfig(:final message):
    // Configuration validation failed
}

Compliance

iOS Privacy Manifest

This SDK includes a PrivacyInfo.xcprivacy file that declares:

  • Collected Data Types: Screen recording data, audio data, device identifiers
  • Accessed APIs: System boot time, disk space, file timestamps
  • Tracking: Not used for tracking (NSPrivacyTracking = false)

Android API 35

This SDK targets Android API 35 and is compatible with Android 15's 16KB page size requirements.

Troubleshooting

Recording fails to start

  1. Check that screen recording permissions are granted
  2. Ensure the app has microphone permission (if audio is enabled)
  3. Verify the device supports screen recording

Upload fails

  1. Verify network connectivity
  2. Check that the embedToken is valid
  3. Ensure sufficient storage space

No audio in recording

  1. Request microphone permission first
  2. Ensure includeAudio: true is passed (default)
  3. Check that no other app is using the microphone

Support

Released under the MIT License.