flutter_page_tracker 3.0.0

  • Readme
  • Changelog
  • Example
  • Installing
  • 79

flutter_page_tracker #

简介 #

FlutterPageTracker 是一个易用的 Flutter 应用页面事件埋点插件。它不仅支持在普通导航事件中监听页面曝光和离开,也支持弹窗的曝光和离开。

针对 TabView(PageView)形式的首页,FlutterPageTracker 可以监听每一个Tab的曝光和离开,并且把Tab形式的页面和普通页面衔接起来。

针对TabView和PageView组件相互嵌套的情况,FlutterPageTracker 可以对每一级嵌套分别监控埋点事件,大大提升埋点的效率。

它具有以下特性:

  • 1.监听普通页面的露出离开事件(PageRoute),
    • 当前页面入栈会触发当前页面的曝光事件和前一个页面的离开事件
    • 当前页面出栈会触发当前页面的离开事件和前一个页面的曝光事件
    • demo
  • 2.监听对话框的露出离开(PopupRoute),
    • 它和PageRoute的区别是,当前对话框的露出和关闭不会触发前一个页面的露出离开事件
    • demo
  • 3.监听PageView、TabView组件的切换事件
    • 当一个PageView或者TabView入栈时,前一个页面会触发页面离开事件
    • 当一个PageView或者TabView出栈时,前一个页面会触发页面曝光事件
    • 当焦点页面发生变化时,旧的页面触发页面露出,新的页面触发PageView
    • PageView组件
      • demo
    • TabView组件
      • demo
  • 4.PageView和TabView嵌套使用
    • 我们可以将这两种组件嵌套在一起使用,不限制嵌套的层次
    • 发生焦点变化的PageView(或者TabView)以及它的子级都会受到曝光事件离开事件
    • demo
  • 5.滑动曝光事件
    • 如果你对列表的滑动露出事件感兴趣,你可以参考flutter_sliver_tracker插件
    • https://github.com/SBDavid/flutter_sliver_tracker
    • demo

运行Demo程序 #

  • 克隆代码到本地: git clone git@github.com:SBDavid/flutter_page_tracker.git
  • 切换工作路径: cd flutter_page_tracker/example/
  • 启动模拟器
  • 运行: flutter run

使用 #

1. 安装 #

dependencies:
  flutter_page_tracker: ^1.2.2

2. 引入flutter_page_tracker #

import 'package:flutter_page_tracker/flutter_page_tracker.dart';

3. 发送普通页面埋点事件 #

3.1 添加路由监听

void main() => runApp(
  TrackerRouteObserverProvider(
    child: MyApp(),
  )
);

3.2 添加路由事件监听

class MyApp extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      // 添加路由事件监听
      navigatorObservers: [TrackerRouteObserverProvider.of(context)],
      home: MyHomePage(title: 'Flutter_Page_tracker Demo'),
    );
  }
}

3.3 在组件中发送埋点事件

必须使用PageTrackerAwareTrackerPageMixin这两个mixin

class HomePageState extends State<MyHomePage> with PageTrackerAware, TrackerPageMixin {
    @override
    Widget build(BuildContext context) {
        return Container();
    }

    @override
    void didPageView() {
        super.didPageView();
        // 发送页面露出事件
    }

    @override
    void didPageExit() {
        super.didPageExit();
        // 发送页面离开事件
    }
}

3.4 Dialog的埋点

class PopupPage extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return TrackerDialogWrapper(
     didPageView: () {
       print('dialog didPageView');
     },
     didPageExit: () {
       print('dialog didPageExit');
     },
     child: SimpleDialog(
       children: <Widget>[
         // body
       ],
     ),
   );
  }
}

3.5 PageView发送埋点事件

StatefulWidget中,推荐直接使用PageViewListenerMixin发送页面事件,如果是StatelessWidget组件则可以使用PageViewListenerWrapperPageViewListenerWrapper内部也是使用PageViewListenerMixin来发送事件。


// 嵌入到PageView组件中页面
class Page extends StatefulWidget {
  final int index;

  const Page({Key key, this.index}): super(key: key);

  @override
  PageState createState() {
    return PageState();
  }
}

