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.