From e3632d44452afa07a9ff7e448a1f4d5445132f46 Mon Sep 17 00:00:00 2001 From: Phuoc Nguyen Date: Thu, 11 Dec 2025 13:44:26 +0700 Subject: [PATCH] add sentry --- ios/Podfile.lock | 23 ++ lib/core/network/api_interceptor.dart | 15 +- lib/core/network/dio_client.dart | 3 +- lib/core/services/frappe_auth_service.dart | 31 +-- lib/core/services/onesignal_service.dart | 76 +++++++ lib/core/services/sentry_service.dart | 250 +++++++++++++++++++++ lib/main.dart | 121 +++------- pubspec.lock | 48 ++++ pubspec.yaml | 4 + 9 files changed, 445 insertions(+), 126 deletions(-) create mode 100644 lib/core/services/sentry_service.dart diff --git a/ios/Podfile.lock b/ios/Podfile.lock index a8e1729..2b8dcc9 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -161,6 +161,8 @@ PODS: - nanopb/encode (= 3.30910.0) - nanopb/decode (3.30910.0) - nanopb/encode (3.30910.0) + - objective_c (0.0.1): + - Flutter - onesignal_flutter (5.3.4): - Flutter - OneSignalXCFramework (= 5.2.14) @@ -212,6 +214,8 @@ PODS: - OneSignalXCFramework/OneSignalOutcomes - open_file_ios (0.0.1): - Flutter + - package_info_plus (0.4.5): + - Flutter - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS @@ -219,6 +223,11 @@ PODS: - SDWebImage (5.21.4): - SDWebImage/Core (= 5.21.4) - SDWebImage/Core (5.21.4) + - Sentry/HybridSDK (8.56.2) + - sentry_flutter (9.8.0): + - Flutter + - FlutterMacOS + - Sentry/HybridSDK (= 8.56.2) - share_plus (0.0.1): - Flutter - shared_preferences_foundation (0.0.1): @@ -242,10 +251,13 @@ DEPENDENCIES: - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) - integration_test (from `.symlinks/plugins/integration_test/ios`) - mobile_scanner (from `.symlinks/plugins/mobile_scanner/darwin`) + - objective_c (from `.symlinks/plugins/objective_c/ios`) - onesignal_flutter (from `.symlinks/plugins/onesignal_flutter/ios`) - OneSignalXCFramework (= 5.2.14) - open_file_ios (from `.symlinks/plugins/open_file_ios/ios`) + - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) + - sentry_flutter (from `.symlinks/plugins/sentry_flutter/ios`) - share_plus (from `.symlinks/plugins/share_plus/ios`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`) @@ -269,6 +281,7 @@ SPEC REPOS: - OneSignalXCFramework - PromisesObjC - SDWebImage + - Sentry - SwiftyGif EXTERNAL SOURCES: @@ -292,12 +305,18 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/integration_test/ios" mobile_scanner: :path: ".symlinks/plugins/mobile_scanner/darwin" + objective_c: + :path: ".symlinks/plugins/objective_c/ios" onesignal_flutter: :path: ".symlinks/plugins/onesignal_flutter/ios" open_file_ios: :path: ".symlinks/plugins/open_file_ios/ios" + package_info_plus: + :path: ".symlinks/plugins/package_info_plus/ios" path_provider_foundation: :path: ".symlinks/plugins/path_provider_foundation/darwin" + sentry_flutter: + :path: ".symlinks/plugins/sentry_flutter/ios" share_plus: :path: ".symlinks/plugins/share_plus/ios" shared_preferences_foundation: @@ -331,12 +350,16 @@ SPEC CHECKSUMS: integration_test: 252f60fa39af5e17c3aa9899d35d908a0721b573 mobile_scanner: 77265f3dc8d580810e91849d4a0811a90467ed5e nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 + objective_c: 77e887b5ba1827970907e10e832eec1683f3431d onesignal_flutter: f69ff09eeaf41cea4b7a841de5a61e79e7fd9a5a OneSignalXCFramework: 7112f3e89563e41ebc23fe807788f11985ac541c open_file_ios: 461db5853723763573e140de3193656f91990d9e + package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 path_provider_foundation: 0b743cbb62d8e47eab856f09262bb8c1ddcfe6ba PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 SDWebImage: d0184764be51240d49c761c37f53dd017e1ccaaf + Sentry: b53951377b78e21a734f5dc8318e333dbfc682d7 + sentry_flutter: f074f75557daea0e1dd9607381a05cc0e3e456fe share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f shared_preferences_foundation: 5086985c1d43c5ba4d5e69a4e8083a389e2909e6 sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d diff --git a/lib/core/network/api_interceptor.dart b/lib/core/network/api_interceptor.dart index acc8b4c..83ef579 100644 --- a/lib/core/network/api_interceptor.dart +++ b/lib/core/network/api_interceptor.dart @@ -56,21 +56,10 @@ class AuthInterceptor extends Interceptor { // Get session data from secure storage final sid = await _secureStorage.read(key: 'frappe_sid'); final csrfToken = await _secureStorage.read(key: 'frappe_csrf_token'); - final fullName = await _secureStorage.read(key: 'frappe_full_name'); - final userId = await _secureStorage.read(key: 'frappe_user_id'); if (sid != null && csrfToken != null) { - // Build cookie header with all required fields - final cookieHeader = [ - 'sid=$sid', - 'full_name=${fullName ?? "User"}', - 'system_user=no', - 'user_id=${userId != null ? Uri.encodeComponent(userId) : ApiConstants.frappePublicUserId}', - 'user_image=', - ].join('; '); - - // Add Frappe session headers - options.headers['Cookie'] = cookieHeader; + // Only send sid in Cookie header - other fields are not needed + options.headers['Cookie'] = 'sid=$sid'; options.headers['X-Frappe-CSRF-Token'] = csrfToken; } diff --git a/lib/core/network/dio_client.dart b/lib/core/network/dio_client.dart index fd50c2f..7e960a4 100644 --- a/lib/core/network/dio_client.dart +++ b/lib/core/network/dio_client.dart @@ -447,6 +447,7 @@ Future cacheOptions(Ref ref) async { Future dio(Ref ref) async { final dio = Dio(); + // Base configuration dio ..options = BaseOptions( @@ -466,7 +467,7 @@ Future dio(Ref ref) async { }, ) // Add interceptors in order - // 1. Custom Curl interceptor (first to log cURL commands) + // 1. Custom Curl interceptor (logs cURL commands) // Uses debugPrint and developer.log for better visibility ..interceptors.add(CustomCurlLoggerInterceptor()) // 2. Logging interceptor diff --git a/lib/core/services/frappe_auth_service.dart b/lib/core/services/frappe_auth_service.dart index 246e9e1..d79848b 100644 --- a/lib/core/services/frappe_auth_service.dart +++ b/lib/core/services/frappe_auth_service.dart @@ -89,13 +89,8 @@ class FrappeAuthService { const url = '${ApiConstants.baseUrl}${ApiConstants.frappeApiMethod}${ApiConstants.frappeLogin}'; - // Build cookie header + // Get stored session - only need sid and csrf_token final storedSession = await getStoredSession(); - final cookieHeader = _buildCookieHeader( - sid: storedSession!['sid']!, - fullName: storedSession['fullName']!, - userId: storedSession['userId']!, - ); final response = await _dio.post>( url, @@ -109,7 +104,7 @@ class FrappeAuthService { }, options: Options( headers: { - 'Cookie': cookieHeader, + 'Cookie': 'sid=${storedSession!['sid']!}', 'X-Frappe-Csrf-Token': storedSession['csrfToken']!, 'Content-Type': 'application/json', }, @@ -203,31 +198,13 @@ class FrappeAuthService { return sid != null && csrfToken != null; } - /// Build cookie header string - String _buildCookieHeader({ - required String sid, - required String fullName, - required String userId, - }) { - return [ - 'sid=$sid', - 'full_name=$fullName', - 'system_user=no', - 'user_id=${Uri.encodeComponent(userId)}', - 'user_image=', - ].join('; '); - } - /// Get headers for Frappe API requests + /// Only sends sid in Cookie - other fields are not needed Future> getHeaders() async { final session = await ensureSession(); return { - 'Cookie': _buildCookieHeader( - sid: session['sid']!, - fullName: session['fullName']!, - userId: session['userId']!, - ), + 'Cookie': 'sid=${session['sid']!}', 'X-Frappe-Csrf-Token': session['csrfToken']!, 'Content-Type': 'application/json', }; diff --git a/lib/core/services/onesignal_service.dart b/lib/core/services/onesignal_service.dart index 8016dfe..aba5714 100644 --- a/lib/core/services/onesignal_service.dart +++ b/lib/core/services/onesignal_service.dart @@ -4,12 +4,16 @@ import 'package:onesignal_flutter/onesignal_flutter.dart'; /// OneSignal service for managing push notifications and external user ID. /// /// This service handles: +/// - Initializing OneSignal SDK /// - Setting external user ID after login (using phone number) /// - Restoring external user ID on app startup /// - Clearing external user ID on logout /// /// Usage: /// ```dart +/// // Initialize in main.dart +/// await OneSignalService.init(appId: 'your-app-id'); +/// /// // After successful login /// await OneSignalService.login(phoneNumber); /// @@ -19,6 +23,78 @@ import 'package:onesignal_flutter/onesignal_flutter.dart'; class OneSignalService { OneSignalService._(); + /// OneSignal App ID - Replace with your actual App ID from OneSignal dashboard + static const String _defaultAppId = '778ca22d-c719-4ec8-86cb-a6b911166066'; + + /// Initialize OneSignal SDK + /// + /// Must be called before using any other OneSignal methods. + /// Sets up push subscription observers and requests notification permission. + /// + /// [appId] - Optional App ID override (uses default if not provided) + /// [requestPermission] - Whether to request notification permission (default: true) + static Future init({ + String? appId, + bool requestPermission = true, + }) async { + try { + // Set debug log level (verbose in debug, none in release) + OneSignal.Debug.setLogLevel(kDebugMode ? OSLogLevel.verbose : OSLogLevel.none); + + // Initialize with App ID + OneSignal.initialize(appId ?? _defaultAppId); + debugPrint('🔔 OneSignal initialized'); + + // Add push subscription observer to track subscription state changes + OneSignal.User.pushSubscription.addObserver((state) { + debugPrint('🔔 Push subscription state changed:'); + debugPrint(' Previous - optedIn: ${state.previous.optedIn}, id: ${state.previous.id}'); + debugPrint(' Current - optedIn: ${state.current.optedIn}, id: ${state.current.id}'); + debugPrint(' Subscription ID: ${state.current.id}'); + debugPrint(' Push Token: ${state.current.token}'); + + if (state.current.id != null) { + debugPrint('🔔 ✅ Device successfully subscribed!'); + } + }); + + // Add notification permission observer + OneSignal.Notifications.addPermissionObserver((isGranted) { + debugPrint('🔔 Notification permission changed: $isGranted'); + }); + + // Request permission if enabled + if (requestPermission) { + final accepted = await OneSignal.Notifications.requestPermission(true); + debugPrint('🔔 Permission request result: $accepted'); + } + + // Give OneSignal SDK time to complete initialization and server registration + await Future.delayed(const Duration(seconds: 2)); + + // Log current subscription status + _logSubscriptionStatus(); + } catch (e) { + debugPrint('🔔 OneSignal error: Failed to initialize - $e'); + } + } + + /// Log current subscription status for debugging + static void _logSubscriptionStatus() { + final optedIn = OneSignal.User.pushSubscription.optedIn; + final id = OneSignal.User.pushSubscription.id; + final token = OneSignal.User.pushSubscription.token; + + debugPrint('🔔 Current subscription status:'); + debugPrint(' Opted In: $optedIn'); + debugPrint(' Subscription ID: $id'); + debugPrint(' Push Token: $token'); + + if (id == null) { + debugPrint('🔔 ⚠️ Subscription ID is null - check device connectivity and OneSignal app ID'); + } + } + /// Login user to OneSignal by setting external user ID. /// /// This associates the device with the user's phone number, diff --git a/lib/core/services/sentry_service.dart b/lib/core/services/sentry_service.dart new file mode 100644 index 0000000..04e62e1 --- /dev/null +++ b/lib/core/services/sentry_service.dart @@ -0,0 +1,250 @@ +import 'package:flutter/foundation.dart'; +import 'package:package_info_plus/package_info_plus.dart'; +import 'package:sentry_flutter/sentry_flutter.dart'; + +/// Sentry service for error tracking and performance monitoring. +/// +/// This service handles: +/// - Initializing Sentry SDK +/// - Capturing exceptions and errors +/// - Capturing custom messages +/// - Setting user context after login +/// - Performance monitoring +/// +/// Usage: +/// ```dart +/// // Initialize in main.dart +/// await SentryService.init( +/// dsn: 'your-sentry-dsn', +/// appRunner: () => runApp(MyApp()), +/// ); +/// +/// // Capture exception +/// SentryService.captureException(error, stackTrace: stackTrace); +/// +/// // Capture message +/// SentryService.captureMessage('User performed action X'); +/// +/// // Set user context after login +/// SentryService.setUser(userId: '123', email: 'user@example.com'); +/// ``` +class SentryService { + SentryService._(); + + /// Sentry DSN - Replace with your actual DSN from Sentry dashboard + static const String _dsn = 'https://2c5893508a29e5ea750b64d5ee31aeef@o4509632266436608.ingest.us.sentry.io/4510464530972672'; + + /// Initialize Sentry SDK + /// + /// Must be called before runApp() in main.dart. + /// Wraps the app with Sentry error boundary. + /// + /// [dsn] - Optional DSN override (uses default if not provided) + /// [appRunner] - The function that runs the app (typically runApp(MyApp())) + /// [environment] - Environment name (e.g., 'development', 'production') + static Future init({ + String? dsn, + required Future Function() appRunner, + String? environment, + }) async { + // Get package info for release version + final packageInfo = await PackageInfo.fromPlatform(); + final release = 'partner@${packageInfo.version}+${packageInfo.buildNumber}'; + + await SentryFlutter.init( + (options) { + options + ..dsn = dsn ?? _dsn + + // Release version: worker@1.0.1+29 + ..release = release + + // Environment configuration + ..environment = environment ?? (kReleaseMode ? 'production' : 'development') + + // Performance monitoring + ..tracesSampleRate = kReleaseMode ? 0.2 : 1.0 // 20% in prod, 100% in dev + ..profilesSampleRate = kReleaseMode ? 0.2 : 1.0 + + // Enable automatic instrumentation + ..enableAutoPerformanceTracing = true + + // Capture failed requests + ..captureFailedRequests = true + + // Debug mode settings + ..debug = kDebugMode + + // Add app-specific tags + ..beforeSend = (event, hint) { + // Filter out certain errors if needed + // Return null to drop the event + return event; + }; + }, + appRunner: appRunner, + ); + + debugPrint('🔴 Sentry initialized (release: $release, enabled: ${!kDebugMode})'); + } + + /// Capture an exception with optional stack trace + /// + /// [exception] - The exception to capture + /// [stackTrace] - Optional stack trace + /// [hint] - Optional hint with additional context + static Future captureException( + dynamic exception, { + StackTrace? stackTrace, + Hint? hint, + }) async { + try { + await Sentry.captureException( + exception, + stackTrace: stackTrace, + hint: hint, + ); + debugPrint('🔴 Sentry: Exception captured - ${exception.runtimeType}'); + } catch (e) { + debugPrint('🔴 Sentry error: Failed to capture exception - $e'); + } + } + + /// Capture a custom message + /// + /// [message] - The message to capture + /// [level] - Severity level (default: info) + /// [params] - Optional parameters to include + static Future captureMessage( + String message, { + SentryLevel level = SentryLevel.info, + Map? params, + }) async { + try { + await Sentry.captureMessage( + message, + level: level, + withScope: params != null + ? (scope) { + params.forEach((key, value) { + scope.setExtra(key, value); + }); + } + : null, + ); + debugPrint('🔴 Sentry: Message captured - $message'); + } catch (e) { + debugPrint('🔴 Sentry error: Failed to capture message - $e'); + } + } + + /// Set user context for error tracking + /// + /// Call this after successful login to associate errors with users. + /// + /// [userId] - User's unique identifier + /// [email] - User's email (optional) + /// [username] - User's display name (optional) + /// [extras] - Additional user data (optional) + static Future setUser({ + required String userId, + String? email, + String? username, + Map? extras, + }) async { + try { + await Sentry.configureScope((scope) { + scope.setUser(SentryUser( + id: userId, + email: email, + username: username, + data: extras, + )); + }); + debugPrint('🔴 Sentry: User set - $userId'); + } catch (e) { + debugPrint('🔴 Sentry error: Failed to set user - $e'); + } + } + + /// Clear user context on logout + static Future clearUser() async { + try { + await Sentry.configureScope((scope) { + scope.setUser(null); + }); + debugPrint('🔴 Sentry: User cleared'); + } catch (e) { + debugPrint('🔴 Sentry error: Failed to clear user - $e'); + } + } + + /// Add a breadcrumb for tracking user actions + /// + /// Breadcrumbs are used to track the sequence of events leading to an error. + /// + /// [message] - Description of the action + /// [category] - Category of the breadcrumb (e.g., 'navigation', 'ui.click') + /// [data] - Additional data (optional) + static Future addBreadcrumb({ + required String message, + String? category, + Map? data, + SentryLevel level = SentryLevel.info, + }) async { + try { + await Sentry.addBreadcrumb(Breadcrumb( + message: message, + category: category, + data: data, + level: level, + timestamp: DateTime.now(), + )); + } catch (e) { + debugPrint('🔴 Sentry error: Failed to add breadcrumb - $e'); + } + } + + /// Set a tag for filtering in Sentry dashboard + /// + /// [key] - Tag name + /// [value] - Tag value + static Future setTag(String key, String value) async { + try { + await Sentry.configureScope((scope) { + scope.setTag(key, value); + }); + } catch (e) { + debugPrint('🔴 Sentry error: Failed to set tag - $e'); + } + } + + /// Set extra context data + /// + /// [key] - Context key + /// [value] - Context value (will be serialized) + static Future setExtra(String key, dynamic value) async { + try { + await Sentry.configureScope((scope) { + scope.setExtra(key, value); + }); + } catch (e) { + debugPrint('🔴 Sentry error: Failed to set extra - $e'); + } + } + + /// Start a performance transaction + /// + /// [name] - Transaction name + /// [operation] - Operation type (e.g., 'http.client', 'ui.load') + /// + /// Returns the transaction to be finished later. + static ISentrySpan? startTransaction(String name, String operation) { + try { + return Sentry.startTransaction(name, operation); + } catch (e) { + debugPrint('🔴 Sentry error: Failed to start transaction - $e'); + return null; + } + } +} diff --git a/lib/main.dart b/lib/main.dart index c201914..1ea14f1 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,17 +1,17 @@ import 'dart:async'; +import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:onesignal_flutter/onesignal_flutter.dart'; import 'package:shared_preferences/shared_preferences.dart'; - import 'package:worker/app.dart'; import 'package:worker/core/database/app_settings_box.dart'; import 'package:worker/core/database/hive_initializer.dart'; -import 'package:firebase_core/firebase_core.dart'; -import 'firebase_options.dart'; +import 'package:worker/core/services/onesignal_service.dart'; +import 'package:worker/core/services/sentry_service.dart'; +import 'package:worker/firebase_options.dart'; /// Main entry point of the Worker Mobile App /// @@ -45,72 +45,35 @@ void main() async { /// Initialize all app dependencies with comprehensive error handling Future _initializeApp() async { - // Set up error handlers before anything else - _setupErrorHandlers(); + // Initialize Sentry first to capture any initialization errors + // Sentry wraps the app runner to capture errors during startup + await SentryService.init( + appRunner: () async { + // Set up error handlers + _setupErrorHandlers(); - try { - // Initialize core dependencies in parallel for faster startup - await Future.wait([_initializeHive(), _initializeSharedPreferences()]); + try { + // Initialize core dependencies in parallel for faster startup + await Future.wait([_initializeHive(), _initializeSharedPreferences()]); - // Initialize OneSignal with verbose logging for debugging - OneSignal.Debug.setLogLevel(OSLogLevel.verbose); + // Initialize OneSignal push notifications + await OneSignalService.init(); - // Initialize with your OneSignal App ID - OneSignal.initialize("778ca22d-c719-4ec8-86cb-a6b911166066"); + // Run the app with Riverpod ProviderScope + runApp(const ProviderScope(child: WorkerApp())); + } catch (error, stackTrace) { + // Critical initialization error - capture and show error screen + debugPrint('Failed to initialize app: $error'); + debugPrint('StackTrace: $stackTrace'); - debugPrint('🔔 OneSignal initialized'); + // Report to Sentry + await SentryService.captureException(error, stackTrace: stackTrace); - // Add observer BEFORE requesting permission to catch the subscription event - OneSignal.User.pushSubscription.addObserver((state) { - debugPrint('🔔 Push subscription state changed:'); - debugPrint(' Previous - optedIn: ${state.previous.optedIn}, id: ${state.previous.id}'); - debugPrint(' Current - optedIn: ${state.current.optedIn}, id: ${state.current.id}'); - debugPrint(' Subscription ID: ${state.current.id}'); - debugPrint(' Push Token: ${state.current.token}'); - - // Save subscription info when available - if (state.current.id != null) { - debugPrint('🔔 ✅ Device successfully subscribed!'); + // Run minimal error app + runApp(_buildErrorApp(error, stackTrace)); } - }); - - // Add notification permission observer - OneSignal.Notifications.addPermissionObserver((isGranted) { - debugPrint('🔔 Notification permission changed: $isGranted'); - }); - - // Request permission - TRUE to show the native permission prompt - final accepted = await OneSignal.Notifications.requestPermission(true); - debugPrint('🔔 Permission request result: $accepted'); - - // Give OneSignal SDK time to complete initialization and server registration - // This is necessary because the subscription happens asynchronously - await Future.delayed(const Duration(seconds: 2)); - - // Check current subscription status after initialization completes - final optedIn = OneSignal.User.pushSubscription.optedIn; - final id = OneSignal.User.pushSubscription.id; - final token = OneSignal.User.pushSubscription.token; - - debugPrint('🔔 Current subscription status (after delay):'); - debugPrint(' Opted In: $optedIn'); - debugPrint(' Subscription ID: $id'); - debugPrint(' Push Token: $token'); - - if (id == null) { - debugPrint('🔔 ⚠️ Subscription ID is still null - check device connectivity and OneSignal app ID'); - } - - // Run the app with Riverpod ProviderScope - runApp(const ProviderScope(child: WorkerApp())); - } catch (error, stackTrace) { - // Critical initialization error - show error screen - debugPrint('Failed to initialize app: $error'); - debugPrint('StackTrace: $stackTrace'); - - // Run minimal error app - runApp(_buildErrorApp(error, stackTrace)); - } + }, + ); } /// Initialize Hive database @@ -164,7 +127,8 @@ Future _initializeSharedPreferences() async { /// Set up global error handlers /// -/// Captures and logs all Flutter framework errors and uncaught exceptions +/// Captures and logs all Flutter framework errors and uncaught exceptions. +/// Reports errors to Sentry for crash analytics. void _setupErrorHandlers() { // Handle Flutter framework errors FlutterError.onError = (FlutterErrorDetails details) { @@ -176,8 +140,11 @@ void _setupErrorHandlers() { debugPrint('StackTrace: ${details.stack}'); } - // In production, you would send to crash analytics service - // Example: FirebaseCrashlytics.instance.recordFlutterError(details); + // Report to Sentry + SentryService.captureException( + details.exception, + stackTrace: details.stack, + ); }; // Handle errors outside of Flutter framework @@ -187,27 +154,11 @@ void _setupErrorHandlers() { debugPrint('StackTrace: $stackTrace'); } - // In production, you would send to crash analytics service - // Example: FirebaseCrashlytics.instance.recordError(error, stackTrace); + // Report to Sentry + SentryService.captureException(error, stackTrace: stackTrace); return true; // Return true to indicate error was handled }; - - // Handle zone errors (async errors not caught by Flutter) - runZonedGuarded( - () { - // App will run in this zone - }, - (error, stackTrace) { - if (kDebugMode) { - debugPrint('Zone Error: $error'); - debugPrint('StackTrace: $stackTrace'); - } - - // In production, you would send to crash analytics service - // Example: FirebaseCrashlytics.instance.recordError(error, stackTrace); - }, - ); } /// Build minimal error app when initialization fails diff --git a/pubspec.lock b/pubspec.lock index 5cf1acc..cac045d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -892,6 +892,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.2.2+1" + jni: + dependency: transitive + description: + name: jni + sha256: d2c361082d554d4593c3012e26f6b188f902acd291330f13d6427641a92b3da1 + url: "https://pub.dev" + source: hosted + version: "0.14.2" js: dependency: transitive description: @@ -1044,6 +1052,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.2" + objective_c: + dependency: transitive + description: + name: objective_c + sha256: "64e35e1e2e79da4e83f2ace3bf4e5437cef523f46c7db2eba9a1419c49573790" + url: "https://pub.dev" + source: hosted + version: "8.0.0" octo_image: dependency: transitive description: @@ -1132,6 +1148,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.0" + package_info_plus: + dependency: "direct main" + description: + name: package_info_plus + sha256: "16eee997588c60225bda0488b6dcfac69280a6b7a3cf02c741895dd370a02968" + url: "https://pub.dev" + source: hosted + version: "8.3.1" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086" + url: "https://pub.dev" + source: hosted + version: "3.2.1" path: dependency: transitive description: @@ -1324,6 +1356,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.28.0" + sentry: + dependency: transitive + description: + name: sentry + sha256: "10a0bc25f5f21468e3beeae44e561825aaa02cdc6829438e73b9b64658ff88d9" + url: "https://pub.dev" + source: hosted + version: "9.8.0" + sentry_flutter: + dependency: "direct main" + description: + name: sentry_flutter + sha256: aafbf41c63c98a30b17bdbf3313424d5102db62b08735c44bff810f277e786a5 + url: "https://pub.dev" + source: hosted + version: "9.8.0" share_plus: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index c10a1f4..97298f0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -73,6 +73,7 @@ dependencies: # Utilities + package_info_plus: ^8.0.0 intl: ^0.20.0 share_plus: ^12.0.1 image_picker: ^1.1.2 @@ -86,6 +87,9 @@ dependencies: onesignal_flutter: ^5.3.4 + # Error Tracking + sentry_flutter: ^9.8.0 + # Navigation go_router: ^14.6.2