Firebase analytics és crashlytics használatának beállítása a FirebaseLogger-ben.
This commit is contained in:
parent
ccf62aafce
commit
e126f340c6
@ -1,4 +1,7 @@
|
|||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:firebase_core/firebase_core.dart';
|
import 'package:firebase_core/firebase_core.dart';
|
||||||
|
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
@ -6,8 +9,10 @@ import 'package:supabase_flutter/supabase_flutter.dart';
|
|||||||
import 'package:terepi_seged/pages/tracking/presentation/controllers/tracking_controller.dart';
|
import 'package:terepi_seged/pages/tracking/presentation/controllers/tracking_controller.dart';
|
||||||
import 'package:terepi_seged/routes/app_pages.dart';
|
import 'package:terepi_seged/routes/app_pages.dart';
|
||||||
import 'package:terepi_seged/services/app_database.dart';
|
import 'package:terepi_seged/services/app_database.dart';
|
||||||
|
import 'package:terepi_seged/services/app_logger.dart';
|
||||||
import 'package:terepi_seged/services/coord_converter_service.dart';
|
import 'package:terepi_seged/services/coord_converter_service.dart';
|
||||||
import 'package:terepi_seged/services/device_identity_service.dart';
|
import 'package:terepi_seged/services/device_identity_service.dart';
|
||||||
|
import 'package:terepi_seged/services/firebase_logger.dart';
|
||||||
import 'package:terepi_seged/services/gnss/gnss_device_service.dart';
|
import 'package:terepi_seged/services/gnss/gnss_device_service.dart';
|
||||||
import 'package:terepi_seged/services/gnss/gnss_service.dart';
|
import 'package:terepi_seged/services/gnss/gnss_service.dart';
|
||||||
import 'package:terepi_seged/services/layer_import_service.dart';
|
import 'package:terepi_seged/services/layer_import_service.dart';
|
||||||
@ -20,6 +25,17 @@ import 'package:terepi_seged/services/track_sync_service.dart';
|
|||||||
Future<void> main() async {
|
Future<void> main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
await Firebase.initializeApp();
|
await Firebase.initializeApp();
|
||||||
|
|
||||||
|
FlutterError.onError = (details) {
|
||||||
|
FlutterError.presentError(details);
|
||||||
|
FirebaseCrashlytics.instance.recordFlutterFatalError(details);
|
||||||
|
};
|
||||||
|
|
||||||
|
PlatformDispatcher.instance.onError = (error, stack) {
|
||||||
|
FirebaseCrashlytics.instance.recordError(error, stack, fatal: true);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
await dotenv.load(fileName: ".env");
|
await dotenv.load(fileName: ".env");
|
||||||
|
|
||||||
await Supabase.initialize(
|
await Supabase.initialize(
|
||||||
@ -29,6 +45,8 @@ Future<void> main() async {
|
|||||||
await AppDatabase.instance.database;
|
await AppDatabase.instance.database;
|
||||||
Get.put(ProjectService(), permanent: true);
|
Get.put(ProjectService(), permanent: true);
|
||||||
|
|
||||||
|
Get.put(AppLogger(), permanent: true);
|
||||||
|
Get.put(FirebaseLogger(), permanent: true);
|
||||||
await Get.putAsync<CoordConverterService>(
|
await Get.putAsync<CoordConverterService>(
|
||||||
() => CoordConverterService().init());
|
() => CoordConverterService().init());
|
||||||
Get.put(GnssDeviceService());
|
Get.put(GnssDeviceService());
|
||||||
|
|||||||
@ -1,10 +1,14 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'package:firebase_core/firebase_core.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_foreground_task/flutter_foreground_task.dart';
|
import 'package:flutter_foreground_task/flutter_foreground_task.dart';
|
||||||
|
import 'package:geolocator/geolocator.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:latlong2/latlong.dart';
|
import 'package:latlong2/latlong.dart';
|
||||||
import 'package:share_plus/share_plus.dart';
|
import 'package:share_plus/share_plus.dart';
|
||||||
|
import 'package:terepi_seged/services/app_logger.dart';
|
||||||
|
import 'package:terepi_seged/services/firebase_logger.dart';
|
||||||
import 'package:terepi_seged/services/project_service.dart';
|
import 'package:terepi_seged/services/project_service.dart';
|
||||||
import 'package:terepi_seged/services/track_sync_service.dart';
|
import 'package:terepi_seged/services/track_sync_service.dart';
|
||||||
|
|
||||||
@ -169,11 +173,18 @@ class TrackingController extends GetxController {
|
|||||||
_positionSub = _source!.positionStream.listen(
|
_positionSub = _source!.positionStream.listen(
|
||||||
_onPosition,
|
_onPosition,
|
||||||
onError: (e) {
|
onError: (e) {
|
||||||
Get.snackbar('GPS hiba', e.toString(),
|
AppLogger.e('positionStream', 'Stream hiba', error: e);
|
||||||
backgroundColor: Colors.red, colorText: Colors.white);
|
FirebaseLogger.e('positionStream', 'Stream hiba', error: e);
|
||||||
stopRecording();
|
if (e is LocationServiceDisabledException ||
|
||||||
|
e is PermissionDeniedException) {
|
||||||
|
Get.snackbar('GPS hiba', e.toString(),
|
||||||
|
backgroundColor: Colors.red, colorText: Colors.white);
|
||||||
|
stopRecording();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
AppLogger.e('track_started', 'name:$trackName');
|
||||||
|
FirebaseLogger.event('track_started', {'name': trackName});
|
||||||
|
|
||||||
// Időmérő
|
// Időmérő
|
||||||
_startElapsedTimer();
|
_startElapsedTimer();
|
||||||
@ -268,57 +279,75 @@ class TrackingController extends GetxController {
|
|||||||
// ── Belső logika ───────────────────────────────────────────────────────────
|
// ── Belső logika ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
Future<void> _onPosition(SourcePosition pos) async {
|
Future<void> _onPosition(SourcePosition pos) async {
|
||||||
if (isPaused.value) return;
|
try {
|
||||||
|
if (isPaused.value) return;
|
||||||
|
final sw = Stopwatch()..start();
|
||||||
|
|
||||||
final trackId = currentTrack.value?.id;
|
final trackId = currentTrack.value?.id;
|
||||||
if (trackId == null) return;
|
if (trackId == null) return;
|
||||||
|
|
||||||
// Távolság a legutóbbi ponttól
|
// Távolság a legutóbbi ponttól
|
||||||
double segmentDist = 0;
|
double segmentDist = 0;
|
||||||
if (_lastPoint != null) {
|
if (_lastPoint != null) {
|
||||||
segmentDist = haversineMeters(
|
segmentDist = haversineMeters(
|
||||||
_lastPoint!.latitude,
|
_lastPoint!.latitude,
|
||||||
_lastPoint!.longitude,
|
_lastPoint!.longitude,
|
||||||
pos.latitude,
|
pos.latitude,
|
||||||
pos.longitude,
|
pos.longitude,
|
||||||
|
);
|
||||||
|
// Szűrés: ugrásszerű változás (pl. GPS lock elvesztése) ignorálása
|
||||||
|
if (segmentDist > 100) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_accumulatedDistance += segmentDist;
|
||||||
|
sessionDistance.value = _accumulatedDistance;
|
||||||
|
|
||||||
|
// Sebesség km/h-ban
|
||||||
|
currentSpeedKmh.value = (pos.speed ?? 0) * 3.6;
|
||||||
|
|
||||||
|
// Pont mentése
|
||||||
|
final point = TrackPoint(
|
||||||
|
trackId: trackId,
|
||||||
|
latitude: pos.latitude,
|
||||||
|
longitude: pos.longitude,
|
||||||
|
altitude: pos.altitude,
|
||||||
|
accuracy: pos.accuracy,
|
||||||
|
speed: pos.speed,
|
||||||
|
heading: pos.heading,
|
||||||
|
timestamp: pos.timestamp,
|
||||||
);
|
);
|
||||||
// Szűrés: ugrásszerű változás (pl. GPS lock elvesztése) ignorálása
|
await _db.addPoint(point, _accumulatedDistance);
|
||||||
if (segmentDist > 100) return;
|
sw.stop();
|
||||||
}
|
|
||||||
|
|
||||||
_accumulatedDistance += segmentDist;
|
if (sw.elapsedMilliseconds > 300) {
|
||||||
sessionDistance.value = _accumulatedDistance;
|
AppLogger.w(
|
||||||
|
'on_Position',
|
||||||
|
'Lassú addPoint: ${sw.elapsedMilliseconds}ms, '
|
||||||
|
'pts: ${livePoints.length}');
|
||||||
|
FirebaseLogger.w(
|
||||||
|
'on_Position',
|
||||||
|
'Lassú addPoint: ${sw.elapsedMilliseconds}ms, '
|
||||||
|
'pts: ${livePoints.length}');
|
||||||
|
}
|
||||||
|
|
||||||
// Sebesség km/h-ban
|
TrackSyncService.to.onNewPoint(point);
|
||||||
currentSpeedKmh.value = (pos.speed ?? 0) * 3.6;
|
|
||||||
|
|
||||||
// Pont mentése
|
_lastPoint = point;
|
||||||
final point = TrackPoint(
|
|
||||||
trackId: trackId,
|
|
||||||
latitude: pos.latitude,
|
|
||||||
longitude: pos.longitude,
|
|
||||||
altitude: pos.altitude,
|
|
||||||
accuracy: pos.accuracy,
|
|
||||||
speed: pos.speed,
|
|
||||||
heading: pos.heading,
|
|
||||||
timestamp: pos.timestamp,
|
|
||||||
);
|
|
||||||
await _db.addPoint(point, _accumulatedDistance);
|
|
||||||
|
|
||||||
TrackSyncService.to.onNewPoint(point);
|
// UI frissítés
|
||||||
|
livePoints.add(LatLng(pos.latitude, pos.longitude));
|
||||||
|
|
||||||
_lastPoint = point;
|
// Értesítés frissítése
|
||||||
|
if (livePoints.length % 10 == 0) {
|
||||||
// UI frissítés
|
final dist = _formatDistance(_accumulatedDistance);
|
||||||
livePoints.add(LatLng(pos.latitude, pos.longitude));
|
FlutterForegroundTask.updateService(
|
||||||
|
notificationTitle: 'Track rögzítése – $dist',
|
||||||
// Értesítés frissítése
|
notificationText: elapsedFormatted.value,
|
||||||
if (livePoints.length % 10 == 0) {
|
);
|
||||||
final dist = _formatDistance(_accumulatedDistance);
|
}
|
||||||
FlutterForegroundTask.updateService(
|
} catch (e, stack) {
|
||||||
notificationTitle: 'Track rögzítése – $dist',
|
AppLogger.e('_onPosition', 'pont feldolgozási hiba - track folytatódik',
|
||||||
notificationText: elapsedFormatted.value,
|
error: e, stack: stack);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -14,6 +14,8 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
|
import 'package:terepi_seged/services/app_logger.dart';
|
||||||
|
import 'package:terepi_seged/services/firebase_logger.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
import '../models/device_info_model.dart';
|
import '../models/device_info_model.dart';
|
||||||
@ -58,6 +60,11 @@ class DeviceIdentityService extends GetxService {
|
|||||||
// Háttérben regisztrálás — nem blokkolja az UI-t
|
// Háttérben regisztrálás — nem blokkolja az UI-t
|
||||||
unawaited(_registerDevice());
|
unawaited(_registerDevice());
|
||||||
_isReady = true;
|
_isReady = true;
|
||||||
|
|
||||||
|
FirebaseLogger.setDeviceIdentifier(info.deviceId, deviceLabel.value);
|
||||||
|
FirebaseLogger.setKey('model', model);
|
||||||
|
FirebaseLogger.setKey('os_version', osInfo);
|
||||||
|
FirebaseLogger.setKey('app_version', appInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Betöltés ──────────────────────────────────────────────────────
|
// ── Betöltés ──────────────────────────────────────────────────────
|
||||||
|
|||||||
190
lib/services/firebase_logger.dart
Normal file
190
lib/services/firebase_logger.dart
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
// Firebase alapú log service — Crashlytics + Analytics
|
||||||
|
//
|
||||||
|
// Crashlytics: breadcrumb log, hiba rögzítés, egyedi attribútumok
|
||||||
|
// Analytics: egyedi esemény tracking (warning-ok, hibák, fontos események)
|
||||||
|
//
|
||||||
|
// Éles appban is működik — kiegészíti az AppLogger-t (ami csak debug módban fut)
|
||||||
|
//
|
||||||
|
// Használat:
|
||||||
|
// FirebaseLogger.i('startRecording', 'Track indítva: $name');
|
||||||
|
// FirebaseLogger.w('_onPosition', 'Ugrás kiszűrve: ${dist}m');
|
||||||
|
// FirebaseLogger.e('positionStream', 'GPS hiba', error: e, stack: s);
|
||||||
|
// FirebaseLogger.event('track_completed', {'distance_m': 3200, 'points': 142});
|
||||||
|
|
||||||
|
import 'package:firebase_analytics/firebase_analytics.dart';
|
||||||
|
import 'package:firebase_crashlytics/firebase_crashlytics.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
|
class FirebaseLogger extends GetxService {
|
||||||
|
static FirebaseLogger get to => Get.find();
|
||||||
|
|
||||||
|
// ── Belső referenciák ─────────────────────────────────────────────
|
||||||
|
|
||||||
|
late final FirebaseCrashlytics _crashlytics;
|
||||||
|
late final FirebaseAnalytics _analytics;
|
||||||
|
|
||||||
|
bool _isReady = false;
|
||||||
|
|
||||||
|
// ── Lifecycle ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> onReady() async {
|
||||||
|
super.onReady();
|
||||||
|
try {
|
||||||
|
_crashlytics = FirebaseCrashlytics.instance;
|
||||||
|
_analytics = FirebaseAnalytics.instance;
|
||||||
|
|
||||||
|
// Crashlytics engedélyezése — release-ben mindig, debug-ban opcionális
|
||||||
|
await _crashlytics.setCrashlyticsCollectionEnabled(!kDebugMode);
|
||||||
|
|
||||||
|
// Eszközazonosító beállítása ha már elérhető
|
||||||
|
_setDeviceInfo();
|
||||||
|
|
||||||
|
_isReady = true;
|
||||||
|
_crashlytics.log('FirebaseLogger inicializálva');
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('FirebaseLogger init hiba: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Publikus API — ugyanolyan mint AppLogger ──────────────────────
|
||||||
|
|
||||||
|
/// Info szintű log — Crashlytics breadcrumb
|
||||||
|
static void i(String tag, String message) {
|
||||||
|
_breadcrumb('I', tag, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Figyelmeztetés — breadcrumb + Analytics esemény
|
||||||
|
static void w(String tag, String message, {Object? error}) {
|
||||||
|
_breadcrumb('W', tag, message);
|
||||||
|
_logWarningEvent(tag, message, error: error);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Hiba — Crashlytics recordError + Analytics esemény
|
||||||
|
static void e(
|
||||||
|
String tag,
|
||||||
|
String message, {
|
||||||
|
Object? error,
|
||||||
|
StackTrace? stack,
|
||||||
|
bool fatal = false,
|
||||||
|
}) {
|
||||||
|
_breadcrumb('E', tag, message);
|
||||||
|
|
||||||
|
if (error != null) {
|
||||||
|
try {
|
||||||
|
FirebaseLogger.to._crashlytics.recordError(
|
||||||
|
error,
|
||||||
|
stack,
|
||||||
|
reason: '[$tag] $message',
|
||||||
|
fatal: fatal,
|
||||||
|
printDetails: kDebugMode,
|
||||||
|
information: ['tag: $tag', 'message: $message'],
|
||||||
|
);
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
_logErrorEvent(tag, message, error: error);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Szeparátor — jól látható elválasztó a Crashlytics logban
|
||||||
|
static void separator(String label) {
|
||||||
|
_breadcrumb('─', '─────', '─── $label ───────────────────────');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Egyedi Analytics esemény — tetszőleges adatokkal
|
||||||
|
static void event(
|
||||||
|
String name, [
|
||||||
|
Map<String, Object>? parameters,
|
||||||
|
]) {
|
||||||
|
try {
|
||||||
|
FirebaseLogger.to._analytics.logEvent(
|
||||||
|
name: _sanitizeEventName(name),
|
||||||
|
parameters: parameters,
|
||||||
|
);
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Egyedi Crashlytics attribútum beállítása
|
||||||
|
static void setKey(String key, dynamic value) {
|
||||||
|
try {
|
||||||
|
FirebaseLogger.to._crashlytics.setCustomKey(key, value);
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Eszközinfo beállítása ─────────────────────────────────────────
|
||||||
|
|
||||||
|
void _setDeviceInfo() {
|
||||||
|
try {
|
||||||
|
// DeviceIdentityService ha már elérhető
|
||||||
|
if (Get.isRegistered<dynamic>()) {
|
||||||
|
final deviceService = Get.find(tag: 'DeviceIdentityService');
|
||||||
|
_crashlytics.setUserIdentifier(deviceService.deviceId ?? '');
|
||||||
|
}
|
||||||
|
} catch (_) {
|
||||||
|
// DeviceIdentityService még nem regisztrált — nem baj
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Crashlytics azonosító frissítése ha a DeviceIdentityService betöltött
|
||||||
|
static void setDeviceIdentifier(String deviceId, String label) {
|
||||||
|
try {
|
||||||
|
FirebaseLogger.to._crashlytics.setUserIdentifier(deviceId);
|
||||||
|
FirebaseLogger.to._crashlytics.setCustomKey('device_label', label);
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Belső segédek ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
static void _breadcrumb(String level, String tag, String message) {
|
||||||
|
try {
|
||||||
|
final line = '$level [$tag] $message';
|
||||||
|
FirebaseLogger.to._crashlytics.log(line);
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _logWarningEvent(
|
||||||
|
String tag,
|
||||||
|
String message, {
|
||||||
|
Object? error,
|
||||||
|
}) {
|
||||||
|
try {
|
||||||
|
FirebaseLogger.to._analytics.logEvent(
|
||||||
|
name: 'app_warning',
|
||||||
|
parameters: {
|
||||||
|
'tag': _trim(tag, 40),
|
||||||
|
'message': _trim(message, 100),
|
||||||
|
if (error != null) 'error': _trim(error.toString(), 100),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void _logErrorEvent(
|
||||||
|
String tag,
|
||||||
|
String message, {
|
||||||
|
Object? error,
|
||||||
|
}) {
|
||||||
|
try {
|
||||||
|
FirebaseLogger.to._analytics.logEvent(
|
||||||
|
name: 'app_error',
|
||||||
|
parameters: {
|
||||||
|
'tag': _trim(tag, 40),
|
||||||
|
'message': _trim(message, 100),
|
||||||
|
if (error != null) 'error': _trim(error.toString(), 100),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Analytics eseménynevek csak betűt, számot és _ -t tartalmazhatnak
|
||||||
|
static String _sanitizeEventName(String name) =>
|
||||||
|
name.replaceAll(RegExp(r'[^a-zA-Z0-9_]'), '_').substring(
|
||||||
|
0,
|
||||||
|
name.length.clamp(0, 40),
|
||||||
|
);
|
||||||
|
|
||||||
|
/// Analytics paraméter érték max 100 karakter
|
||||||
|
static String _trim(String s, int max) =>
|
||||||
|
s.length > max ? s.substring(0, max) : s;
|
||||||
|
}
|
||||||
@ -41,10 +41,10 @@ dependencies:
|
|||||||
flutter_map_location_marker: ^10.1.0
|
flutter_map_location_marker: ^10.1.0
|
||||||
path_provider: ^2.1.5
|
path_provider: ^2.1.5
|
||||||
rive: ^0.13.20
|
rive: ^0.13.20
|
||||||
firebase_core: ^3.12.0
|
firebase_core: ^4.11.0
|
||||||
firebase_auth: ^5.5.0
|
firebase_auth: ^6.5.4
|
||||||
cloud_firestore: ^5.6.4
|
cloud_firestore: ^6.6.0
|
||||||
firebase_storage: ^12.4.3
|
firebase_storage: ^13.4.3
|
||||||
firebase_crashlytics: ^5.2.4
|
firebase_crashlytics: ^5.2.4
|
||||||
firebase_analytics: ^12.4.3
|
firebase_analytics: ^12.4.3
|
||||||
google_sign_in: ^6.2.2
|
google_sign_in: ^6.2.2
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user