Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I track Flutter screens in Firebase analytics?

I have a Flutter app and I'm testing Google Analytics for Firebase on Flutter.

I wanted to see the routes our users (well, me for now) are visiting. I followed the setup steps in firebase_analytics and I checked their example app, too. I enabled debugging for Analytics as described in the Debug View docs

Unfortunately, the only two kinds of screen views (firebase_screen_class) I receive in my Analytics Debug view are Flutter and MainActivity.

I'd expect to see /example-1, /example-2 and /welcome somewhere, but I don't.

This is the app I'm running in Flutter

class App extends StatelessWidget {   final FirebaseAnalytics analytics = FirebaseAnalytics();   @override   Widget build(BuildContext context) {     return MaterialApp(       routes: <String, WidgetBuilder>{         '/example-1': (_) => Example1(),         '/example-2': (_) => Example2(),         '/welcome': (_) => Welcome(),       },       home: Welcome(),       navigatorObservers: [FirebaseAnalyticsObserver(analytics: analytics)],     );   } } 
like image 589
Vince Varga Avatar asked Apr 24 '19 12:04

Vince Varga


People also ask

How do I test my Firebase Analytics flutter?

To test Flutter apps with Firebase Test Lab, you can write Flutter integration tests, build Android APKs or iOS test zip files, and run as regular Android instrumentation tests or iOS XCTests.

How do I track Firebase Analytics?

Implementation path Just add the Firebase SDK to your new or existing app, and data collection begins automatically. You can view analytics data in the Firebase console within hours. You can use Analytics to log custom events that make sense for your app, like E-Commerce purchases or achievements.

What is screen view in Firebase Analytics?

Automatically track screensAnalytics automatically tracks some information about screens in your application, such as the class name of the UIViewController or Activity that is currently in focus. When a screen transition occurs, Analytics logs a screen_view event that identifies the new screen.


1 Answers

This exact use-case is in the documentation for Firebase Analytics under the Track Screenviews section.

Manually tracking screens is useful if your app does not use a separate UIViewController or Activity for each screen you may wish to track, such as in a game.

This is exactly the case with Flutter, as Flutter is taking care of the screen updates: most simple Flutter apps run one single FlutterActivity/FlutterAppDelegate and it takes care of rendering different screens on its own, so letting Firebase Analytics automatically track screens will not bring the desired effect.

As far as my past experience goes, the FirebaseAnalyticsObserver was not very helpful, however, I recommend you, too, check their docs again, they do imply that things should "just work". My best guess is that it didn't work well for me because I didn't use RouteSettings on any of my routes *.

In case FirebaseAnalyticsObserver won't work or apply for your app, the next approach worked quite well for me over the past months of development.

You can set the current screen with FirebaseAnalytics at any point, if you call the setCurrentScreen method with the screen name:

import 'package:firebase_analytics/firebase_analytics.dart'; // Somewhere in your widgets... FirebaseAnalytics().setCurrentScreen(screenName: 'Example1'); 

As a first attempt I did this in the widget constructor, but that will not work well and miscount the events: if you pop or push routes, all widget constructors in the stack will be called, even though only the top route really qualifies as "the current screen".

To solve this, we need to use the RouteAware class and only set the current screen in case it's the top route: either our route is added to the stack or the previous top route was popped and we arrived onto the route.

RouteAware comes with boilerplate code and we don't want to repeat that boilerplate for all of our screens. Even for small apps, you have tens of different screens, so I created the RouteAwareAnalytics mixin:

import 'package:firebase_analytics/firebase_analytics.dart'; import 'package:flutter/widgets.dart';  // A Navigator observer that notifies RouteAwares of changes to state of their Route final routeObserver = RouteObserver<PageRoute>();  mixin RouteAwareAnalytics<T extends StatefulWidget> on State<T>     implements RouteAware {   AnalyticsRoute get route;    @override   void didChangeDependencies() {     routeObserver.subscribe(this, ModalRoute.of(context));     super.didChangeDependencies();   }    @override   void dispose() {     routeObserver.unsubscribe(this);     super.dispose();   }    @override   void didPop() {}    @override   void didPopNext() {     // Called when the top route has been popped off,     // and the current route shows up.     _setCurrentScreen(route);   }    @override   void didPush() {     // Called when the current route has been pushed.     _setCurrentScreen(route);   }    @override   void didPushNext() {}    Future<void> _setCurrentScreen(AnalyticsRoute analyticsRoute) {     print('Setting current screen to $analyticsRoute');     return FirebaseAnalytics().setCurrentScreen(       screenName: screenName(analyticsRoute),       screenClassOverride: screenClass(analyticsRoute),     );   } } 

I created an enum to track the screens (and functions to turn the enum to screen names). I used the enums to be able to easily track all routes, refactor route names. Using these enums and functions, I can unit test all possible values and enforce consistent naming: no accidental spaces or special characters, no inconsistent capitalization. There could be other, better ways to determine screen class values, but I went with this approach.

enum AnalyticsRoute { example }  String screenClass(AnalyticsRoute route) {   switch (route) {     case AnalyticsRoute.example:       return 'ExampleRoute';   }   throw ArgumentError.notNull('route'); }  String screenName(AnalyticsRoute route) {   switch (route) {     case AnalyticsRoute.example:       return '/example';   }   throw ArgumentError.notNull('route'); } 

Next step in the inital setup is to register the routeObserver as a navigatorObserver of your MaterialApp:

MaterialApp(   // ...   navigatorObservers: [     routeObserver,     // FirebaseAnalyticsObserver(analytics: FirebaseAnalytics()),   ], ); 

Finally, we can add our first example route that's tracked. Add the with RouteAwareAnalytics to your states and override get route.

class ExampleRoute extends StatefulWidget {   @override   _ExampleRouteState createState() => _ExampleRouteState(); }  class _ExampleRouteState extends State<ExampleRoute> with RouteAwareAnalytics{   @override   Widget build(BuildContext context) => Text('Example');    @override   AnalyticsRoute get route => AnalyticsRoute.example; } 

Every time you add a new route, you can do so with little effort: first, add a new enum value, then the Dart compiler will guide you what to add next: add the screen name and class override values in their respective switch-case. Then, find your state that's building your route, add with RouteAwareAnalytics, and add the route getter.

* The reason why I didn't use RouteSettings is that I prefer Simon Lightfoot's approach with the typed arguments instead of the Object arguments the settings provide:

class ExampleRoute extends StatefulWidget {   const ExampleRoute._({@required this.integer, Key key}) : super(key: key);   // All types of members are supported, but I used int as example   final int integer;   static Route<void> route({@required int integer}) =>       MaterialPageRoute(         // I could add the settings here, though, it wouldn't enforce good types         builder: (_) => ExampleRoute._(integer: integer),       );   // ... } 
like image 105
Vince Varga Avatar answered Sep 28 '22 14:09

Vince Varga