Skip to content

Instantly share code, notes, and snippets.

@PlugFox
Last active September 14, 2024 09:32
Show Gist options
  • Save PlugFox/37174aee45a86c0692ba2b68e166b67a to your computer and use it in GitHub Desktop.
Save PlugFox/37174aee45a86c0692ba2b68e166b67a to your computer and use it in GitHub Desktop.
AppObserver implements BlocObserver
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