rivium_trace_flutter_sdk 0.1.0 copy "rivium_trace_flutter_sdk: ^0.1.0" to clipboard
rivium_trace_flutter_sdk: ^0.1.0 copied to clipboard

Flutter SDK for RiviumTrace error tracking platform. Supports web, mobile, and Chrome extensions.

example/lib/main.dart

// example/main.dart
import 'package:flutter/material.dart';
import 'package:rivium_trace_flutter_sdk/rivium_trace_flutter_sdk.dart';

void main() async {
  // ========================================================================
  // RECOMMENDED: Use initWithZone for comprehensive error catching
  // This wraps the app in runZonedGuarded to catch async errors,
  // and automatically handles crash detection on mobile platforms.
  //
  // IMPORTANT: Call initWithZone BEFORE WidgetsFlutterBinding.ensureInitialized()
  // ========================================================================
  await RiviumTrace.initWithZone(
    RiviumTraceConfig(
      apiKey: 'rv_live_64e0ada5eeb66e3adf6136337802a5a34713ce4966372854',
      environment: 'development',
      release: '0.1.0',
      debug: true,
      enabled: true,
      captureUncaughtErrors: true,
      // Sample rate: 1.0 = capture 100% of errors
      // Set to 0.5 for 50%, 0.25 for 25%, etc.
      sampleRate: 1.0,
      // Offline storage: automatically stores errors when offline (mobile only)
      enableOfflineStorage: true,
      maxBreadcrumbs: 30,
    ),
    () async {
      WidgetsFlutterBinding.ensureInitialized();

      // Set user ID for tracking
      RiviumTrace.setUserId('user123');

      // Set global tags and extra context
      RiviumTrace.setTag('app_variant', 'full');
      RiviumTrace.setExtra('onboarding_completed', true);

      // Enable logging
      RiviumTrace.enableLogging(
        sourceId: 'flutter-demo-app',
        sourceName: 'Flutter Demo App',
      );

      runApp(MyApp());
    },
  );
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'RiviumTrace SDK Demo',
      navigatorObservers: [
        // Add RiviumTrace navigator observer for automatic breadcrumbs
        RiviumTraceNavigatorObserver(),
      ],
      home: HomePage(),
      routes: {'/details': (context) => DetailsPage()},
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  String _status = 'Ready';

  @override
  void initState() {
    super.initState();

    // Log app start
    RiviumTrace.info('Home page initialized');

    // Send an info message
    RiviumTrace.captureMessage('User opened the app');
  }

  Future<void> _testErrorCapture() async {
    setState(() => _status = 'Capturing error...');

    try {
      throw Exception('This is a test error for RiviumTrace');
    } catch (e, stackTrace) {
      await RiviumTrace.captureException(
        e,
        stackTrace: stackTrace,
        message: 'User triggered test error',
        extra: {'user_action': 'test_error_button'},
      );

      setState(() => _status = 'Error captured!');
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text('Error captured and sent to RiviumTrace'),
            backgroundColor: Colors.red,
          ),
        );
      }
    }
  }

  // ========================================================================
  // Intentional Crash - Test uncaught exception handling
  // When using initWithZone, the zone catches this and sends it to RiviumTrace.
  // On next app launch, CrashDetector also detects the unclean exit.
  // ========================================================================
  void _testTriggerCrash() {
    RiviumTrace.addUserBreadcrumb('User triggered intentional crash');
    RiviumTrace.captureMessage(
      'About to crash intentionally',
      level: MessageLevel.warning,
    );

    // This throws outside a try/catch — the zone from initWithZone catches it
    throw Exception('Intentional crash for testing RiviumTrace');
  }

  Future<void> _testMessages() async {
    setState(() => _status = 'Sending messages...');

    // Info message
    await RiviumTrace.captureMessage('User completed onboarding');

    // Warning message with extra data
    await RiviumTrace.captureMessage(
      'API rate limit approaching',
      level: MessageLevel.warning,
      extra: {'remaining_calls': 10, 'limit': 100},
    );

    // Debug message without breadcrumbs
    await RiviumTrace.captureMessage(
      'Cache hit for user data',
      level: MessageLevel.debug,
      includeBreadcrumbs: false,
      extra: {'cache_key': 'user_profile'},
    );

    // Error level message
    await RiviumTrace.captureMessage(
      'Payment processing failed',
      level: MessageLevel.error,
      extra: {'transaction_id': 'tx_12345', 'amount': 99.99},
    );

    setState(() => _status = 'Messages sent!');
    if (mounted) {
      ScaffoldMessenger.of(
        context,
      ).showSnackBar(SnackBar(content: Text('4 messages sent to RiviumTrace')));
    }
  }

  Future<void> _testLogging() async {
    setState(() => _status = 'Sending logs...');

    RiviumTrace.trace('Entering checkout flow');
    RiviumTrace.debug('Cart items loaded', metadata: {'item_count': 3});
    RiviumTrace.info('User started checkout');
    RiviumTrace.warn('Inventory low for item SKU-123', metadata: {'stock': 2});
    RiviumTrace.logError('Failed to apply discount code');
    RiviumTrace.fatal('Database connection lost');

    // Flush logs immediately
    await RiviumTrace.flushLogs();

    setState(
      () => _status = 'Logs sent! (${RiviumTrace.pendingLogCount} pending)',
    );
    if (mounted) {
      ScaffoldMessenger.of(
        context,
      ).showSnackBar(SnackBar(content: Text('6 logs sent to RiviumTrace')));
    }
  }

  Future<void> _testPerformance() async {
    setState(() => _status = 'Tracking performance...');

    // Track an async operation
    final result = await RiviumTrace.trackOperation(
      'simulateApiCall',
      () async {
        await Future.delayed(Duration(milliseconds: 350));
        return 'success';
      },
    );

    // Report a manual performance span
    await RiviumTrace.reportPerformanceSpan(
      PerformanceSpan.fromHttpRequest(
        method: 'GET',
        url: 'https://api.example.com/users',
        startTime: DateTime.now().subtract(Duration(milliseconds: 200)),
        durationMs: 200,
        statusCode: 200,
        platform: RiviumTrace.getPlatform(),
        environment: 'production',
        releaseVersion: '0.1.0',
      ),
    );

    // Report a DB query span
    await RiviumTrace.reportPerformanceSpan(
      PerformanceSpan.forDbQuery(
        queryType: 'SELECT',
        tableName: 'users',
        startTime: DateTime.now().subtract(Duration(milliseconds: 15)),
        durationMs: 15,
        rowsAffected: 1,
        platform: RiviumTrace.getPlatform(),
        environment: 'production',
      ),
    );

    setState(() => _status = 'Performance tracked! Result: $result');
    if (mounted) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Performance spans sent to RiviumTrace')),
      );
    }
  }

  // ========================================================================
  // Performance HTTP Client - Automatic HTTP request tracking
  // Wraps an http.Client to automatically capture performance spans
  // for every HTTP request made through it.
  // ========================================================================
  Future<void> _testPerformanceHttpClient() async {
    setState(() => _status = 'Testing PerformanceHttpClient...');

    // Create a performance-tracking HTTP client via the SDK factory
    final client = RiviumTrace.createPerformanceClient(
      addBreadcrumbs: true,
      // Optionally exclude hosts you don't want tracked
      excludedHosts: {'internal-service.local'},
      // Only report requests slower than 10ms
      minDurationMs: 10,
    );

    try {
      // Every request through this client is automatically tracked as a
      // performance span and sent to RiviumTrace — no manual span creation needed
      final response = await client.get(
        Uri.parse('https://jsonplaceholder.typicode.com/posts/1'),
      );

      // Make a second request to demonstrate batch span reporting
      final response2 = await client.get(
        Uri.parse('https://jsonplaceholder.typicode.com/users/1'),
      );

      // Force flush any buffered spans
      await client.flush();

      setState(
        () => _status =
            'PerformanceHttpClient done! Status: ${response.statusCode}, ${response2.statusCode}',
      );
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text('2 HTTP requests auto-tracked as performance spans'),
          ),
        );
      }
    } catch (e) {
      setState(() => _status = 'HTTP request failed: $e');
    } finally {
      client.close();
    }
  }

  Future<void> _testBreadcrumbs() async {
    setState(() => _status = 'Adding breadcrumbs...');

    // Add various breadcrumb types
    RiviumTrace.addBreadcrumb('App initialized', type: BreadcrumbType.system);
    RiviumTrace.addUserBreadcrumb('Tapped breadcrumb button');
    RiviumTrace.addHttpBreadcrumb(
      'GET',
      'https://api.example.com/data',
      statusCode: 200,
      durationMs: 150,
    );
    RiviumTraceBreadcrumbs.addState(
      'theme_changed',
      data: {'from': 'light', 'to': 'dark'},
    );

    // Send a message with the breadcrumbs attached so they reach the backend
    await RiviumTrace.captureMessage(
      'Breadcrumb test - 4 breadcrumbs recorded',
      level: MessageLevel.info,
      extra: {'breadcrumb_count': 4, 'test_type': 'breadcrumbs'},
    );

    setState(() => _status = 'Breadcrumbs added and sent!');
    if (mounted) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('4 breadcrumbs added and sent to RiviumTrace')),
      );
    }
  }

  // ========================================================================
  // Crash Detection - Check if the app crashed in the previous session
  // Mobile only: uses marker files to detect unclean exits.
  // Automatically handled by initWithZone, but you can also check manually.
  // ========================================================================
  Future<void> _testCrashDetection() async {
    setState(() => _status = 'Checking crash status...');

    final didCrash = await CrashDetector.didCrashLastSession();
    final lastCrashTime = await CrashDetector.getLastCrashTime();

    if (didCrash) {
      // Retrieve the crash report from the previous session
      final crashReport = await CrashDetector.getCrashReport(
        RiviumTrace.getPlatform() ?? 'unknown',
        'production',
        '0.1.0',
      );

      if (crashReport != null) {
        // Send the crash report to RiviumTrace
        await RiviumTrace.captureException(
          Exception(crashReport.message),
          message: 'Crash detected from previous session',
          extra: {
            'crash_time': lastCrashTime?.toIso8601String(),
            'detected_at': DateTime.now().toIso8601String(),
          },
        );
      }

      setState(
        () => _status =
            'Previous session crashed at ${lastCrashTime?.toIso8601String() ?? "unknown"}',
      );
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text('Crash detected from previous session!'),
            backgroundColor: Colors.red,
          ),
        );
      }
    } else {
      setState(() => _status = 'No crash detected in previous session');
      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text('No crash in previous session (clean exit)'),
            backgroundColor: Colors.green,
          ),
        );
      }
    }
  }

  // ========================================================================
  // Offline Storage - Store errors locally when offline, retry on reconnect
  // Enabled via enableOfflineStorage: true in RiviumTraceConfig (default).
  // Mobile only — web platform is excluded.
  // ========================================================================
  Future<void> _testOfflineStorage() async {
    setState(() => _status = 'Checking offline storage...');

    final hasStored = await OfflineStorageService.hasStoredErrors();
    final storedCount = await OfflineStorageService.getStoredErrorCount();

    if (hasStored) {
      // Retrieve and display stored errors
      final storedErrors = await OfflineStorageService.getStoredErrors();

      setState(
        () => _status =
            'Offline storage: $storedCount errors stored\n'
            'First stored: ${storedErrors.first['message'] ?? 'unknown'}',
      );

      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text(
              '$storedCount offline errors found — SDK will auto-retry sending',
            ),
            backgroundColor: Colors.orange,
          ),
        );
      }
    } else {
      // Demonstrate how offline storage works:
      // When the SDK fails to send an error (no network), it automatically
      // stores it locally. On next app launch, stored errors are retried.
      setState(
        () => _status =
            'Offline storage: 0 errors stored\n'
            'Errors are auto-stored when network is unavailable (mobile only)',
      );

      if (mounted) {
        ScaffoldMessenger.of(context).showSnackBar(
          SnackBar(
            content: Text(
              'No offline errors stored — errors queue automatically when offline',
            ),
          ),
        );
      }
    }
  }

  // ========================================================================
  // Sample Rate Demo - Show how sampleRate affects error capture
  // ========================================================================
  Future<void> _testSampleRate() async {
    setState(() => _status = 'Testing sample rate...');

    // The sample rate is configured at init time (see main()).
    // With sampleRate: 1.0, all errors are captured.
    // With sampleRate: 0.5, ~50% of errors are randomly dropped.
    //
    // To demonstrate, we send 10 errors and check how many are actually sent.
    // With sampleRate: 1.0 (current config), all 10 should be sent.
    // If you change to sampleRate: 0.5, roughly 5 of 10 will be sent.

    int sentCount = 0;

    for (int i = 0; i < 10; i++) {
      try {
        throw Exception('Sample rate test error #$i');
      } catch (e, stackTrace) {
        await RiviumTrace.captureException(
          e,
          stackTrace: stackTrace,
          message: 'Sample rate test',
          extra: {'error_index': i, 'total_errors': 10},
          callback: (success) {
            if (success) sentCount++;
          },
        );
      }
    }

    // Small delay to let callbacks complete
    await Future.delayed(Duration(milliseconds: 500));

    setState(
      () => _status =
          'Sample rate test: $sentCount/10 errors sent\n'
          'Current rate: 1.0 (100%)',
    );
    if (mounted) {
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(
          content: Text('$sentCount of 10 errors captured (sampleRate: 1.0)'),
        ),
      );
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('RiviumTrace SDK Demo')),
      body: Padding(
        padding: EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            // Status card
            Card(
              color: Colors.blue[50],
              child: Padding(
                padding: EdgeInsets.all(16),
                child: Column(
                  children: [
                    Text(
                      'SDK Status',
                      style: TextStyle(
                        fontWeight: FontWeight.bold,
                        fontSize: 16,
                      ),
                    ),
                    SizedBox(height: 8),
                    Text('Initialized: ${RiviumTrace.isInitialized()}'),
                    Text('Platform: ${RiviumTrace.getPlatform() ?? "unknown"}'),
                    Text('User: ${RiviumTrace.getUserId() ?? "not set"}'),
                    Text(
                      'Session: ${RiviumTrace.getSessionId()?.substring(0, 8) ?? "none"}...',
                    ),
                    SizedBox(height: 8),
                    Text(_status, style: TextStyle(color: Colors.grey[600])),
                  ],
                ),
              ),
            ),

            SizedBox(height: 16),

            // Feature buttons
            Expanded(
              child: ListView(
                children: [
                  _buildSectionHeader('Core Features'),
                  _buildFeatureButton(
                    'Capture Error',
                    'Test exception capture with stack trace',
                    Icons.error,
                    Colors.red,
                    _testErrorCapture,
                  ),
                  _buildFeatureButton(
                    'Trigger Crash',
                    'Throw uncaught exception (will crash app!)',
                    Icons.dangerous,
                    Colors.red[900]!,
                    _testTriggerCrash,
                  ),
                  _buildFeatureButton(
                    'Send Messages',
                    'Send info, warning, debug, and error messages',
                    Icons.message,
                    Colors.blue,
                    _testMessages,
                  ),
                  _buildFeatureButton(
                    'Send Logs',
                    'Send trace, debug, info, warn, error, fatal logs',
                    Icons.list_alt,
                    Colors.green,
                    _testLogging,
                  ),
                  _buildFeatureButton(
                    'Track Performance',
                    'Send HTTP and DB performance spans (manual)',
                    Icons.speed,
                    Colors.orange,
                    _testPerformance,
                  ),
                  _buildFeatureButton(
                    'Add Breadcrumbs',
                    'Add system, user, HTTP, and state breadcrumbs',
                    Icons.timeline,
                    Colors.purple,
                    _testBreadcrumbs,
                  ),
                  _buildFeatureButton(
                    'Navigate',
                    'Go to details page (sends navigation breadcrumb)',
                    Icons.navigate_next,
                    Colors.teal,
                    () async {
                      await RiviumTrace.captureMessage(
                        'User navigating to details page',
                        level: MessageLevel.info,
                        extra: {'from': 'HomePage', 'to': 'DetailsPage'},
                      );
                      if (context.mounted) {
                        Navigator.pushNamed(context, '/details');
                      }
                    },
                  ),

                  SizedBox(height: 12),
                  _buildSectionHeader('Advanced Features'),
                  _buildFeatureButton(
                    'Performance HTTP Client',
                    'Auto-track HTTP requests as performance spans',
                    Icons.http,
                    Colors.deepOrange,
                    _testPerformanceHttpClient,
                  ),
                  _buildFeatureButton(
                    'Crash Detection',
                    'Check if previous session crashed (mobile only)',
                    Icons.warning_amber,
                    Colors.amber[800]!,
                    _testCrashDetection,
                  ),
                  _buildFeatureButton(
                    'Offline Storage',
                    'View locally stored errors for offline retry (mobile only)',
                    Icons.cloud_off,
                    Colors.grey[700]!,
                    _testOfflineStorage,
                  ),
                  _buildFeatureButton(
                    'Sample Rate',
                    'Send 10 errors to demonstrate sample rate filtering',
                    Icons.tune,
                    Colors.indigo,
                    _testSampleRate,
                  ),
                ],
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildSectionHeader(String title) {
    return Padding(
      padding: EdgeInsets.only(bottom: 8, top: 4),
      child: Text(
        title,
        style: TextStyle(
          fontSize: 14,
          fontWeight: FontWeight.bold,
          color: Colors.grey[600],
          letterSpacing: 0.5,
        ),
      ),
    );
  }

  Widget _buildFeatureButton(
    String title,
    String subtitle,
    IconData icon,
    Color color,
    VoidCallback onPressed,
  ) {
    return Card(
      margin: EdgeInsets.only(bottom: 8),
      child: ListTile(
        leading: Icon(icon, color: color),
        title: Text(title, style: TextStyle(fontWeight: FontWeight.bold)),
        subtitle: Text(subtitle),
        trailing: Icon(Icons.chevron_right),
        onTap: onPressed,
      ),
    );
  }
}

class DetailsPage extends StatefulWidget {
  const DetailsPage({super.key});

  @override
  State<DetailsPage> createState() => _DetailsPageState();
}

class _DetailsPageState extends State<DetailsPage> {
  @override
  void initState() {
    super.initState();

    // Log and send page view
    RiviumTrace.info('Details page viewed');
    RiviumTrace.captureMessage(
      'Details page viewed',
      level: MessageLevel.info,
      extra: {
        'current_route': RiviumTraceNavigatorObserver.currentRoute ?? 'unknown',
        'previous_route': RiviumTraceNavigatorObserver.previousRoute ?? 'none',
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('Details Page')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('Navigation breadcrumb was automatically recorded!'),
            SizedBox(height: 16),
            Text(
              'Current route: ${RiviumTraceNavigatorObserver.currentRoute ?? "unknown"}',
            ),
            Text(
              'Previous route: ${RiviumTraceNavigatorObserver.previousRoute ?? "none"}',
            ),
            SizedBox(height: 24),
            ElevatedButton(
              onPressed: () async {
                await RiviumTrace.captureMessage(
                  'User navigating back from details page',
                  level: MessageLevel.info,
                );
                if (context.mounted) {
                  Navigator.pop(context);
                }
              },
              child: Text('Go Back'),
            ),
          ],
        ),
      ),
    );
  }
}
0
likes
160
points
126
downloads

Documentation

API reference

Publisher

verified publisherrivium.co

Weekly Downloads

Flutter SDK for RiviumTrace error tracking platform. Supports web, mobile, and Chrome extensions.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

crypto, flutter, http, path_provider, universal_platform, web

More

Packages that depend on rivium_trace_flutter_sdk