Flutter頁面曝光事件埋點框架

flutter_page_tracker

GitHub鏈接git

簡介

FlutterPageTracker是一個用於監聽頁面露出離開的plugin。它具備如下特性:github

  • 1.監聽普通頁面的露出離開事件(PageRoute),
    • 當前頁面入棧會觸發當前頁面的曝光事件和前一個頁面的離開事件
    • 當前頁面出棧會觸發當前頁面的離開事件和前一個頁面的曝光事件
  • 2.監聽對話框的露出離開(PopupRoute),
    • 它和PageRoute的區別是,當前對話框的露出和關閉不會觸發前一個頁面的露出離開事件
  • 3.監聽PageView、TabView組件的切換事件
    • 當一個PageView或者TabView入棧時,前一個頁面會觸發頁面離開事件
    • 當一個PageView或者TabView出棧時,前一個頁面會觸發頁面曝光事件
    • 當焦點頁面發生變化時,舊的頁面觸發頁面露出,新的頁面觸發PageView
    • PageView組件
    • 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 在組件中發送埋點事件

必須使用PageTrackerAwareTrackerPageMixin這兩個mixinapp

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.3 Dialog的埋點

class PopupPage extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return SimpleDialog(
      children: <Widget>[
        TrackerDialogWrapper(
        
          didPageView: () {
            // 發送頁面曝光事件
          },
          
          didPageExit: () {
            // 發送頁面離開事件
          },
          child: Container(),
        ),
      ],
    );
  }
}
複製代碼

3.3 TabView發送埋點事件(PageView參考example)

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.4 TabView中嵌套PageView(PageView也能夠嵌套TabView,TabView也能夠嵌套TabView)

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事件。less

咱們首先想到的是使用RouteObserver,可是PageViewPageExit發送的順序相反。而且PopupRoute類型的路由會影響前一個頁面的埋點事件發送,例如咱們入棧的順序是 A頁面 -> A頁面上的彈窗 -> B頁面,可是在這個過程當中A頁面的PageExit事件沒有發送。ide

因此咱們必須本身管理路由棧,這樣判斷不一樣路由的類型,並控制事件的順序。詳細實現方案在後面展開。ui

2.2 TagView組件於PageView組件

這兩個組件雖然與Flutter的路由無關,可是在產品經理眼中它們任屬於頁面。而且當Tab發生首次曝光和切換的時候咱們都須要發送埋點事件。this

例如當Tab頁A首次曝光時,咱們首先發送上一個頁面的PageExit事件,而後發送TabA的PageView事件。當咱們從TabA切換到TabB的時候,先發送TabA的PageExit事件,而後發送TabB的PageView事件。當咱們push一個新的路由時,須要發送TabB的PageExit事件。spa

這套流程須要Tab頁和普通頁面之間經過事件機制來交互,若是直接把這套機制搬到業務代碼中,那麼業務代碼中就會包含大量與業務無關而且重複的代碼。詳細的抽象方案見後文。插件

3. 解決這些問題

3.1 解決PageView,PageExit的順序問題

RouteObserver給了咱們一個不錯的起點,咱們重寫其中的didPopdidPush方法就並調整事件發送的順序就能夠解決這個問題。詳見TrackerStackObserver,在didpop方法中咱們先觸發上一個路由的PageExit事件,而後再觸發當前路由的PageView事件。

3.2 避免彈窗的干擾(例如Dialog)

RouteObserver.didPop(Route route, Route previousRoute)中,咱們能夠經過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,這就解決了問題二。

相關文章
相關標籤/搜索