class PageState extends State<Page> with PageTrackerAware, PageViewListenerMixin {

  int get pageViewIndex => widget.index;

  @override
  void didPageView() {
    super.didPageView();
    // 页面曝光事件
  }

  @override
  void didPageExit() {
    super.didPageExit();
    // 页面离开事件
  }

  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

// PageView组件
class PageViewMixinPage extends StatefulWidget {

  @override
  PageViewMixinPageState createState() => PageViewMixinPageState();
}

class PageViewMixinPageState extends State<PageViewMixinPage> {

  PageController pageController;

  @override
  void initState() {
    super.initState();
    pageController = PageController();
  }

  @override
  Widget build(BuildContext context) {
    return PageViewWrapper(
       changeDelegate: PageViewChangeDelegate(pageController),
       pageAmount: 3,
       initialPage: pageController.initialPage,
       child: PageView(
         controller: pageController,
         children: <Widget>[
           Page(index: 0,),
           Page(index: 1,),
           Page(index: 3,),
         ],
       ),
     );
  }
}

3.6 TabView发送埋点事件

在这个例子中我们只用PageViewListenerWrapper来发送页面事件,我们也可以向例子3.3中一样使用直接使用PageViewListenerMixin。 在StatefulWidget中,荐使用mixin更简洁。

class TabViewPage extends StatefulWidget {
  TabViewPage({Key key,}) : super(key: key);

  @override
  _State createState() => _State();
}

class _State extends State<TabViewPage> with TickerProviderStateMixin {
  TabController tabController = TabController(initialIndex: 0, length: 3, vsync: this);

  @override
  Widget build(BuildContext context) {

    return Scaffold(
        // 添加TabView的包裹层
        body: PageViewWrapper(
          // Tab页数量
          pageAmount: 3,
          // 初始Tab下标
          initialPage: 0, 
          // 监听Tab onChange事件
          changeDelegate: TabViewChangeDelegate(tabController),
          child: TabBarView(
            controller: tabController,
            children: <Widget>[
              Builder(
                builder: (_) {
                  // 监听由PageViewWrapper转发的PageView,PageExit事件
                  return PageViewListenerWrapper(
                    0,
                    onPageView: () {
                      // 发送页面曝光事件
                    },
                    onPageExit: () {
                      // 发送页面离开事件
                    },
                    child: Container(),
                  );
                },
              ),
              // 第二个Tab
              // 第三个Tab
            ],
          ),
        ),
    );
  }
}

3.7 TabView中嵌套PageView(PageView也可以嵌套TabView,TabView也可以嵌套TabView)

在这个例子中我们只用PageViewListenerWrapper来发送页面事件,我们也可以向例子3.5中一样使用直接使用PageViewListenerMixin。 在StatefulWidget中,荐使用mixin更简洁。

class PageViewInTabViewPage extends StatefulWidget {

  @override
  _State createState() => _State();
}

class _State extends State<PageViewInTabViewPage> with TickerProviderStateMixin {

  TabController tabController;
  PageController pageController;

  @override
  void initState() {
    super.initState();
    tabController = TabController(initialIndex: 0, length: 3, vsync: this);
    pageController = PageController();
  }

