如何在Firebase分析中跟踪Flutter屏幕?

58

我有一个Flutter应用程序,正在测试Flutter的Google Analytics for Firebase。

我想查看我们的用户(现在只是我)访问的路由。 我按照firebase_analytics中的设置步骤进行,并查看了他们的示例应用程序。 我按照Debug View docs中描述的启用Analytics的调试。

不幸的是,在我的Analytics调试视图中收到的唯二的屏幕视图(firebase_screen_class)是FlutterMainActivity

我希望能够在某个地方看到/example-1/example-2/welcome,但我没有看到。

这是我在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)],
    );
  }
}

为什么你不只是在每个单独的屏幕上放置事件,而不是制作漏斗? - Bo Z
在你的屏幕构造函数中添加 Firebase 事件。 - Atif AbbAsi
5个回答

79

在Firebase Analytics文档的Track Screenviews章节中有一个精确的用例。

如果您的应用程序没有为要跟踪的每个屏幕使用单独的UIViewController或Activity(例如在游戏中),则手动跟踪屏幕非常有用。

这正是Flutter所面临的情况,因为Flutter负责屏幕更新:大多数简单的Flutter应用程序运行一个单独的FlutterActivity/FlutterAppDelegate并自行处理不同屏幕的呈现,因此让Firebase Analytics自动跟踪屏幕将无法产生所需的效果。

据我过去的经验,FirebaseAnalyticsObserver并没有太大帮助,但我建议您再次检查他们的文档,因为他们暗示事情应该“只需要工作”。我最好的猜测是对我来说它没起作用,因为我没有在任何路由上使用RouteSettings*

如果FirebaseAnalyticsObserver不能工作或不适用于您的应用程序,则在开发的过去几个月中,下一种方法对我而言效果非常好。

如果您使用屏幕名调用setCurrentScreen方法,则可以在任何时候使用FirebaseAnalytics设置当前屏幕:

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

作为第一次尝试,我在小部件构造函数中完成了这个操作,但这样做不太好,会误计事件:如果您弹出或推入路由,则堆栈中的所有小部件构造函数都将被调用,即使只有顶部路由真正符合“当前屏幕”的条件。
为解决此问题,我们需要使用RouteAware类,并仅在它是顶部路由的情况下设置当前屏幕:无论是将我们的路由添加到堆栈还是弹出先前的顶部路由并到达路由。 RouteAware附带样板代码,我们不想为所有屏幕重复该样板。即使对于小型应用程序,您也有数十个不同的屏幕,因此我创建了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),
    );
  }
}

我创建了一个枚举来跟踪屏幕(并创建了将枚举转换为屏幕名称的函数)。我使用枚举来轻松跟踪所有路由、重构路由名称。使用这些枚举和函数,我可以对所有可能的值进行单元测试,并强制实施一致的命名:没有意外的空格或特殊字符,没有不一致的大写字母。可能有其他更好的方法来确定屏幕类值,但我选择了这种方法。
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');
}

在初始设置的下一步是将routeObserver注册为您的MaterialAppnavigatorObserver

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

最后,我们可以添加第一个被跟踪的示例路由。将with RouteAwareAnalytics添加到您的状态并重写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;
}

每次添加新路由时,您可以轻松完成:首先添加一个新的枚举值,然后Dart编译器会指导您接下来要添加什么:在各自的switch-case中添加屏幕名称和类重写值。然后,在构建路由的状态中找到with RouteAwareAnalytics,并添加route getter。 *我没有使用RouteSettings的原因是,我更喜欢Simon Lightfoot的方法,该方法使用类型化参数而不是设置提供的Object参数:
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),
      );
  // ...
}

3
DRY 的优点:我意识到仅使用 screen_view 事件无法构建漏斗,因此我只需更新抽象类的 setCurrentScreen 方法,并添加额外的 analytics.logEvent(name: 'screen_view-$screenName'); - Vince Varga
1
你真的能在Firebase控制台中看到你的真实屏幕名称吗?无论我如何使用setCurrentScreen("whatever");命名事件,我只能看到"screen_name"。 - Logemann
5
有没有已知的方法可以为ModalRoutes实现日志记录?我在我的应用程序中有很多底部表单,它们是无状态小部件(这就是为什么我不想使用RouteAware的原因,因为它需要有状态的小部件),而标准的FirebaseAnalyticsObserver不起作用,因为它只能观察PageRoutes。 - Limbou
2
有没有想过如何在无状态小部件上使用mixin? - Firu
@Limbou 我广泛使用animations包中的OpenContainer,它使用ModalRoute。我将路由观察器类型更改为ModalRoute,所以它是final routeObserver = RouteObserver<ModalRoute>();,这对我很有效。因为PageRoute扩展了ModalRoute,所以也可能适用于您? - James Allen
显示剩余5条评论

30

添加导航观察器

向您的MatetialApp添加Firebase分析导航观察器:

class MyApp extends StatelessWidget {
  FirebaseAnalytics analytics = FirebaseAnalytics();
...

MaterialApp(
  home: MyAppHome(),
  navigatorObservers: [
    FirebaseAnalyticsObserver(analytics: analytics), // <-- here
  ],
);

就是这样!您的分析结果应该会显示在DebugView中:

enter image description here

注意!

如果您第一次在应用中集成分析功能,需要大约一天时间才能在仪表板上看到您的分析结果。

立即查看结果

要立即查看调试结果,请在终端上运行上述命令,然后检查它们是否出现在 DebugView 中:

adb shell setprop debug.firebase.analytics.app [your_app_package_name]

享受!


我在你的截图中看不到实际的屏幕名称,只看到screen_name? - giorgio79
如果您点击屏幕视图分析之一,您将获得更多信息,例如时间戳、令牌和屏幕名称。 - genericUser
1
screen_view事件具有多个参数,实际屏幕名称信息位于screen_view -> firebase_screen参数中。谢谢@genericUser。 - Sergey

4

我遇到了这个问题一段时间,最终成功解决了

对于我来说,问题在于我没有正确地在MaterialPageRoute中传递设置(settings)

return MaterialPageRoute(
      settings: RouteSettings(
        name: routeName,
      ),
      builder: (_) => viewToShow);
}

我按照FilledStack上的教程进行操作,通过查看示例代码解决了我的问题。


2
如果在“screen_view”事件的“firebase_screen_class”参数中看到“Flutter”,则表示您已正确配置。您应该在“firebase_screen”参数中找到您期望的值,而不是“firebase_screen_class”。另外,值得检查“firebase_previous_screen”参数,以查看之前打开的屏幕是什么。

1
如果您在使用setCurrentScreen设置屏幕名称时没有看到您的屏幕名称,请确保在分析控制台的“页面和屏幕”页面中设置了下拉字段为页面标题和屏幕名称,然后您将能够看到您的Flutter屏幕。

enter image description here

在我们的情况下,我们无法使用导航观察器,但最终解决的方法是使用同名包中的VisibilityDetector。 在其他情况下,我们只需在构建函数的开头调用我们自己的logScreen函数,但只有在屏幕名称发生变化时才调用Firebase的setCurrentScreen函数。
String _lastScreenName = '';

void logScreen(String screenName) {
  if (_lastScreenName == screenName) return;
  print('logScreen: $screenName');

  kFirebaseAnalytics?.setCurrentScreen(screenName: screenName);
  _lastScreenName = screenName;
}

(如果你喜欢的话)你可以把这个放进一个单例里。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接