crash_manager 1.0.0 copy "crash_manager: ^1.0.0" to clipboard
crash_manager: ^1.0.0 copied to clipboard

A comprehensive Flutter crash manager package with Firebase Crashlytics and Sentry support.

Flutter Crash Manager #

A comprehensive Flutter crash reporting package that provides a unified interface for multiple crash reporting services including Firebase Crashlytics and Sentry.

Features #

  • Unified Interface: Single API for multiple crash reporting services
  • Firebase Crashlytics Support: Complete integration with Firebase Crashlytics
  • Sentry Support: Full Sentry integration for cross-platform error tracking
  • Multi-Service Support: Use multiple crash reporting services simultaneously
  • Rich Context: User context, device information, and custom attributes
  • Breadcrumb Logging: Detailed debugging trails
  • Crash Filtering: Custom filters to control which crashes are reported
  • Privacy Controls: Built-in sensitive data filtering
  • Type Safety: Comprehensive type safety with proper null handling
  • Production Ready: Built for high-performance production applications

Installation #

Add this package to your pubspec.yaml:

dependencies:
  crash_manager:
    git:
      url: https://github.com/bahricanyesil/flutter-crash-manager.git

Quick Start #

1. Basic Setup #

import 'package:crash_manager/crash_manager.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // Create and initialize your preferred crash manager (already initialized!)
  final crashManager = await FirebaseCrashManager.create();

  // Set user context
  await crashManager.setUserContext(UserContext(
    userId: 'user123',
    email: 'user@example.com',
    name: 'John Doe',
  ));

  runApp(MyApp());
}

2. Recording Crashes #

try {
  // Your code that might throw
  riskyOperation();
} catch (error, stackTrace) {
  // Record fatal crash
  await crashManager.recordFatalCrash(error, stackTrace);

  // Or record non-fatal error
  await crashManager.recordNonFatalError(error, stackTrace);
}

3. Adding Context and Breadcrumbs #

// Add custom attributes
await crashManager.setCustomAttribute('feature', 'user_profile');
await crashManager.setCustomAttribute('experiment_id', 'exp_123');

// Add breadcrumbs for debugging
await crashManager.addBreadcrumb('User clicked login button');
await crashManager.addBreadcrumb('API call started', data: {'endpoint': '/login'});

Firebase Crashlytics Setup #

Android Setup #

  1. Add Firebase to your Android app:

    • Go to Firebase Console
    • Create a new project or use existing one
    • Add your Android app to the project
    • Download google-services.json and place it in android/app/
  2. Configure build.gradle files:

    Project-level android/build.gradle:

    buildscript {
      dependencies {
        classpath 'com.google.gms:google-services:4.4.0'
        classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.9'
      }
    }
    

    App-level android/app/build.gradle:

    apply plugin: 'com.google.gms.google-services'
    apply plugin: 'com.google.firebase.crashlytics'
    
    android {
      compileSdkVersion 34
    
      defaultConfig {
        multiDexEnabled true
      }
    }
    
    dependencies {
      implementation 'com.google.firebase:firebase-crashlytics'
      implementation 'com.google.firebase:firebase-analytics'
    }
    

iOS Setup #

  1. Add Firebase to your iOS app:

    • In Firebase Console, add your iOS app
    • Download GoogleService-Info.plist
    • Add the file to your Xcode project in ios/Runner/
  2. Configure Xcode:

    • Open ios/Runner.xcworkspace
    • Ensure GoogleService-Info.plist is added to the Runner target
    • No additional configuration needed for Crashlytics

Firebase Initialization Scenarios #

The Firebase crash manager handles multiple initialization scenarios automatically:

Scenario 1: Auto-Initialize Firebase (Simplest)

import 'package:crash_manager/crash_manager.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // Auto-initializes Firebase if not already done
  final crashManager = await FirebaseCrashManager.create();

  runApp(MyApp());
}

Scenario 2: User Already Initialized Firebase (Most Common)

import 'package:firebase_core/firebase_core.dart';
import 'package:crash_manager/crash_manager.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // User initializes Firebase for other services (Auth, Firestore, etc.)
  await Firebase.initializeApp();

  // Crash manager detects existing Firebase and uses it
  final crashManager = await FirebaseCrashManager.create();

  runApp(MyApp());
}

Scenario 3: Custom Firebase Options

import 'package:firebase_core/firebase_core.dart';
import 'package:crash_manager/crash_manager.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // Initialize with custom Firebase options
  final crashManager = await FirebaseCrashManager.createWithOptions(
    firebaseOptions: FirebaseOptions(
      apiKey: 'your-api-key',
      appId: 'your-app-id',
      messagingSenderId: 'your-sender-id',
      projectId: 'your-project-id',
      // ... other options
    ),
  );

  runApp(MyApp());
}

Scenario 4: Multiple Firebase Apps