  @override
  Widget build(BuildContext context) {

    return Scaffold(
        // 外层TabView
        body: PageViewWrapper(
          pageAmount: 3, // 子Tab数量
          initialPage: 0, // 首个展现的Tab序号
          changeDelegate: TabViewChangeDelegate(tabController),
          child: TabBarView(
            controller: tabController,
            children: <Widget>[
              Builder(
                builder: (BuildContext context) {
                  // 转发上层的事件
                  return PageViewListenerWrapper(
                      0,
                      // 内层PageView
                      child: PageViewWrapper(
                        changeDelegate: PageViewChangeDelegate(pageController),
                        pageAmount: 3,
                        initialPage: pageController.initialPage,
                        child: PageView(
                          controller: pageController,
                          children: <Widget>[
                            PageViewListenerWrapper(
                              0,
                              onPageView: () {
                                // 页面露出事件
                              },
                              onPageExit: () {
                                // 页面离开事件
                              },
                              child: Container()
                            ),
                            // PageView中的第二个页面
                            // PageView中的第三个页面
                          ],
                        ),
                      )
                  );
                },
              ),
              // tab2
              // tab3
            ],
          ),
        )
    );
  }
}

原理篇 #

1.概述 #

页面的埋点追踪通常处于业务开发的最后一环,留给埋点的开发时间通常并不充裕,但是埋点数据对于后期的产品调整有重要的意义,所以一个稳定高效的埋点框架是非常重要的。

2. 我们期望埋点框架所具备的功能 #

2.1 PageView,PageExit事件

我们期望当调用Navigator.of(context).pushNamed("XXX Page");时,首先对之前的页面发送PageExit,然后对当前页面发送PageView事件。当调用Navigator.of(context).pop();时则,首先发送当前页面的PageExit事件,再发送之前页面的PageView事件。

我们首先想到的是使用RouteObserver,但是PageViewPageExit发送的顺序相反。并且PopupRoute类型的路由会影响前一个页面的埋点事件发送,例如我们入栈的顺序是 A页面 -> A页面上的弹窗 -> B页面,但是在这个过程中A页面的PageExit事件没有发送。

所以我们必须自己管理路由栈,这样判断不同路由的类型,并控制事件的顺序。详细实现方案在后面展开。

2.2 TagView组件于PageView组件

这两个组件虽然与Flutter的路由无关,但是在产品经理眼中它们任属于页面。并且当Tab发生首次曝光和切换的时候我们都需要发送埋点事件。

例如当Tab页A首次曝光时,我们首先发送上一个页面的PageExit事件,然后发送TabA的PageView事件。当我们从TabA切换到TabB的时候,先发送TabA的PageExit事件,然后发送TabB的PageView事件。当我们push一个新的路由时,需要发送TabB的PageExit事件。

这套流程需要Tab页和普通页面之间通过事件机制来交互,如果直接把这套机制搬到业务代码中,那么业务代码中就会包含大量与业务无关并且重复的代码。详细的抽象方案见后文。

3. 解决这些问题 #

3.1 解决PageView,PageExit的顺序问题

RouteObserver给了我们一个不错的起点,我们重写其中的didPopdidPush方法就并调整事件发送的顺序就可以解决这个问题。详见TrackerStackObserver,在didpop方法中我们先触发上一个路由的PageExit事件,然后再触发当前路由的PageView事件。

3.2 避免弹窗的干扰(例如Dialog)

RouteObserver.didPop(Route中,我们可以通过previousRoute找到上一个路由,并更具它来发送上一个路由的PageView事件。但是如果上一个路由是Dialog,就会造成错误,因为我们实际想要的是包含这个Dialog的路由。

要解决这个问题我们必须自己维护一个路由栈,这样当didPop触发时我们就可以找到真正的上一个路由。请参考这一段代码,这里的routes是当前的路由栈。

3.3 如何上报TabView中的埋点事件,并和其它页面串联起来

这个问题可以分解为两个小问题:

    1. 如何把TabView页面和普通的路由进行串联?
    1. 当Tab发生切换时如何发送埋点事件?

为了解决这两个问题,我们需要一个容器来管理tab页面的状态并且承载事件转发的任务。详见下图: 管理TabView中的事件

其中TabsWrapper会监听来自Flutter的路由事件,并转发给当前曝光的Tab,这就可以解决了问题一。

同时TabsWrappe也会包含一个TabController和上一个被打开的Tab索引,TabsWrappe会监听来自TabController的onChange(index)事件,并把事件转发给对应的tab,这就解决了问题二。

[0.0.1] - first publish #

[1.0.0] - add doc #

[1.0.1] - 完善文档 #

[1.0.2] - 修正文档中的错误 #

[1.0.3] - 添加图片 #

[1.0.4] - 添加图片 #

[1.1.0] - 支持在TabView中嵌套PageView #

[1.1.1] - - 修改provider版本到3.2.0 #

[1.2.0] - 支持弹窗埋点 #

[1.2.1] - bugfix: 弹窗埋点可以不传didPageView/didPageExit #

[1.2.2] - 升级demo和文档 #

[1.2.3] - 更具pub.dev的提示修改文档和代码 #

[2.0.0] - 去除对provider的依赖,并支持最新版本flutter1.9.1 #

[2.0.1] - 修改文档,增加flutter_sliver_tracker的外链 #

[2.0.2] - 修改文档,增加flutter_sliver_tracker的图片 #

[2.1.0] - PageView/TabView推荐直接使用mixin,但是StatelessWidget只能使用Wrapper #

[2.1.1] - bugfix: 使用PageViewListenerMixin上的of发放 #

[2.1.2] - bugfix: tab曝光埋点使用asBroadcastStream,支持多次事件绑定 #

[2.1.3] - bugfix: 控制数据的范围 #

[2.1.4] - bugfix: 在生产环境不抛出异常 #

[2.1.5] - readme: 添加文档,绑定RouteObserver #

[2.1.6] - readme: 弹窗埋点demo修改 #

[2.1.7] - bugfix: PageView组件,应该在首次注册回调事件的时候触发首次页面曝光。这样即使PageView组件随着焦点离开被销毁,也能发页面曝光事件 #

[2.1.8] - bugfix: 在dispose中捕获异常 #

[2.2.0] - feature: 统计页面加载时长 #

[2.2.1] - bugfix: rebuildStartTime #

[2.2.2] - 优化:较少统计页面加载时长对业务代码的入侵 #

[3.0.0] - 优化:分别计算三段加载时间,通过provider提供环境配置 #

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:flutter_page_tracker/flutter_page_tracker.dart';

// 页面
import 'home_page.dart';
import 'detail_page.dart';
import 'pageview_wrapper_page.dart';
import 'tabview_page.dart';
import 'pageview_in_tabview_page.dart';
import 'pageview_mixin_page.dart';

void main() => runApp(
  TrackerRouteObserverProvider(
    child: PageLoadProvider(env: "dev",child: MyApp()),
  )
);

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {

    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        textTheme: TextTheme(
          button: TextStyle(fontSize: 30),
          display1: TextStyle(fontSize: 40),
          display2: TextStyle(fontSize: 40),
          display3: TextStyle(fontSize: 40),
          display4: TextStyle(fontSize: 40),
          headline: TextStyle(fontSize: 40),
          title: TextStyle(fontSize: 40),
          subhead: TextStyle(fontSize: 40),
          body1: TextStyle(fontSize: 20, color: Colors.white),
        )
      ),
      navigatorObservers: [TrackerRouteObserverProvider.of(context)],
      home: MyHomePage(title: 'Flutter_Page_tracker Demo'),
      routes: {
        "home": (_) => MyHomePage(title: 'Flutter_Page_tracker Demo'),
        "detail": (_) => DetailPage(),
        "pageview_mixin": (_) => PageViewMixinPage(title: 'PageView Mixin Demo'),
        "pageview": (_) => PageViewWrapperPage(title: 'PageView Wrapper Demo'),
        "tabview": (_) => TabViewPage(title: 'TabView Demo'),
        "pageviewInTabView": (_) => PageviewInTabviewPage(title: 'Pageview Wraped in TabView demo'),
      },
    );
  }
}

