[{"data":1,"prerenderedAt":27},["ShallowReactive",2],{"articolo-analytics-in-flutter-con-firebase-analytics-tracciare-eventi-schermate-e-conversioni":3,"comments-article-analytics-in-flutter-con-firebase-analytics-tracciare-eventi-schermate-e-conversioni":26},{"id":4,"title":5,"slug":6,"excerpt":7,"body":8,"cover_image":9,"cover_remote_url":10,"cover_credit":11,"video_url":15,"status":16,"published_at":17,"meta_title":18,"meta_description":19,"category":20,"author":24},96,"Analytics in Flutter con Firebase Analytics: tracciare eventi, schermate e conversioni","analytics-in-flutter-con-firebase-analytics-tracciare-eventi-schermate-e-conversioni","Guida pratica per integrare Firebase Analytics in un'app Flutter: setup, eventi custom, tracking automatico delle schermate con go_router, user properties, gestione del consenso GDPR e debugging con DebugView.","Capire come gli utenti usano davvero la nostra app è la differenza tra sviluppare a sensazione e sviluppare sui dati. Firebase Analytics (basato su Google Analytics 4) è la soluzione più diffusa nel mondo Flutter: è gratuita, funziona su Android, iOS e Web, e si integra con il resto dell'ecosistema Firebase (Crashlytics, Remote Config, A\u002FB Testing).\n\nIn questo articolo vediamo come integrarlo in modo pulito e manutenibile, evitando l'errore classico di spargere chiamate a `FirebaseAnalytics.instance` in mezzo ai widget.\n\n## Setup iniziale\n\nDopo aver configurato il progetto Firebase con la CLI (`flutterfire configure`), aggiungiamo le dipendenze:\n\n```yaml\ndependencies:\n  firebase_core: ^3.8.0\n  firebase_analytics: ^11.3.6\n```\n\nInizializziamo Firebase nel `main`:\n\n```dart\nvoid main() async {\n  WidgetsFlutterBinding.ensureInitialized();\n  await Firebase.initializeApp(\n    options: DefaultFirebaseOptions.currentPlatform,\n  );\n  runApp(const MyApp());\n}\n```\n\nSu Android non serve altro: il plugin raccoglie automaticamente eventi come `first_open`, `session_start`, `app_update` e `app_remove`. Su iOS, se vuoi tracciare la provenienza delle installazioni, ricordati di gestire l'App Tracking Transparency (ne parliamo più avanti).\n\n## Eventi predefiniti, eventi custom e i limiti di GA4\n\nGA4 mette a disposizione una serie di **eventi predefiniti** con parametri già mappati nei report: `login`, `sign_up`, `search`, `select_content`, `purchase`, `add_to_cart`. Usarli quando esistono è sempre la scelta migliore, perché alimentano dashboard e funnel già pronti.\n\n```dart\nfinal analytics = FirebaseAnalytics.instance;\n\nawait analytics.logLogin(loginMethod: 'google');\nawait analytics.logSearch(searchTerm: 'scarpe da running');\nawait analytics.logSelectContent(contentType: 'product', itemId: 'SKU-1234');\n```\n\nQuando l'evento è specifico del tuo dominio, si usa `logEvent`:\n\n```dart\nawait analytics.logEvent(\n  name: 'recipe_saved',\n  parameters: \u003CString, Object>{\n    'recipe_id': 'r_8891',\n    'category': 'primi',\n    'from_screen': 'recipe_detail',\n    'is_premium': 1, \u002F\u002F niente bool: usa 0\u002F1\n  },\n);\n```\n\nAttenzione ai vincoli di GA4, perché vengono ignorati silenziosamente:\n\n- massimo **500 nomi di evento distinti** per app;\n- nome evento: max 40 caratteri, deve iniziare con una lettera, solo lettere\u002Fnumeri\u002Funderscore;\n- massimo **25 parametri per evento**;\n- nome parametro max 40 caratteri, valore stringa max 100 caratteri;\n- i parametri accettano solo `String` e `num`: i booleani vanno convertiti in `0`\u002F`1`;\n- i nomi che iniziano con `firebase_`, `google_` o `ga_` sono riservati.\n\nUn'altra regola d'oro: **non registrare mai dati personali** (email, numeri di telefono, nomi, ID fiscali) nei parametri. Oltre a essere una violazione del GDPR, viola anche i termini d'uso di Firebase.\n\n## Un servizio di analytics astratto\n\nChiamare direttamente `FirebaseAnalytics.instance` dai widget rende il codice difficile da testare e lega la UI a un SDK di terze parti. Meglio definire un'interfaccia e un catalogo tipizzato di eventi.\n\n```dart\nabstract interface class AnalyticsService {\n  Future\u003Cvoid> logEvent(AnalyticsEvent event);\n  Future\u003Cvoid> setUserId(String? id);\n  Future\u003Cvoid> setUserProperty(String name, String? value);\n  Future\u003Cvoid> logScreen(String name, {String? screenClass});\n}\n\n\u002F\u002F\u002F Catalogo degli eventi: una sola fonte di verità per nomi e parametri.\nsealed class AnalyticsEvent {\n  const AnalyticsEvent();\n\n  String get name;\n  Map\u003CString, Object> get parameters => const {};\n}\n\nfinal class RecipeSaved extends AnalyticsEvent {\n  const RecipeSaved({required this.recipeId, required this.category});\n\n  final String recipeId;\n  final String category;\n\n  @override\n  String get name => 'recipe_saved';\n\n  @override\n  Map\u003CString, Object> get parameters => {\n        'recipe_id': recipeId,\n        'category': category,\n      };\n}\n\nfinal class CheckoutStarted extends AnalyticsEvent {\n  const CheckoutStarted({required this.value, required this.itemCount});\n\n  final double value;\n  final int itemCount;\n\n  @override\n  String get name => 'checkout_started';\n\n  @override\n  Map\u003CString, Object> get parameters => {\n        'value': value,\n        'currency': 'EUR',\n        'item_count': itemCount,\n      };\n}\n```\n\nL'implementazione Firebase diventa banale e completamente isolata:\n\n```dart\nclass FirebaseAnalyticsService implements AnalyticsService {\n  FirebaseAnalyticsService(this._analytics);\n\n  final FirebaseAnalytics _analytics;\n\n  @override\n  Future\u003Cvoid> logEvent(AnalyticsEvent event) async {\n    try {\n      await _analytics.logEvent(\n        name: event.name,\n        parameters: event.parameters.isEmpty ? null : event.parameters,\n      );\n    } catch (e, s) {\n      \u002F\u002F L'analytics non deve mai far crashare l'app.\n      debugPrint('Analytics error: $e\\n$s');\n    }\n  }\n\n  @override\n  Future\u003Cvoid> setUserId(String? id) => _analytics.setUserId(id: id);\n\n  @override\n  Future\u003Cvoid> setUserProperty(String name, String? value) =>\n      _analytics.setUserProperty(name: name, value: value);\n\n  @override\n  Future\u003Cvoid> logScreen(String name, {String? screenClass}) =>\n      _analytics.logScreenView(screenName: name, screenClass: screenClass);\n}\n```\n\nIn ambiente di sviluppo puoi sostituirla con una `DebugAnalyticsService` che stampa gli eventi in console, oppure con una `NoopAnalyticsService` nei test, senza toccare una riga di UI.\n\n## Tracciare le schermate con go_router\n\nSu Flutter il tracking automatico delle schermate non funziona come sul nativo: il framework vede una sola `Activity`\u002F`ViewController`. Serve quindi un `NavigatorObserver`.\n\nCon la navigazione classica basta aggiungere l'observer fornito dal plugin:\n\n```dart\nMaterialApp(\n  navigatorObservers: [\n    FirebaseAnalyticsObserver(analytics: FirebaseAnalytics.instance),\n  ],\n);\n```\n\nCon `go_router` conviene invece scrivere un observer che usi il **nome della rotta**, molto più leggibile dei path con parametri:\n\n```dart\nclass AnalyticsRouteObserver extends NavigatorObserver {\n  AnalyticsRouteObserver(this._analytics);\n\n  final AnalyticsService _analytics;\n\n  void _track(Route\u003Cdynamic>? route) {\n    final settings = route?.settings;\n    if (route is! PageRoute || settings == null) return;\n\n    final name = settings.name;\n    if (name == null || name.isEmpty) return;\n\n    _analytics.logScreen(name, screenClass: route.runtimeType.toString());\n  }\n\n  @override\n  void didPush(Route\u003Cdynamic> route, Route\u003Cdynamic>? previousRoute) =>\n      _track(route);\n\n  @override\n  void didPop(Route\u003Cdynamic> route, Route\u003Cdynamic>? previousRoute) =>\n      _track(previousRoute);\n\n  @override\n  void didReplace({Route\u003Cdynamic>? newRoute, Route\u003Cdynamic>? oldRoute}) =>\n      _track(newRoute);\n}\n```\n\nE si registra così:\n\n```dart\nfinal router = GoRouter(\n  observers: [AnalyticsRouteObserver(analyticsService)],\n  routes: [\n    GoRoute(\n      path: '\u002Frecipes\u002F:id',\n      name: 'recipe_detail', \u002F\u002F questo nome finirà in screen_view\n      builder: (context, state) => RecipeDetailPage(\n        id: state.pathParameters['id']!,\n      ),\n    ),\n  ],\n);\n```\n\nAttenzione: con `ShellRoute` e `StatefulShellRoute` (bottom navigation persistente) il cambio di tab non genera un push, quindi l'observer non scatta. In quei casi va loggato manualmente lo `screen_view` nel callback di cambio branch.\n\n## User ID e user properties\n\nLe **user properties** sono attributi persistenti dell'utente (massimo 25 per progetto) e permettono di segmentare tutti i report: sono lo strumento ideale per rispondere a domande come \"gli utenti premium completano il checkout più spesso?\".\n\n```dart\nFuture\u003Cvoid> onUserLoggedIn(User user) async {\n  \u002F\u002F ID pseudonimo, mai l'email\n  await analyticsService.setUserId(user.uid);\n  await analyticsService.setUserProperty('plan', user.isPremium ? 'premium' : 'free');\n  await analyticsService.setUserProperty('app_theme', 'dark');\n}\n\nFuture\u003Cvoid> onLogout() async {\n  await analyticsService.setUserId(null);\n  await analyticsService.setUserProperty('plan', null);\n}\n```\n\nRicorda di ripulire le proprietà al logout, altrimenti i dati del vecchio utente continueranno a segmentare la sessione del nuovo.\n\n## Consenso, privacy e GDPR\n\nIn Europa la raccolta dati richiede consenso esplicito. La strategia corretta è **disattivare la raccolta di default** e abilitarla solo dopo l'accettazione.\n\nSu Android, nel `AndroidManifest.xml`:\n\n```xml\n\u003Cmeta-data\n    android:name=\"firebase_analytics_collection_enabled\"\n    android:value=\"false\" \u002F>\n```\n\nSu iOS, in `Info.plist`:\n\n```xml\n\u003Ckey>FIREBASE_ANALYTICS_COLLECTION_ENABLED\u003C\u002Fkey>\n\u003Cfalse\u002F>\n```\n\nPoi, a runtime:\n\n```dart\nFuture\u003Cvoid> applyConsent({required bool analyticsAllowed, required bool adsAllowed}) async {\n  final analytics = FirebaseAnalytics.instance;\n\n  await analytics.setAnalyticsCollectionEnabled(analyticsAllowed);\n\n  await analytics.setConsent(\n    analyticsStorageConsentGranted: analyticsAllowed,\n    adStorageConsentGranted: adsAllowed,\n    adUserDataConsentGranted: adsAllowed,\n    adPersonalizationSignalsConsentGranted: adsAllowed,\n  );\n}\n```\n\nSu iOS, se usi l'IDFA per l'attribuzione, devi anche richiedere il permesso ATT (pacchetto `app_tracking_transparency`) **prima** di abilitare la raccolta, e dichiarare l'uso dei dati nel Privacy Manifest richiesto da Apple.\n\n## Debug: vedere gli eventi in tempo reale\n\nGli eventi vengono inviati in batch (tipicamente ogni ora), quindi in sviluppo serve la **DebugView** della console Firebase.\n\nSu Android, da terminale:\n\n```bash\nadb shell setprop debug.firebase.analytics.app com.example.myapp\n# per disattivare:\nadb shell setprop debug.firebase.analytics.app .none.\n```\n\nSu iOS, aggiungi l'argomento `-FIRDebugEnabled` negli argomenti di lancio dello schema Xcode (o `flutter run --dart-define` non basta: serve l'argomento nativo).\n\nUn trucco utile: nella build di debug, invece di inviare a Firebase, logga a console.\n\n```dart\nclass ConsoleAnalyticsService implements AnalyticsService {\n  @override\n  Future\u003Cvoid> logEvent(AnalyticsEvent event) async {\n    debugPrint('📊 ${event.name} → ${event.parameters}');\n  }\n  \u002F\u002F ...altre implementazioni no-op\n}\n\nfinal AnalyticsService analyticsService = kDebugMode\n    ? ConsoleAnalyticsService()\n    : FirebaseAnalyticsService(FirebaseAnalytics.instance);\n```\n\nCosì eviti di sporcare i dati di produzione con il traffico degli sviluppatori (in alternativa, filtra il traffico interno con una user property `env: dev`).\n\n## Testare il tracking\n\nAvere un'interfaccia astratta rende i test banali:\n\n```dart\nclass FakeAnalyticsService implements AnalyticsService {\n  final List\u003CAnalyticsEvent> events = [];\n\n  @override\n  Future\u003Cvoid> logEvent(AnalyticsEvent event) async => events.add(event);\n\n  @override\n  Future\u003Cvoid> logScreen(String name, {String? screenClass}) async {}\n\n  @override\n  Future\u003Cvoid> setUserId(String? id) async {}\n\n  @override\n  Future\u003Cvoid> setUserProperty(String name, String? value) async {}\n}\n\ntestWidgets('salvando la ricetta viene tracciato recipe_saved', (tester) async {\n  final fake = FakeAnalyticsService();\n  await tester.pumpWidget(buildApp(analytics: fake));\n\n  await tester.tap(find.byIcon(Icons.bookmark_outline));\n  await tester.pumpAndSettle();\n\n  expect(fake.events.single, isA\u003CRecipeSaved>());\n  expect(fake.events.single.parameters['category'], 'primi');\n});\n```\n\n## Errori comuni da evitare\n\n- **Tracciare tutto**: ogni evento senza una domanda di business dietro è rumore. Parti da 10-15 eventi chiave legati al funnel principale.\n- **Nomi incoerenti**: `ButtonClick`, `button_clicked`, `btn_click` diventano tre eventi diversi e inutilizzabili. Il catalogo tipizzato risolve alla radice.\n- **Alta cardinalità nei parametri**: usare un `recipe_id` come dimensione va bene, ma non aspettarti report leggibili se i valori distinti sono decine di migliaia.\n- **Await bloccanti**: non aspettare mai il completamento di un log prima di aggiornare la UI; l'analytics è fire-and-forget.\n- **Dimenticare la registrazione delle dimensioni custom**: in GA4 i parametri custom vanno registrati come dimensioni o metriche personalizzate nella console, altrimenti non compaiono nei report standard.\n\n## Conclusioni\n\nFirebase Analytics è potente, ma il valore dipende quasi interamente dalla disciplina con cui lo si integra. Definire un catalogo di eventi tipizzato, isolare l'SDK dietro un'interfaccia, tracciare automaticamente le schermate tramite un observer e gestire correttamente il consenso sono i quattro passi che trasformano un tracking caotico in una fonte di dati affidabile — e testabile — su cui costruire decisioni di prodotto.","https:\u002F\u002Fflutter.it\u002Fstorage\u002Farticles\u002F133fac58-3413-4dfa-8196-9d1c8d9fa2e1.jpg","https:\u002F\u002Fimages.unsplash.com\u002Fphoto-1626342766970-73cab95398a4?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=M3w5NzA2NTJ8MHwxfHJhbmRvbXx8fHx8fHx8fDE3ODkyNzIwOTF8&ixlib=rb-4.1.0&q=80&w=1080",{"name":12,"author_url":13,"photo_url":14},"Sulpicio Helps","https:\u002F\u002Funsplash.com\u002F@sulpicio","https:\u002F\u002Funsplash.com\u002Fphotos\u002Fblack-android-smartphone-displaying-11-00-vRCelJ8dxYo",null,"published","2026-09-13T04:01:31+00:00","Firebase Analytics in Flutter: guida pratica","Come integrare Firebase Analytics in Flutter: eventi custom, screen tracking con go_router, user properties, consenso GDPR e debugging con DebugView.",{"id":21,"name":22,"slug":23},1,"Guide","guide",{"id":21,"name":25},"Flutter Bot",[],1789295421915]