import 'package:firebase_core/firebase_core.dart';
import 'package:crash_manager/crash_manager.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // Initialize multiple Firebase apps
  await Firebase.initializeApp(); // Default app
  await Firebase.initializeApp(
    name: 'secondary',
    options: secondaryOptions,
  );

  // Use specific Firebase app for crash reporting
  final crashManager = await FirebaseCrashManager.create(
    firebaseAppName: 'secondary',
  );

  runApp(MyApp());
}

Scenario 5: Explicit Control (No Auto-Initialize)

import 'package:firebase_core/firebase_core.dart';
import 'package:crash_manager/crash_manager.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // You control Firebase initialization
  await Firebase.initializeApp();

  // Crash manager assumes Firebase is already initialized
  final crashManager = await FirebaseCrashManager.createWithExistingFirebase();

  runApp(MyApp());
}

Error Handling for Missing Configuration

If Firebase configuration files are missing, you'll get a helpful error:

Firebase configuration files not found. Please ensure you have:
• Android: google-services.json in android/app/
• iOS: GoogleService-Info.plist in ios/Runner/
Or provide FirebaseOptions manually.
See: https://firebase.flutter.dev/docs/overview#installation

Sentry Setup #

1. Create Sentry Account and Project #

  1. Go to Sentry.io and create an account
  2. Create a new Flutter project
  3. Copy your DSN from the project settings

2. Initialize Sentry with Configuration #

Unlike Firebase, Sentry requires configuration to be passed directly to the crash manager:

import 'package:crash_manager/crash_manager.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // Create and initialize Sentry crash manager with configuration (already initialized!)
  final crashManager = await SentryCrashManager.create(
    config: SentryConfig(
      dsn: 'YOUR_SENTRY_DSN_HERE', // Required - get from Sentry dashboard
      environment: 'production', // or 'development', 'staging'
      release: '1.0.0', // Your app version
      debug: kDebugMode, // Enable debug logs in debug mode
      tracesSampleRate: 1.0, // Performance monitoring sample rate
      maxBreadcrumbs: 100, // Maximum breadcrumbs to keep
      enableAutoSessionTracking: true, // Track user sessions
      attachScreenshot: true, // Attach screenshots on crashes (iOS/Android)
      attachViewHierarchy: true, // Attach view hierarchy (iOS/Android)
      tags: {
        'team': 'mobile',
        'platform': 'flutter',
      },
      inAppIncludes: ['com.yourcompany.yourapp'], // Filter stack traces
    ),
  );

  runApp(MyApp());
}

3. Advanced Configuration Options #

The SentryConfig class provides comprehensive configuration options:

const SentryConfig(
  dsn: 'YOUR_SENTRY_DSN_HERE', // Required

  // Environment settings
  environment: 'production', // deployment environment
  release: '1.0.0+1', // app version/build

  // Performance monitoring
  tracesSampleRate: 1.0, // 0.0 to 1.0 (0% to 100%)

  // Debug settings
  debug: false, // enable debug logging

  // Breadcrumbs
  maxBreadcrumbs: 100, // max breadcrumbs to keep in memory

  // Session tracking
  enableAutoSessionTracking: true, // automatically track sessions

  // Screenshots and view hierarchy (mobile only)
  attachScreenshot: false, // attach screenshots on crashes
  attachViewHierarchy: false, // attach view hierarchy on crashes

  // Stack trace filtering
  inAppIncludes: ['com.yourcompany.yourapp'], // include patterns
  inAppExcludes: ['flutter', 'dart'], // exclude patterns

  // Global tags
  tags: {
    'team': 'mobile',
    'platform': 'flutter',
    'version': '1.0.0',
  },
);

Multi-Service Setup #

Use both Firebase Crashlytics and Sentry simultaneously:

void main() async {
  WidgetsFlutterBinding.ensureInitialized();

  // Create multi crash manager with both services (already initialized!)
  final crashManager = await MultiCrashManager.create();

  // Create and add Firebase Crashlytics
  final firebaseManager = await FirebaseCrashManager.create();
  crashManager.addManager(firebaseManager);

  // Create and add Sentry with configuration
  final sentryManager = await SentryCrashManager.create(
    config: SentryConfig(
      dsn: 'YOUR_SENTRY_DSN_HERE',
      environment: 'production',
      debug: kDebugMode,
      tracesSampleRate: 0.1, // Lower sample rate for production
      enableAutoSessionTracking: true,
      tags: {
        'service': 'multi_crash_manager',
        'platform': 'flutter',
      },
    ),
  );
  crashManager.addManager(sentryManager);

  runApp(MyApp());
}

Advanced Usage #

Custom Crash Filtering #

// Set up custom crash filter
crashManager.setCrashFilter((crashReport) {
  // Don't report network errors in debug mode
  if (kDebugMode && crashReport.message.contains('SocketException')) {
    return false;
  }

  // Don't report crashes from specific users
  if (crashReport.userId == 'test_user') {
    return false;
  }

  return true;
});