Use this package as a library

1. Depend on it

Add this to your package's pubspec.yaml file:


dependencies:
  flutter_page_tracker: ^3.0.0

2. Install it

You can install packages from the command line:

with Flutter:


$ flutter pub get

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

3. Import it

Now in your Dart code, you can use:


import 'package:flutter_page_tracker/flutter_page_tracker.dart';
  
Popularity:
Describes how popular the package is relative to other packages. [more]
63
Health:
Code health derived from static analysis. [more]
97
Maintenance:
Reflects how tidy and up-to-date the package is. [more]
90
Overall:
Weighted score of the above. [more]
79
Learn more about scoring.

We analyzed this package on Jul 9, 2020, and provided a score, details, and suggestions below. Analysis was completed with status completed using:

  • Dart: 2.8.4
  • pana: 0.13.14
  • Flutter: 1.17.5

Analysis suggestions

Package does not support Flutter platform linux

Because:

  • package:flutter_page_tracker/flutter_page_tracker.dart that imports:
  • package:flutter_page_tracker/src/page_load_mixin.dart that imports:
  • package:fluttertoast/fluttertoast.dart that declares support for platforms: android, ios

Package does not support Flutter platform macos

Because:

  • package:flutter_page_tracker/flutter_page_tracker.dart that imports:
  • package:flutter_page_tracker/src/page_load_mixin.dart that imports:
  • package:fluttertoast/fluttertoast.dart that declares support for platforms: android, ios

