Last active
September 14, 2024 09:32
-
-
Save PlugFox/37174aee45a86c0692ba2b68e166b67a to your computer and use it in GitHub Desktop.
AppObserver implements BlocObserver
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import 'dart:async'; | |
import 'package:firebase_analytics/firebase_analytics.dart' | |
show FirebaseAnalytics; | |
import 'package:flutter_bloc/flutter_bloc.dart'; | |
import 'package:l/l.dart' show L, LogMessage, LogLevel; | |
///import 'package:.../src/utils/mobile_crashlytics.dart'; | |
import 'package:meta/meta.dart' show required, immutable; | |
import 'package:platform_info/platform_info.dart'; | |
class AppObserver implements BlocObserver { | |
bool _isClosed = false; | |
FirebaseAnalytics _analytics; | |
MobileCrashlytics _crashlytics; | |
StreamSubscription<LogMessage> _loggerSubscription; | |
final L _logger; | |
final List<_AppObserverEvent> _cashedEvents = []; | |
final List<_AppObserverError> _cashedErrors = []; | |
// SINGLETON + | |
static AppObserver get I => _internalSingleton; | |
static final AppObserver _internalSingleton = AppObserver._internal(); | |
factory AppObserver() => _internalSingleton; | |
AppObserver._internal() : _logger = L.I { | |
_logger.vvvvv('Создан AppObserver'); | |
_loggerSubscription = _logger.stream.listen(_logEventFromLogger); | |
} | |
// SINGLETON - | |
void addFirebaseSupport({ | |
@required FirebaseAnalytics analytics, | |
@required MobileCrashlytics crashlytics, | |
}) { | |
if (_isClosed) return; | |
_analytics = analytics; | |
_crashlytics = crashlytics; | |
try { | |
for (final event in _cashedEvents) { | |
_analytics.logEvent( | |
name: event.name, | |
parameters: event.parameters, | |
); | |
} | |
_cashedEvents.clear(); | |
} on dynamic catch (error, stackTrace) { | |
_logger.e('Error with cashed events in AppObserver:' | |
'\n${error.toString()}' | |
'\n${stackTrace.toString()}'); | |
} | |
try { | |
for (final event in _cashedErrors) { | |
_crashlytics.recordError( | |
event.error, | |
event.stackTrace, | |
reason: event.comment, | |
); | |
} | |
_cashedErrors.clear(); | |
} on dynamic catch (error, stackTrace) { | |
_logger.e('Error with cashed errors in AppObserver:' | |
'\n${error.toString()}' | |
'\n${stackTrace.toString()}'); | |
} | |
} | |
@override | |
void onEvent(Bloc bloc, Object event) { | |
_logger.vvvvvv(' * $event'); | |
} | |
@override | |
void onChange(Cubit cubit, Change change) {} | |
@override | |
void onTransition(Bloc bloc, Transition transition) { | |
_logger.vvvvvv(' * $transition'); | |
_logEvent( | |
'observer_transition', | |
<String, dynamic>{ | |
'event': transition.event.toString().getFirst(100), | |
'current_state': transition.currentState.toString().getFirst(100), | |
'next_state': transition.nextState.toString().getFirst(100), | |
}, | |
); | |
} | |
@override | |
void onError(Cubit cubit, Object error, StackTrace stackTrace) { | |
_logger.e(error.toString()); | |
recordError(error, | |
stackTrace: stackTrace, | |
comment: 'Cubit: ${cubit.runtimeType.toString()}'); | |
} | |
void recordError(Object error, {StackTrace stackTrace, String comment}) { | |
if (_isClosed && platform.buildMode != BuildMode.release) { | |
return; | |
} | |
if (_crashlytics != null) { | |
_crashlytics.recordError(error, stackTrace, reason: comment ?? ''); | |
} else { | |
_cashedErrors.add(_AppObserverError( | |
error: error, | |
stackTrace: stackTrace, | |
comment: comment, | |
)); | |
} | |
_logEvent( | |
'observer_error', | |
<String, dynamic>{ | |
'error': error.toString().getFirst(100), | |
if (stackTrace is StackTrace) | |
'stacktrace': stackTrace.toString().getFirst(100), | |
if (comment is String) 'comment': comment.getFirst(100), | |
}, | |
); | |
} | |
void recordEvent(String name, {Map<String, dynamic> parameters}) { | |
_logger.i(' * $name'); | |
_logEvent( | |
name, | |
parameters, | |
); | |
} | |
void close() { | |
if (_isClosed) return; | |
_isClosed = true; | |
_loggerSubscription.cancel(); | |
} | |
void _logEventFromLogger(LogMessage message) { | |
String name; | |
switch (message.level) { | |
case LogLevel.shout: | |
name = 'logger_shout'; | |
break; | |
case LogLevel.v: | |
name = 'logger_v'; | |
break; | |
case LogLevel.error: | |
name = 'logger_error'; | |
break; | |
case LogLevel.vv: | |
name = 'logger_vv'; | |
break; | |
case LogLevel.warning: | |
name = 'logger_warning'; | |
break; | |
default: | |
return; | |
} | |
_logEvent( | |
name, | |
message.toMap(), | |
); | |
} | |
void _logEvent(String name, [Map<String, dynamic> parameters]) { | |
if (_isClosed && platform.buildMode != BuildMode.release) { | |
return; | |
} | |
if (_analytics != null) { | |
_analytics.logEvent( | |
name: name, | |
parameters: parameters, | |
); | |
} else { | |
_cashedEvents.add(_AppObserverEvent( | |
name: name, | |
parameters: parameters, | |
)); | |
} | |
} | |
} | |
extension _GetFirstStringExtension on String { | |
String getFirst([int length = 100]) => | |
this.length > length ? substring(0, length ?? 100) : this; | |
} | |
@immutable | |
class _AppObserverEvent { | |
final String name; | |
final Map<String, dynamic> parameters; | |
const _AppObserverEvent({@required this.name, this.parameters}); | |
@override | |
String toString() => ' * $name'; | |
} | |
@immutable | |
class _AppObserverError { | |
final Object error; | |
final StackTrace stackTrace; | |
final String comment; | |
const _AppObserverError( | |
{@required this.error, this.stackTrace, this.comment}); | |
@override | |
String toString() => ' * $error'; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment