rivium_trace_flutter_sdk 0.1.1 copy "rivium_trace_flutter_sdk: ^0.1.1" to clipboard
rivium_trace_flutter_sdk: ^0.1.1 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

Documentation
API reference

Publisher

verified publisherrivium.co

Weekly Downloads

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

Homepage
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