Comprehensive Context Setup #

// Set device information
final deviceInfo = await DeviceInfoCollector.collect();
await crashManager.setDeviceInfo(deviceInfo);

// Set user context with custom attributes
await crashManager.setUserContext(UserContext(
  userId: 'user123',
  email: 'user@example.com',
  name: 'John Doe',
  role: 'premium',
  customAttributes: {
    'subscription_tier': 'premium',
    'feature_flags': ['new_ui', 'beta_features'],
    'last_login': DateTime.now().toIso8601String(),
  },
));

// Set session ID
await crashManager.setSessionId('session_abc123');

Manual Crash Reporting #

// Create custom crash report
final crashReport = CrashUtils.createCrashReport(
  error: Exception('Custom error occurred'),
  stackTrace: StackTrace.current,
  isFatal: false,
  customAttributes: {
    'user_action': 'button_click',
    'screen': 'profile_page',
  },
  userId: 'user123',
  sessionId: 'session_abc123',
);

await crashManager.recordCrashReport(crashReport);

Performance Monitoring #

// Add performance breadcrumbs
await crashManager.addBreadcrumb(
  'API call started',
  data: {
    'endpoint': '/api/users',
    'method': 'GET',
    'timestamp': DateTime.now().toIso8601String(),
  },
  level: CrashLogLevel.info,
);

// Measure operation performance
final stopwatch = Stopwatch()..start();
try {
  await performOperation();
  await crashManager.addBreadcrumb(
    'Operation completed successfully',
    data: {'duration_ms': stopwatch.elapsedMilliseconds},
  );
} catch (error, stackTrace) {
  await crashManager.recordNonFatalError(
    error,
    stackTrace,
    customAttributes: {
      'operation_duration_ms': stopwatch.elapsedMilliseconds,
      'operation_type': 'api_call',
    },
  );
}

API Reference #

CrashManager Interface #

abstract class CrashManager {
  Future<void> initialize();
  Future<void> setUserContext(UserContext userContext);
  Future<void> recordFatalCrash(Object error, StackTrace stackTrace);
  Future<void> recordNonFatalError(Object error, StackTrace? stackTrace);
  Future<void> addBreadcrumb(String message, {Map<String, Object?>? data});
  Future<void> setCustomAttribute(String key, Object? value);
  void setCrashFilter(bool Function(CrashReport report) filter);
  // ... and more
}

Data Models #

CrashReport

class CrashReport extends BaseDataModel<CrashReport> {
  final String id;
  final String message;
  final String stackTrace;
  final String errorType;
  final DateTime timestamp;
  final bool isFatal;
  final Map<String, Object?> userContext;
  final Map<String, Object?> customAttributes;
  // ... and more
}

UserContext

class UserContext extends BaseDataModel<UserContext> {
  final String? userId;
  final String? email;
  final String? name;
  final String? role;
  final Map<String, Object?> customAttributes;
  final bool isAuthenticated;
  // ... and more
}

DeviceInfo

class DeviceInfo extends BaseDataModel<DeviceInfo> {
  final String platform;
  final String model;
  final String manufacturer;
  final String osVersion;
  final String appVersion;
  final String buildNumber;
  // ... and more
}

Best Practices #

1. Privacy and Security #

  • Always sanitize sensitive data before reporting
  • Use the built-in data sanitization features
  • Implement custom filters for sensitive operations

2. Performance #

  • Use non-fatal error reporting for non-critical issues
  • Limit breadcrumb frequency for high-traffic operations
  • Implement rate limiting for error reporting

3. Testing #

  • Use test crashes sparingly and only in development
  • Implement proper error boundaries in your app
  • Test crash reporting in staging environments

4. Monitoring #

  • Regularly review crash reports and patterns
  • Set up alerts for critical crash thresholds
  • Monitor crash-free user percentages

Troubleshooting #

Firebase Crashlytics Issues #

Problem: Crashes not appearing in Firebase Console

  • Solution: Ensure debug builds are configured to send crashes
  • Check: Verify google-services.json and GoogleService-Info.plist are correctly placed
  • Wait: It can take up to 15 minutes for crashes to appear

Problem: Build errors with Crashlytics

  • Solution: Ensure all gradle plugins are up to date
  • Check: Verify minimum SDK versions are met

Sentry Issues #

Problem: Events not appearing in Sentry

  • Solution: Verify DSN is correct and project is active
  • Check: Ensure network connectivity and no firewall blocking

Problem: High event volume

  • Solution: Implement proper sampling rates and filters
  • Configure: Set appropriate tracesSampleRate values

Contributing #

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Add tests for new functionality
  5. Submit a pull request

License #

This project is licensed under the MIT License - see the LICENSE file for details.

Support #

For support and questions:

Changelog #

See CHANGELOG.md for a detailed list of changes and updates.