add sentry
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -447,6 +447,7 @@ Future<CacheOptions> cacheOptions(Ref ref) async {
|
||||
Future<Dio> dio(Ref ref) async {
|
||||
final dio = Dio();
|
||||
|
||||
|
||||
// Base configuration
|
||||
dio
|
||||
..options = BaseOptions(
|
||||
@@ -466,7 +467,7 @@ Future<Dio> 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
|
||||
|
||||
@@ -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<Map<String, dynamic>>(
|
||||
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<Map<String, String>> 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',
|
||||
};
|
||||
|
||||
@@ -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<void> 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<void>.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,
|
||||
|
||||
250
lib/core/services/sentry_service.dart
Normal file
250
lib/core/services/sentry_service.dart
Normal file
@@ -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<void> init({
|
||||
String? dsn,
|
||||
required Future<void> 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<void> 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<void> captureMessage(
|
||||
String message, {
|
||||
SentryLevel level = SentryLevel.info,
|
||||
Map<String, dynamic>? 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<void> setUser({
|
||||
required String userId,
|
||||
String? email,
|
||||
String? username,
|
||||
Map<String, dynamic>? 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<void> 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<void> addBreadcrumb({
|
||||
required String message,
|
||||
String? category,
|
||||
Map<String, dynamic>? 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<void> 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<void> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
121
lib/main.dart
121
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<void> _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<void> _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
|
||||
|
||||
48
pubspec.lock
48
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:
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user