Crash and ANR Handling
Comprehensive error tracking and crash reporting for Flutter apps
Crash and ANR Handling
Enhance your Flutter app's reliability with comprehensive crash detection, ANR monitoring, and custom exception tracking integrated with UXCam session recordings.
Overview
UXCam's Flutter SDK provides advanced crash handling capabilities that capture crashes and ANRs (Application Not Responding) alongside session recordings, giving you complete context for debugging issues.
Key Features:
- Automatic crash detection and reporting
- ANR detection and tracking
- Custom exception reporting
- Integration with session replays
- Stack trace capture
- Device and environment context
Basic Crash Handling Setup
Enable Automatic Crash Handling
import 'package:flutter_uxcam/flutter_uxcam.dart';
FlutterUxConfig config = FlutterUxConfig(
userAppKey: "YOUR_APP_KEY",
enableCrashHandling: true, // Enable automatic crash detection
enableAutomaticScreenNameTagging: true,
);
await FlutterUxcam.startWithConfiguration(config);Flutter Error Handling Integration
void main() {
// Capture Flutter framework errors
FlutterError.onError = (FlutterErrorDetails details) {
// Report to UXCam
FlutterUxcam.recordException(
details.exception.toString(),
details.stack?.toString() ?? 'No stack trace available',
);
// Also print to console in debug mode
if (kDebugMode) {
FlutterError.presentError(details);
}
};
// Capture errors not caught by Flutter
PlatformDispatcher.instance.onError = (error, stack) {
FlutterUxcam.recordException(error.toString(), stack.toString());
return true;
};
runApp(MyApp());
}Advanced Crash Handling
Custom Exception Reporting
class CustomExceptionHandler {
static void reportException(
dynamic error,
StackTrace? stackTrace, {
String? context,
Map<String, dynamic>? additionalData,
}) {
// Create detailed error message
String errorMessage = error.toString();
if (context != null) {
errorMessage = '$context: $errorMessage';
}
// Add additional context
if (additionalData != null) {
errorMessage += '\nAdditional Data: ${additionalData.toString()}';
}
// Report to UXCam
FlutterUxcam.recordException(
errorMessage,
stackTrace?.toString() ?? 'No stack trace available',
);
// Log locally for debugging
debugPrint('Exception reported to UXCam: $errorMessage');
}
static Future<T> wrapWithExceptionHandling<T>(
Future<T> future, {
String? context,
Map<String, dynamic>? additionalData,
}) async {
try {
return await future;
} catch (error, stackTrace) {
reportException(
error,
stackTrace,
context: context,
additionalData: additionalData,
);
rethrow;
}
}
}
// Usage examples
class DataService {
static Future<List<User>> fetchUsers() async {
return CustomExceptionHandler.wrapWithExceptionHandling(
_performNetworkRequest(),
context: 'DataService.fetchUsers',
additionalData: {'endpoint': '/api/users'},
);
}
static Future<List<User>> _performNetworkRequest() async {
// Your network request implementation
throw Exception('Network error');
}
}Business Logic Error Tracking
class BusinessLogicErrorHandler {
static void reportBusinessError({
required String operation,
required String errorType,
required String errorMessage,
Map<String, dynamic>? context,
}) {
// Create structured error message
final errorData = {
'operation': operation,
'errorType': errorType,
'message': errorMessage,
'timestamp': DateTime.now().toIso8601String(),
'context': context ?? {},
};
// Report as custom exception
FlutterUxcam.recordException(
'Business Logic Error: $operation',
'Error Type: $errorType\n'
'Message: $errorMessage\n'
'Context: ${errorData.toString()}',
);
// Also log as custom event for analytics
FlutterUxcam.logEvent('business_error', errorData);
}
}
// Usage in business logic
class PaymentService {
static Future<PaymentResult> processPayment(PaymentRequest request) async {
try {
// Payment processing logic
if (request.amount <= 0) {
BusinessLogicErrorHandler.reportBusinessError(
operation: 'processPayment',
errorType: 'ValidationError',
errorMessage: 'Invalid payment amount',
context: {
'amount': request.amount,
'currency': request.currency,
'userId': request.userId,
},
);
throw PaymentException('Invalid payment amount');
}
// Continue with payment processing
return await _processPaymentInternal(request);
} catch (error, stackTrace) {
CustomExceptionHandler.reportException(
error,
stackTrace,
context: 'PaymentService.processPayment',
additionalData: {'requestId': request.id},
);
rethrow;
}
}
}ANR Detection
Custom ANR Monitoring
class ANRDetector {
static Timer? _watchdogTimer;
static DateTime _lastUpdate = DateTime.now();
static const Duration ANR_THRESHOLD = Duration(seconds: 5);
static void startMonitoring() {
_watchdogTimer = Timer.periodic(Duration(seconds: 1), (timer) {
final now = DateTime.now();
final timeSinceLastUpdate = now.difference(_lastUpdate);
if (timeSinceLastUpdate > ANR_THRESHOLD) {
_reportANR(timeSinceLastUpdate);
}
_lastUpdate = now;
});
}
static void _reportANR(Duration anrDuration) {
final anrMessage = 'ANR detected: UI thread blocked for ${anrDuration.inMilliseconds}ms';
FlutterUxcam.recordException(
anrMessage,
_getCurrentStackTrace(),
);
// Log as custom event for analytics
FlutterUxcam.logEvent('anr_detected', {
'duration_ms': anrDuration.inMilliseconds,
'screen': 'current_screen', // Add current screen context
'timestamp': DateTime.now().toIso8601String(),
});
}
static String _getCurrentStackTrace() {
return StackTrace.current.toString();
}
static void heartbeat() {
_lastUpdate = DateTime.now();
}
static void stopMonitoring() {
_watchdogTimer?.cancel();
_watchdogTimer = null;
}
}
// Usage in main app
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> with WidgetsBindingObserver {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
ANRDetector.startMonitoring();
}
@override
void dispose() {
ANRDetector.stopMonitoring();
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
navigatorObservers: [ANRNavigatorObserver()],
// Your app content
);
}
}
class ANRNavigatorObserver extends NavigatorObserver {
@override
void didPush(Route<dynamic> route, Route<dynamic>? previousRoute) {
ANRDetector.heartbeat();
super.didPush(route, previousRoute);
}
@override
void didPop(Route<dynamic> route, Route<dynamic>? previousRoute) {
ANRDetector.heartbeat();
super.didPop(route, previousRoute);
}
}Network Error Handling
HTTP Error Tracking
class NetworkErrorHandler {
static Future<T> handleNetworkRequest<T>(
Future<T> request, {
required String endpoint,
required String method,
Map<String, dynamic>? requestData,
}) async {
try {
return await request;
} on SocketException catch (error, stackTrace) {
_reportNetworkError(
'Network Connection Error',
endpoint,
method,
error.toString(),
stackTrace,
requestData,
);
rethrow;
} on TimeoutException catch (error, stackTrace) {
_reportNetworkError(
'Network Timeout Error',
endpoint,
method,
error.toString(),
stackTrace,
requestData,
);
rethrow;
} on HttpException catch (error, stackTrace) {
_reportNetworkError(
'HTTP Error',
endpoint,
method,
error.toString(),
stackTrace,
requestData,
);
rethrow;
} catch (error, stackTrace) {
_reportNetworkError(
'Unknown Network Error',
endpoint,
method,
error.toString(),
stackTrace,
requestData,
);
rethrow;
}
}
static void _reportNetworkError(
String errorType,
String endpoint,
String method,
String errorMessage,
StackTrace stackTrace,
Map<String, dynamic>? requestData,
) {
final contextMessage = '''
Network Error Details:
Type: $errorType
Endpoint: $endpoint
Method: $method
Error: $errorMessage
Request Data: ${requestData?.toString() ?? 'None'}
''';
FlutterUxcam.recordException(contextMessage, stackTrace.toString());
// Also track as event for analytics
FlutterUxcam.logEvent('network_error', {
'error_type': errorType,
'endpoint': endpoint,
'method': method,
'error_message': errorMessage,
});
}
}
// Usage with HTTP client
class ApiService {
static final HttpClient _httpClient = HttpClient();
static Future<Map<String, dynamic>> get(String endpoint) async {
return NetworkErrorHandler.handleNetworkRequest(
_performGetRequest(endpoint),
endpoint: endpoint,
method: 'GET',
);
}
static Future<Map<String, dynamic>> post(
String endpoint,
Map<String, dynamic> data,
) async {
return NetworkErrorHandler.handleNetworkRequest(
_performPostRequest(endpoint, data),
endpoint: endpoint,
method: 'POST',
requestData: data,
);
}
}State Management Error Integration
Bloc Error Handling
class UXCamBlocObserver extends BlocObserver {
@override
void onError(BlocBase bloc, Object error, StackTrace stackTrace) {
super.onError(bloc, error, stackTrace);
// Report bloc errors to UXCam
FlutterUxcam.recordException(
'Bloc Error in ${bloc.runtimeType}: ${error.toString()}',
stackTrace.toString(),
);
// Log as custom event
FlutterUxcam.logEvent('bloc_error', {
'bloc_type': bloc.runtimeType.toString(),
'error': error.toString(),
'timestamp': DateTime.now().toIso8601String(),
});
}
@override
void onTransition(BlocBase bloc, Transition transition) {
super.onTransition(bloc, transition);
// Track critical state transitions
if (_isCriticalTransition(transition)) {
FlutterUxcam.logEvent('critical_state_transition', {
'bloc_type': bloc.runtimeType.toString(),
'from_state': transition.currentState.runtimeType.toString(),
'to_state': transition.nextState.runtimeType.toString(),
'event': transition.event.runtimeType.toString(),
});
}
}
bool _isCriticalTransition(Transition transition) {
// Define your critical transitions
return transition.nextState.toString().contains('Error') ||
transition.nextState.toString().contains('Failed');
}
}
// Initialize in main
void main() {
Bloc.observer = UXCamBlocObserver();
runApp(MyApp());
}Provider Error Handling
class UXCamChangeNotifier extends ChangeNotifier {
void safeNotifyListeners() {
try {
notifyListeners();
} catch (error, stackTrace) {
FlutterUxcam.recordException(
'ChangeNotifier Error: ${error.toString()}',
stackTrace.toString(),
);
// Still notify listeners if possible
super.notifyListeners();
}
}
@override
void notifyListeners() {
safeNotifyListeners();
}
}Testing Crash Handling
Development Testing
class CrashTestWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Crash Testing')),
body: Column(
children: [
ElevatedButton(
onPressed: () {
// Test custom exception
CustomExceptionHandler.reportException(
Exception('Test exception'),
StackTrace.current,
context: 'Manual test',
);
},
child: Text('Test Custom Exception'),
),
ElevatedButton(
onPressed: () {
// Test network error
throw SocketException('Test network error');
},
child: Text('Test Network Error'),
),
ElevatedButton(
onPressed: () {
// Test business logic error
BusinessLogicErrorHandler.reportBusinessError(
operation: 'testOperation',
errorType: 'ValidationError',
errorMessage: 'Test validation failed',
context: {'testData': 'sample'},
);
},
child: Text('Test Business Error'),
),
],
),
);
}
}Production Considerations
Error Rate Monitoring
class ErrorRateMonitor {
static int _errorCount = 0;
static DateTime _windowStart = DateTime.now();
static const Duration MONITORING_WINDOW = Duration(minutes: 5);
static const int ERROR_THRESHOLD = 10;
static void reportError(dynamic error, StackTrace stackTrace) {
_errorCount++;
final now = DateTime.now();
if (now.difference(_windowStart) > MONITORING_WINDOW) {
_checkErrorRate();
_resetWindow();
}
FlutterUxcam.recordException(error.toString(), stackTrace.toString());
}
static void _checkErrorRate() {
if (_errorCount > ERROR_THRESHOLD) {
FlutterUxcam.logEvent('high_error_rate_detected', {
'error_count': _errorCount,
'window_minutes': MONITORING_WINDOW.inMinutes,
'timestamp': DateTime.now().toIso8601String(),
});
// Could trigger additional actions like:
// - Reducing app functionality
// - Showing user a message
// - Switching to safe mode
}
}
static void _resetWindow() {
_errorCount = 0;
_windowStart = DateTime.now();
}
}Best Practices
Do's
- ✅ Enable automatic crash handling in production
- ✅ Wrap critical operations with exception handling
- ✅ Include relevant context in error reports
- ✅ Monitor error rates and patterns
- ✅ Test crash handling in development
- ✅ Integrate with your existing error tracking
Don'ts
- ❌ Don't catch and ignore exceptions without reporting
- ❌ Don't include sensitive data in error reports
- ❌ Don't overwhelm UXCam with too many exceptions
- ❌ Don't forget to test error scenarios
- ❌ Don't rely solely on automatic crash detection
Next Steps
- User Consent - Implement privacy controls
- Integration Logging - Debug your setup
- Troubleshooting - Solve integration issues
Comprehensive error tracking helps you build more reliable Flutter apps with better user experiences.
Updated 8 months ago