Package does not support Flutter platform web

Because:

  • package:flutter_page_tracker/flutter_page_tracker.dart that imports:
  • package:flutter_page_tracker/src/page_load_mixin.dart that imports:
  • package:fluttertoast/fluttertoast.dart that declares support for platforms: android, ios

Package does not support Flutter platform windows

Because:

  • package:flutter_page_tracker/flutter_page_tracker.dart that imports:
  • package:flutter_page_tracker/src/page_load_mixin.dart that imports:
  • package:fluttertoast/fluttertoast.dart that declares support for platforms: android, ios

Package not compatible with SDK dart

Because:

  • flutter_page_tracker that is a package requiring null.

Health suggestions

Fix lib/src/page_view_listener_mixin.dart. (-1 points)

Analysis of lib/src/page_view_listener_mixin.dart reported 2 hints:

line 105 col 20: 'ancestorStateOfType' is deprecated and shouldn't be used. Use findAncestorStateOfType instead. This feature was deprecated after v1.12.1..

line 105 col 40: 'TypeMatcher' is deprecated and shouldn't be used. TypeMatcher has been deprecated because it is no longer used in framework(only in deprecated methods). This feature was deprecated after v1.12.1..

Fix lib/src/page_view_wrapper.dart. (-1 points)

Analysis of lib/src/page_view_wrapper.dart reported 2 hints:

line 38 col 12: 'ancestorStateOfType' is deprecated and shouldn't be used. Use findAncestorStateOfType instead. This feature was deprecated after v1.12.1..

line 39 col 11: 'TypeMatcher' is deprecated and shouldn't be used. TypeMatcher has been deprecated because it is no longer used in framework(only in deprecated methods). This feature was deprecated after v1.12.1..

Fix lib/src/page_load_provider.dart. (-0.50 points)

Analysis of lib/src/page_load_provider.dart reported 1 hint:

line 20 col 23: 'inheritFromWidgetOfExactType' is deprecated and shouldn't be used. Use dependOnInheritedWidgetOfExactType instead. This feature was deprecated after v1.12.1..

Fix additional 7 files with analysis or formatting issues. (-0.50 points)

Additional issues in the following files:

  • lib/src/tracker_route_observer_provider.dart (1 hint)
  • lib/src/page_load_mixin.dart (Run flutter format to format lib/src/page_load_mixin.dart.)
  • lib/src/page_tracker_aware.dart (Run flutter format to format lib/src/page_tracker_aware.dart.)
  • lib/src/tracker_dialog_wrapper.dart (Run flutter format to format lib/src/tracker_dialog_wrapper.dart.)
  • lib/src/tracker_page_mixin.dart (Run flutter format to format lib/src/tracker_page_mixin.dart.)
  • lib/src/tracker_page_widget.dart (Run flutter format to format lib/src/tracker_page_widget.dart.)
  • lib/src/tracker_route_observer.dart (Run flutter format to format lib/src/tracker_route_observer.dart.)

Maintenance issues and suggestions

Support latest dependencies. (-10 points)

The version constraint in pubspec.yaml does not support the latest published versions for 1 dependency (fluttertoast).

Dependencies

Package Constraint Resolved Available
Direct dependencies
Dart SDK >=2.1.0 <3.0.0
flutter 0.0.0
fluttertoast ^2.2.11 2.2.11 6.0.1
Transitive dependencies
collection 1.14.12 1.14.13
meta 1.1.8 1.2.2
sky_engine 0.0.99
typed_data 1.1.6 1.2.0
vector_math 2.0.8 2.1.0-nullsafety
Dev dependencies
flutter_test